From 56f8c74732d8a06351ba069438326810c00bef31 Mon Sep 17 00:00:00 2001 From: Nameless Date: Mon, 18 Dec 2023 22:56:16 -0600 Subject: [PATCH 01/23] std.os.uefi: change Status enum to snake_case --- lib/std/os/uefi/status.zig | 157 ++++++++++++++++++++++++------------- 1 file changed, 104 insertions(+), 53 deletions(-) diff --git a/lib/std/os/uefi/status.zig b/lib/std/os/uefi/status.zig index e975b92a150d..6284874eb2b1 100644 --- a/lib/std/os/uefi/status.zig +++ b/lib/std/os/uefi/status.zig @@ -4,141 +4,148 @@ const high_bit = 1 << @typeInfo(usize).Int.bits - 1; pub const Status = enum(usize) { /// The operation completed successfully. - Success = 0, + success = 0, /// The image failed to load. - LoadError = high_bit | 1, + load_error = high_bit | 1, /// A parameter was incorrect. - InvalidParameter = high_bit | 2, + invalid_parameter = high_bit | 2, /// The operation is not supported. - Unsupported = high_bit | 3, + unsupported = high_bit | 3, /// The buffer was not the proper size for the request. - BadBufferSize = high_bit | 4, + bad_buffer_size = high_bit | 4, /// The buffer is not large enough to hold the requested data. The required buffer size is returned in the appropriate parameter when this error occurs. - BufferTooSmall = high_bit | 5, + buffer_too_small = high_bit | 5, /// There is no data pending upon return. - NotReady = high_bit | 6, + not_ready = high_bit | 6, /// The physical device reported an error while attempting the operation. - DeviceError = high_bit | 7, + device_error = high_bit | 7, /// The device cannot be written to. - WriteProtected = high_bit | 8, + write_protected = high_bit | 8, /// A resource has run out. - OutOfResources = high_bit | 9, + out_of_resources = high_bit | 9, /// An inconstancy was detected on the file system causing the operating to fail. - VolumeCorrupted = high_bit | 10, + volume_corrupted = high_bit | 10, /// There is no more space on the file system. - VolumeFull = high_bit | 11, + volume_full = high_bit | 11, /// The device does not contain any medium to perform the operation. - NoMedia = high_bit | 12, + no_media = high_bit | 12, /// The medium in the device has changed since the last access. - MediaChanged = high_bit | 13, + media_changed = high_bit | 13, /// The item was not found. - NotFound = high_bit | 14, + not_found = high_bit | 14, /// Access was denied. - AccessDenied = high_bit | 15, + access_denied = high_bit | 15, /// The server was not found or did not respond to the request. - NoResponse = high_bit | 16, + no_response = high_bit | 16, /// A mapping to a device does not exist. - NoMapping = high_bit | 17, + no_mapping = high_bit | 17, /// The timeout time expired. - Timeout = high_bit | 18, + timeout = high_bit | 18, /// The protocol has not been started. - NotStarted = high_bit | 19, + not_started = high_bit | 19, /// The protocol has already been started. - AlreadyStarted = high_bit | 20, + already_started = high_bit | 20, /// The operation was aborted. - Aborted = high_bit | 21, + aborted = high_bit | 21, /// An ICMP error occurred during the network operation. - IcmpError = high_bit | 22, + icmp_error = high_bit | 22, /// A TFTP error occurred during the network operation. - TftpError = high_bit | 23, + tftp_error = high_bit | 23, /// A protocol error occurred during the network operation. - ProtocolError = high_bit | 24, + protocol_error = high_bit | 24, /// The function encountered an internal version that was incompatible with a version requested by the caller. - IncompatibleVersion = high_bit | 25, + incompatible_version = high_bit | 25, /// The function was not performed due to a security violation. - SecurityViolation = high_bit | 26, + security_violation = high_bit | 26, /// A CRC error was detected. - CrcError = high_bit | 27, + crc_error = high_bit | 27, /// Beginning or end of media was reached - EndOfMedia = high_bit | 28, + end_of_media = high_bit | 28, /// The end of the file was reached. - EndOfFile = high_bit | 31, + end_of_file = high_bit | 31, /// The language specified was invalid. - InvalidLanguage = high_bit | 32, + invalid_language = high_bit | 32, /// The security status of the data is unknown or compromised and the data must be updated or replaced to restore a valid security status. - CompromisedData = high_bit | 33, + compromised_data = high_bit | 33, /// There is an address conflict address allocation - IpAddressConflict = high_bit | 34, + ip_address_conflict = high_bit | 34, /// A HTTP error occurred during the network operation. - HttpError = high_bit | 35, + http_error = high_bit | 35, - NetworkUnreachable = high_bit | 100, + /// The destination network was unreachable. + network_unreachable = high_bit | 100, - HostUnreachable = high_bit | 101, + /// The destination host was unreachable. + host_unreachable = high_bit | 101, - ProtocolUnreachable = high_bit | 102, + /// This protocol is not supported in the remote system. + protocol_unreachable = high_bit | 102, - PortUnreachable = high_bit | 103, + /// No service is available at this port on the remote host. + port_unreachable = high_bit | 103, - ConnectionFin = high_bit | 104, + /// The operation failed because the peer has closed the network connection. + connection_finished = high_bit | 104, - ConnectionReset = high_bit | 105, + /// The operation failed because the network connection was reset. + connection_reset = high_bit | 105, - ConnectionRefused = high_bit | 106, + /// The operation failed because the network connection was refused. + connection_refused = high_bit | 106, /// The string contained one or more characters that the device could not render and were skipped. - WarnUnknownGlyph = 1, + warn_unknown_glyph = 1, /// The handle was closed, but the file was not deleted. - WarnDeleteFailure = 2, + warn_delete_failure = 2, /// The handle was closed, but the data to the file was not flushed properly. - WarnWriteFailure = 3, + warn_write_failure = 3, /// The resulting buffer was too small, and the data was truncated to the buffer size. - WarnBufferTooSmall = 4, + warn_buffer_too_small = 4, /// The data has not been updated within the timeframe set by localpolicy for this type of data. - WarnStaleData = 5, + warn_stale_data = 5, /// The resulting buffer contains UEFI-compliant file system. - WarnFileSystem = 6, + warn_filesystem = 6, /// The operation will be processed across a system reset. - WarnResetRequired = 7, + warn_reset_required = 7, _, @@ -186,12 +193,56 @@ pub const Status = enum(usize) { }; pub fn err(self: Status) EfiError!void { - inline for (@typeInfo(EfiError).ErrorSet.?) |efi_err| { - if (self == @field(Status, efi_err.name)) { - return @field(EfiError, efi_err.name); - } + switch (self) { + .success => return, + .warn_unknown_glyph => return, + .warn_delete_failure => return, + .warn_write_failure => return, + .warn_buffer_too_small => return, + .warn_stale_data => return, + .warn_filesystem => return, + .warn_reset_required => return, + .load_error => return error.LoadError, + .invalid_parameter => return error.InvalidParameter, + .unsupported => return error.Unsupported, + .bad_buffer_size => return error.BadBufferSize, + .buffer_too_small => return error.BufferTooSmall, + .not_ready => return error.NotReady, + .device_error => return error.DeviceError, + .write_protected => return error.WriteProtected, + .out_of_resources => return error.OutOfResources, + .volume_corrupted => return error.VolumeCorrupted, + .volume_full => return error.VolumeFull, + .no_media => return error.NoMedia, + .media_changed => return error.MediaChanged, + .not_found => return error.NotFound, + .access_denied => return error.AccessDenied, + .no_response => return error.NoResponse, + .no_mapping => return error.NoMapping, + .timeout => return error.Timeout, + .not_started => return error.NotStarted, + .already_started => return error.AlreadyStarted, + .aborted => return error.Aborted, + .icmp_error => return error.IcmpError, + .tftp_error => return error.TftpError, + .protocol_error => return error.ProtocolError, + .incompatible_version => return error.IncompatibleVersion, + .security_violation => return error.SecurityViolation, + .crc_error => return error.CrcError, + .end_of_media => return error.EndOfMedia, + .end_of_file => return error.EndOfFile, + .invalid_language => return error.InvalidLanguage, + .compromised_data => return error.CompromisedData, + .ip_address_conflict => return error.IpAddressConflict, + .http_error => return error.HttpError, + .network_unreachable => return error.NetworkUnreachable, + .host_unreachable => return error.HostUnreachable, + .protocol_unreachable => return error.ProtocolUnreachable, + .port_unreachable => return error.PortUnreachable, + .connection_finished => return error.ConnectionFin, + .connection_reset => return error.ConnectionReset, + .connection_refused => return error.ConnectionRefused, } - // self is .Success or Warning } }; From dbcda3749e183f021228aaceec9ebc87b202257a Mon Sep 17 00:00:00 2001 From: Nameless Date: Tue, 19 Dec 2023 11:26:04 -0600 Subject: [PATCH 02/23] std.os.uefi: add zig-like bindings for boot services --- lib/std/os/uefi.zig | 2 + lib/std/os/uefi/status.zig | 6 +- lib/std/os/uefi/tables/boot_services.zig | 1117 ++++++++++++++++++++-- lib/std/os/uefi/tables/header.zig | 28 + lib/std/os/uefi/tables/table_header.zig | 9 - 5 files changed, 1052 insertions(+), 110 deletions(-) create mode 100644 lib/std/os/uefi/tables/header.zig delete mode 100644 lib/std/os/uefi/tables/table_header.zig diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 165c3df2495f..eaca768ebb90 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -31,6 +31,8 @@ pub const cc = switch (@import("builtin").target.cpu.arch) { else => .C, }; +pub const Crc32 = std.hash.crc.Crc32IsoHdlc; + pub const MacAddress = extern struct { address: [32]u8, }; diff --git a/lib/std/os/uefi/status.zig b/lib/std/os/uefi/status.zig index 6284874eb2b1..476e0b7f44fd 100644 --- a/lib/std/os/uefi/status.zig +++ b/lib/std/os/uefi/status.zig @@ -1,5 +1,3 @@ -const testing = @import("std").testing; - const high_bit = 1 << @typeInfo(usize).Int.bits - 1; pub const Status = enum(usize) { @@ -150,6 +148,7 @@ pub const Status = enum(usize) { _, pub const EfiError = error{ + Unknown, LoadError, InvalidParameter, Unsupported, @@ -242,10 +241,13 @@ pub const Status = enum(usize) { .connection_finished => return error.ConnectionFin, .connection_reset => return error.ConnectionReset, .connection_refused => return error.ConnectionRefused, + else => return error.Unknown, } } }; +const testing = @import("../../std.zig").testing; + test "status" { var st: Status = .DeviceError; try testing.expectError(error.DeviceError, st.err()); diff --git a/lib/std/os/uefi/tables/boot_services.zig b/lib/std/os/uefi/tables/boot_services.zig index 04d912753b16..a518bf5c5910 100644 --- a/lib/std/os/uefi/tables/boot_services.zig +++ b/lib/std/os/uefi/tables/boot_services.zig @@ -16,6 +16,18 @@ const OpenProtocolAttributes = uefi.tables.OpenProtocolAttributes; const ProtocolInformationEntry = uefi.tables.ProtocolInformationEntry; const EfiEventNotify = uefi.tables.EfiEventNotify; const cc = uefi.cc; +const Guid = uefi.Guid; +const Status = uefi.Status; + +const DevicePathProtocol = uefi.protocol.DevicePath; + +const Event = uefi.Event; +const Handle = uefi.Handle; + +const PhysicalAddress = u64; +const VirtualAddress = u64; +const ProtocolInterface = *const anyopaque; +const RegistrationValue = *const anyopaque; /// Boot services are services provided by the system's firmware until the operating system takes /// over control over the hardware by calling exitBootServices. @@ -29,146 +41,731 @@ const cc = uefi.cc; /// /// As the boot_services table may grow with new UEFI versions, it is important to check hdr.header_size. pub const BootServices = extern struct { - hdr: TableHeader, + hdr: uefi.tables.TableHeader, + + _raiseTpl: *const fn (new_tpl: TaskPriorityLevel) callconv(cc) TaskPriorityLevel, + _restoreTpl: *const fn (old_tpl: TaskPriorityLevel) callconv(cc) void, + + _allocatePages: *const fn (alloc_type: AllocateType, mem_type: MemoryType, pages: usize, memory: PhysicalAddress) callconv(cc) Status, + _freePages: *const fn (memory: [*]align(4096) u8, pages: usize) callconv(cc) Status, + _getMemoryMap: *const fn (mmap_size: *usize, mmap: ?*const anyopaque, mapKey: *MemoryMap.Key, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status, + _allocatePool: *const fn (pool_type: MemoryType, size: usize, buffer: *[*]align(8) u8) callconv(cc) Status, + _freePool: *const fn (buffer: [*]align(8) u8) callconv(cc) Status, + + _createEvent: *const fn (type: u32, notify_tpl: TaskPriorityLevel, notify_func: ?EventNotify, notifyCtx: ?EventNotifyContext, event: *Event) callconv(cc) Status, + _setTimer: *const fn (event: Event, type: TimerKind.Enum, trigger_time: u64) callconv(cc) Status, + _waitForEvent: *const fn (event_len: usize, events: [*]const Event, index: *usize) callconv(cc) Status, + _signalEvent: *const fn (event: Event) callconv(cc) Status, + _closeEvent: *const fn (event: Event) callconv(cc) Status, + _checkEvent: *const fn (event: Event) callconv(cc) Status, + + _installProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, interface_type: EfiInterfaceType, interface: ProtocolInterface) callconv(cc) Status, + _reinstallProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, old_interface: ProtocolInterface, new_interface: ProtocolInterface) callconv(cc) Status, + _uninstallProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: ProtocolInterface) callconv(cc) Status, + + // this function is deprecated, it will not be bound. + _handleProtocol: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: *?ProtocolInterface) callconv(cc) Status, + + reserved: *const anyopaque, + + _registerProtocolNotify: *const fn (protocol: *align(8) const Guid, event: Event, registration: *RegistrationValue) callconv(cc) Status, + _locateHandle: *const fn (search_type: LocateSearchType, protocol: ?*align(8) const Guid, search_key: ?RegistrationValue, buffer_size: *usize, buffer: [*]Handle) callconv(cc) Status, + _locateDevicePath: *const fn (protocol: *align(8) const Guid, device_path: **const DevicePathProtocol, device: *Handle) callconv(cc) Status, + _installConfigurationTable: *const fn (guid: *align(8) const Guid, table: ?*const anyopaque) callconv(cc) Status, + + _loadImage: *const fn (boot_policy: bool, parent_image_handle: Handle, device_path: ?*const DevicePathProtocol, source_buffer: ?[*]const u8, source_size: usize, image_handle: *?Handle) callconv(cc) Status, + _startImage: *const fn (image_handle: Handle, exit_data_size: ?*usize, exit_data: ?*[*]align(2) const u8) callconv(cc) Status, + _exit: *const fn (image_handle: Handle, exit_status: Status, exit_data_size: usize, exit_data: ?[*]align(2) const u8) callconv(cc) Status, + _unloadImage: *const fn (image_handle: Handle) callconv(cc) Status, + _exitBootServices: *const fn (image_handle: Handle, map_key: usize) callconv(cc) Status, + + _getNextMonotonicCount: *const fn (count: *u64) callconv(cc) Status, + _stall: *const fn (microseconds: usize) callconv(cc) Status, + _setWatchdogTimer: *const fn (timeout: usize, watchdogCode: u64, data_size: usize, watchdog_data: ?[*]const u8) callconv(cc) Status, + + // Following introduced in EFI 1.1 + _connectController: *const fn (controller_handle: Handle, driver_image_handle: ?[*:null]?Handle, remaining_device_path: ?*const DevicePathProtocol, recursive: bool) callconv(cc) Status, + _disconnectController: *const fn (controller_handle: Handle, driver_image_handle: ?Handle, child_handle: ?Handle) callconv(cc) Status, + + _openProtocol: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: *?ProtocolInterface, agent_handle: ?Handle, controller_handle: ?Handle, attributes: OpenProtocolAttributes) callconv(cc) Status, + _closeProtocol: *const fn (handle: Handle, protocol: *align(8) const Guid, agent_handle: Handle, controller_handle: ?Handle) callconv(cc) Status, + _openProtocolInformation: *const fn (handle: Handle, protocol: *align(8) const Guid, entry_buffer: *[*]const ProtocolInformationEntry, entry_count: *usize) callconv(cc) Status, + _protocolsPerHandle: *const fn (handle: Handle, protocol_buffer: *[*]*align(8) const Guid, protocol_buffer_count: *usize) callconv(cc) Status, + _locateHandleBuffer: *const fn (search_type: LocateSearchType, protocol: ?*align(8) const Guid, registration: ?RegistrationValue, num_handles: *usize, buffer: *[*]Handle) callconv(cc) Status, + _locateProtocol: *const fn (protocol: *align(8) const Guid, registration: ?RegistrationValue, interface: *?ProtocolInterface) callconv(cc) Status, + + // TODO: use callconv(cc) instead once that works + _installMultipleProtocolInterfaces: *const fn (handle: *Handle, ...) callconv(.C) Status, + + // TODO: use callconv(cc) instead once that works + _uninstallMultipleProtocolInterfaces: *const fn (handle: *Handle, ...) callconv(.C) Status, + + // this function is just an implementation of the crc32, so we don't need to bind it + _calculateCrc32: *const fn (data: [*]const u8, data_size: usize, *u32) callconv(cc) Status, + + // these two functions just implement memcpy and memset, so we don't need to bind them + _copyMem: *const fn (dest: [*]u8, src: [*]const u8, len: usize) callconv(cc) void, + _setMem: *const fn (buffer: [*]u8, size: usize, value: u8) callconv(cc) void, + + // Following introduced in UEFI 2.0 + _createEventEx: *const fn (type: u32, notify_tpl: TaskPriorityLevel, notify_func: ?EventNotify, notify_ctx: ?EventNotifyContext, event_group: ?*align(8) const Guid, event: *Event) callconv(cc) Status, + + /// Creates an event. + pub fn createEvent( + self: *const BootServices, + /// The type of event to create and its mode and attributes. See `EventKind` for more information. + kind: u32, + /// The task priority level of event notifications. + notify_tpl: TaskPriorityLevel, + /// Pointer to the event’s notification function, if any. + notify_func: ?EventNotify, + /// Pointer to the notification function’s context; corresponds to the *notify_ctx* parameter of the notification. + notify_ctx: ?EventNotifyContext, + ) !Event { + var event: Event = undefined; + try self._createEvent(kind, notify_tpl, notify_func, notify_ctx, &event).err(); + return event; + } + + /// Creates an event in a group. + pub fn createEventEx( + self: *const BootServices, + /// The type of event to create and its mode and attributes. See `EventKind` for more information. + kind: u32, + /// The task priority level of event notifications. + notify_tpl: TaskPriorityLevel, + /// Pointer to the event’s notification function, if any. + notify_func: ?EventNotify, + /// Pointer to the notification function’s context; corresponds to the *notify_ctx* parameter of the notification. + notify_ctx: ?EventNotifyContext, + /// Pointer to the unique identifier of the group to which this event belongs. + event_group: *align(8) const Guid, + ) !Event { + if (!self.hdr.isAtLeastRevision(2, 0)) + return error.Unsupported; + + var event: Event = undefined; + try self._createEventEx(kind, notify_tpl, notify_func, notify_ctx, event_group, &event).err(); + return event; + } + + /// Close an event. + pub fn closeEvent( + self: *const BootServices, + /// The event to close. + event: Event, + ) void { + _ = self._closeEvent(event); + } + + /// Signal an event. + pub fn signalEvent( + self: *const BootServices, + /// The event to signal. + event: Event, + ) void { + _ = self._signalEvent(event); + } + + /// Stops execution until the first event is signaled. + /// + /// Returns the index of the event which was signaled. + pub fn waitForEvent( + self: *const BootServices, + /// A slice of events to wait on. + events: []const Event, + ) !usize { + var index: usize = 0; + try self._waitForEvent(events.len, events.ptr, &index).err(); + return index; + } + + /// Checks whether an event is in the signaled state. + /// + /// Cannot be called on events of type `EventKind.notify_signal`. + pub fn checkEvent( + self: *const BootServices, + /// The event to check. + event: Event, + ) !bool { + var status: Status = self._checkEvent(event); + switch (status) { + .success => return true, + .not_ready => return false, + else => return status.err(), + } + } + + /// Sets the type of timer and the trigger time for a timer event. + /// + /// Timers only have 100ns time resolution. + pub fn setTimer( + self: *const BootServices, + /// The event to signal. + event: Event, + /// The type of timer and the trigger time for a timer event. + kind: TimerKind, + ) !void { + switch (kind) { + .cancel => try self._setTimer(event, kind, 0).err(), + .periodic => |n| try self._setTimer(event, kind, n / 100).err(), + .relative => |n| try self._setTimer(event, kind, n / 100).err(), + } + } /// Raises a task's priority level and returns its previous level. - raiseTpl: *const fn (new_tpl: usize) callconv(cc) usize, + /// + /// The caller must restore the task priority level with `restoreTPL()` to the previous level before + /// returning control to the system. + pub fn raiseTpl( + self: *const BootServices, + /// The new priority level. + new_tpl: TaskPriorityLevel, + ) TaskPriorityLevel { + return self._raiseTpl(new_tpl); + } /// Restores a task's priority level to its previous value. - restoreTpl: *const fn (old_tpl: usize) callconv(cc) void, + pub fn restoreTpl( + self: *const BootServices, + /// The previous priority level. + old_tpl: TaskPriorityLevel, + ) void { + self._restoreTpl(old_tpl); + } /// Allocates memory pages from the system. - allocatePages: *const fn (alloc_type: AllocateType, mem_type: MemoryType, pages: usize, memory: *[*]align(4096) u8) callconv(cc) Status, + /// + /// The memory returned is physical memory, apply the virtual address map to get the correct virtual address. + pub fn allocatePages( + self: *const BootServices, + /// The type of allocation to perform. + alloc_type: AllocateType, + /// The type of memory to allocate. + mem_type: MemoryType, + /// The number of contiguous 4 KiB pages to allocate. + pages: usize, + ) ![]align(4096) u8 { + var buffer: [*]align(4096) u8 = switch (alloc_type) { + .any => @ptrFromInt(0), + .max_address => |addr| @ptrFromInt(addr), + .at_address => |addr| @ptrFromInt(addr), + }; + + // EFI memory addresses are always 64-bit, even on 32-bit systems + const pointer: PhysicalAddress = @intFromPtr(&buffer); + + try self._allocatePages(alloc_type, mem_type, pages, pointer).err(); + return buffer[0 .. pages * 4096]; + } /// Frees memory pages. - freePages: *const fn (memory: [*]align(4096) u8, pages: usize) callconv(cc) Status, + /// + /// The slice must point to the physical address of the pages. + pub fn freePages( + self: *const BootServices, + /// The slice of the pages to be freed. + memory: []align(4096) u8, + ) void { + // any error here arises from user error (ie. freeing a page not allocated by allocatePages) or a firmware bug + _ = self._freePages(memory.ptr, @divExact(memory.len, 4096)); + } + + /// Returns the size of the current memory map. + /// + /// It is recommended to call this in a loop until it returns `null`. + pub fn getMemoryMapSize( + self: *const BootServices, + /// The size of the memory currently allocated for the map. + previous_size: usize, + ) !?usize { + var mmap_size: usize = previous_size; + var mmap_key: MemoryMap.Key = 0; + var descriptor_size: usize = 0; + var descriptor_version: u32 = 0; - /// Returns the current memory map. - getMemoryMap: *const fn (mmap_size: *usize, mmap: ?[*]MemoryDescriptor, mapKey: *usize, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status, + switch (self._getMemoryMap(&mmap_size, null, &mmap_key, &descriptor_size, &descriptor_version)) { + .buffer_too_small => return mmap_size, + .invalid_parameter => return null, + else => |s| return s.err(), + } + } + + /// Fetches the current memory map. + /// + /// Use `getMemoryMapSize()` to determine the size of the buffer to allocate. + pub fn getMemoryMap( + self: *const BootServices, + /// The memory map to fill. + map: *MemoryMap, + ) !void { + var mmap_size: usize = map.size; + var mmap_key: MemoryMap.Key = map.key; + var descriptor_size: usize = map.descriptor_size; + var descriptor_version: u32 = map.descriptor_version; + + try self._getMemoryMap(&mmap_size, map.map, &mmap_key, &descriptor_size, &descriptor_version).err(); + + map.size = mmap_size; + map.key = mmap_key; + map.descriptor_size = descriptor_size; + map.descriptor_version = descriptor_version; + } /// Allocates pool memory. - allocatePool: *const fn (pool_type: MemoryType, size: usize, buffer: *[*]align(8) u8) callconv(cc) Status, + /// + /// All allocations are 8-byte aligned. + pub fn allocatePool( + self: *const BootServices, + /// The type of pool to allocate. + pool_type: MemoryType, + /// The number of bytes to allocate. + size: usize, + ) ![]align(8) u8 { + var buffer: [*]align(8) u8 = undefined; + try self._allocatePool(pool_type, size, &buffer).err(); + + const aligned_size = std.mem.alignForward(usize, size, 8); + return buffer[0..aligned_size]; + } /// Returns pool memory to the system. - freePool: *const fn (buffer: [*]align(8) u8) callconv(cc) Status, + /// + /// Does *not* allow partial frees, the entire allocation will be freed, even if the slice is a segment. + pub fn freePool( + self: *const BootServices, + /// The slice of the pool to be freed. + buffer: []align(8) u8, + ) void { + // any error here arises from user error (ie. freeing a page not allocated by allocatePool) or a firmware bug + _ = self._freePool(buffer.ptr); + } - /// Creates an event. - createEvent: *const fn (type: u32, notify_tpl: usize, notify_func: ?*const fn (Event, ?*anyopaque) callconv(cc) void, notifyCtx: ?*const anyopaque, event: *Event) callconv(cc) Status, + /// Installs a protocol interface on a device handle. If the handle does not exist, it is created and added to the + /// list of handles in the system. + /// + /// It is recommended to use `installMultipleProtocolInterfaces()` instead, as it performs more error checking. + pub fn installProtocolInterface( + self: *const BootServices, + /// The handle to install the protocol interface on. + handle: ?Handle, + /// The GUID of the protocol. + protocol: *align(8) const Guid, + /// The type of interface to install. + interface_type: EfiInterfaceType, + /// The interface to install. Can only be `null` if `protocol` refers to a protocol that has no interface. + interface: ?ProtocolInterface, + ) !Handle { + var new_handle: ?Handle = handle; + try self._installProtocolInterface(&new_handle, protocol, interface_type, interface).err(); + return new_handle; + } - /// Sets the type of timer and the trigger time for a timer event. - setTimer: *const fn (event: Event, type: TimerDelay, triggerTime: u64) callconv(cc) Status, + /// Uninstalls a protocol interface from a device handle. + /// + /// The caller ensures that there are no references to the old interface that is being removed. + /// + /// It is recommended to use `uninstallMultipleProtocolInterfaces()` instead. + pub fn uninstallProtocolInterface( + self: *const BootServices, + /// The handle to uninstall the protocol interface from. + handle: Handle, + /// The GUID of the protocol. + protocol: *align(8) const Guid, + /// The interface to uninstall. Can only be `null` if `protocol` refers to a protocol that has no interface. + interface: ?ProtocolInterface, + ) !void { + try self._uninstallProtocolInterface(handle, protocol, interface).err(); + } - /// Stops execution until an event is signaled. - waitForEvent: *const fn (event_len: usize, events: [*]const Event, index: *usize) callconv(cc) Status, + /// Reinstalls a protocol interface on a device handle. + /// + /// The caller ensures that there are no references to the old interface that is being removed. + pub fn reinstallProtocolInterface( + self: *const BootServices, + /// The handle to reinstall the protocol interface on. + handle: Handle, + /// The GUID of the protocol. + protocol: *align(8) const Guid, + /// The old interface to uninstall. Can only be `null` if `protocol` refers to a protocol that has no interface. + old_interface: ?ProtocolInterface, + /// The new interface to install. Can only be `null` if `protocol` refers to a protocol that has no interface. + new_interface: ?ProtocolInterface, + ) !void { + try self._reinstallProtocolInterface(handle, protocol, old_interface, new_interface).err(); + } - /// Signals an event. - signalEvent: *const fn (event: Event) callconv(cc) Status, + /// Creates an event that is to be signaled whenever an interface is installed for a specified protocol. + pub fn registerProtocolNotify( + self: *const BootServices, + /// The GUID of the protocol. + protocol: *align(8) const Guid, + /// The event to signal when the protocol is installed. + event: Event, + /// A pointer to a memory location to receive the registration value. THe value must be saved and used by the + /// notification function to retrieve the list of handles that have added or removed the protocol. + registration: **const anyopaque, + ) !void { + try self._registerProtocolNotify(protocol, event, registration).err(); + } - /// Closes an event. - closeEvent: *const fn (event: Event) callconv(cc) Status, + /// Returns the size in bytes of the buffer that is required to hold the list of handles that support a specified + /// protocol. + pub fn locateHandleSize( + self: *const BootServices, + /// The type of search to perform. + search_type: LocateSearchType, + ) !usize { + var buffer_size: usize = 0; - /// Checks whether an event is in the signaled state. - checkEvent: *const fn (event: Event) callconv(cc) Status, + const status = switch (search_type) { + .all => self._locateHandle(search_type, null, null, &buffer_size, null), + .by_notify => |search_key| self._locateHandle(search_type, null, search_key, &buffer_size, null), + .by_protocol => |protocol| self._locateHandle(search_type, protocol, null, &buffer_size, null), + }; - /// Installs a protocol interface on a device handle. If the handle does not exist, it is created - /// and added to the list of handles in the system. installMultipleProtocolInterfaces() - /// performs more error checking than installProtocolInterface(), so its use is recommended over this. - installProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, interface_type: EfiInterfaceType, interface: *anyopaque) callconv(cc) Status, + switch (status) { + .buffer_too_small => return buffer_size, + else => return status.err(), + } + } - /// Reinstalls a protocol interface on a device handle - reinstallProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, old_interface: *anyopaque, new_interface: *anyopaque) callconv(cc) Status, + /// Returns an array of handles that support a specified protocol. + /// + /// Use `locateHandleSize()` to determine the size of the buffer to allocate. + pub fn locateHandle( + self: *const BootServices, + /// The type of search to perform. + search_type: LocateSearchType, + /// The buffer in which to return the array of handles. + buffer: [*]u8, + ) ![]Handle { + var handle_buffer: [*]Handle = @ptrCast(buffer.ptr); + var buffer_size: usize = buffer.len; - /// Removes a protocol interface from a device handle. Usage of - /// uninstallMultipleProtocolInterfaces is recommended over this. - uninstallProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: *anyopaque) callconv(cc) Status, + switch (search_type) { + .all => try self._locateHandle(search_type, null, null, &buffer_size, &handle_buffer).err(), + .by_notify => |search_key| self._locateHandle(search_type, null, search_key, &buffer_size, &handle_buffer), + .by_protocol => |protocol| self._locateHandle(search_type, protocol, null, &buffer_size, &handle_buffer), + } - /// Queries a handle to determine if it supports a specified protocol. - handleProtocol: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: *?*anyopaque) callconv(cc) Status, + return buffer[0..@divExact(buffer_size, @sizeOf(Handle))]; + } - reserved: *anyopaque, + pub const LocatedDevicePath = struct { ?Handle, *const DevicePathProtocol }; - /// Creates an event that is to be signaled whenever an interface is installed for a specified protocol. - registerProtocolNotify: *const fn (protocol: *align(8) const Guid, event: Event, registration: **anyopaque) callconv(cc) Status, + /// Locates the handle to a device on the device path that supports the specified protocol. + pub fn locateDevicePath( + self: *const BootServices, + /// The GUID of the protocol. + protocol: *align(8) const Guid, + /// The device path to search for. + device_path: *const DevicePathProtocol, + ) !LocatedDevicePath { + var handle: ?Handle = null; + var path: *const DevicePathProtocol = device_path; + switch (self._locateDevicePath(protocol, &handle, &path)) { + .success => return .{ handle, path }, + .not_found => return .{ null, path }, + else => |status| return status.err(), + } + } - /// Returns an array of handles that support a specified protocol. - locateHandle: *const fn (search_type: LocateSearchType, protocol: ?*align(8) const Guid, search_key: ?*const anyopaque, bufferSize: *usize, buffer: [*]Handle) callconv(cc) Status, + /// Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the handle, + /// it opens the protocol on behalf of the calling agent. + pub fn openProtocol( + self: *const BootServices, + /// The handle for the protocol interface that is being opened. + handle: Handle, + /// The GUID of the protocol to open. + protocol: *align(8) const Guid, + /// The handle of the agent that is opening the protocol interface specified by `protocol`. + agent_handle: ?Handle, + /// The handle of the controller that requires the protocol interface. + controller_handle: ?Handle, + /// Attributes to open the protocol with. + attributes: OpenProtocolAttributes, + ) !?ProtocolInterface { + var interface: ?ProtocolInterface = undefined; + try self._openProtocol(handle, protocol, &interface, agent_handle, controller_handle, attributes).err(); + return interface; + } - /// Locates the handle to a device on the device path that supports the specified protocol - locateDevicePath: *const fn (protocols: *align(8) const Guid, device_path: **const DevicePathProtocol, device: *?Handle) callconv(cc) Status, + /// Closes a protocol on a handle that was opened using `openProtocol()`. + pub fn closeProtocol( + self: *const BootServices, + /// The handle for the protocol interface that was previously opened with `openProtocol()`. + handle: Handle, + /// The GUID of the protocol to close. + protocol: *align(8) const Guid, + /// The handle of the agent that is closing the protocol interface specified by `protocol`. + agent_handle: Handle, + /// The handle of the controller that required the protocol interface. + controller_handle: ?Handle, + ) void { + // any error here arises from user error (ie. closing a protocol that was not supported) or a firmware bug + _ = self._closeProtocol(handle, protocol, agent_handle, controller_handle); + } - /// Adds, updates, or removes a configuration table entry from the EFI System Table. - installConfigurationTable: *const fn (guid: *align(8) const Guid, table: ?*anyopaque) callconv(cc) Status, + /// Retrieves the list of agents that currently have a protocol interface opened in a buffer allocated from the pool. + /// Returns `null` if the handle does not support the requested protocol. + /// + /// The caller owns the returned pool memory, it should be freed with `freePool`. + pub fn openProtocolInformation( + self: *const BootServices, + /// The handle for the protocol interface that is being queried. + handle: Handle, + /// The GUID of the protocol to list. + protocol: *align(8) const Guid, + ) !?[]const ProtocolInformationEntry { + var entry_buffer: [*]const ProtocolInformationEntry = undefined; + var entry_count: usize = 0; + switch (self._openProtocolInformation(handle, protocol, &entry_buffer, &entry_count)) { + .success => return entry_buffer[0..entry_count], + .not_found => return null, + else => |status| return status.err(), + } + } - /// Loads an EFI image into memory. - loadImage: *const fn (boot_policy: bool, parent_image_handle: Handle, device_path: ?*const DevicePathProtocol, source_buffer: ?[*]const u8, source_size: usize, imageHandle: *?Handle) callconv(cc) Status, + /// Connects one or more drivers to a controller. + pub fn connectController( + self: *const BootServices, + /// The handle of the controller to connect. + controller_handle: Handle, + /// The handle of the driver image that is connecting to the controller. + driver_image_handle: ?Handle, + /// The remaining device path. + /// + /// If null, then handles for all children of the controller will be created. + remaining_device_path: ?*const DevicePathProtocol, + /// Whether to connect all children. + recursive: bool, + ) !void { + try self._connectController(controller_handle, driver_image_handle, remaining_device_path, recursive).err(); + } - /// Transfers control to a loaded image's entry point. - startImage: *const fn (image_handle: Handle, exit_data_size: ?*usize, exit_data: ?*[*]u16) callconv(cc) Status, + /// Disconnects one or more drivers from a controller. + pub fn disconnectController( + self: *const BootServices, + /// The handle of the controller to disconnect. + controller_handle: Handle, + /// The handle of the driver image that is disconnecting from the controller. + /// + /// If null, then all drivers currently managing the controller are disconnected from the controller. + driver_image_handle: ?Handle, + /// The handle of the child controller to disconnect. + /// + /// If null, then all children of the controller are destroyed before the drivers are disconnected from the controller. + child_handle: ?Handle, + ) !void { + try self._disconnectController(controller_handle, driver_image_handle, child_handle).err(); + } - /// Terminates a loaded EFI image and returns control to boot services. - exit: *const fn (image_handle: Handle, exit_status: Status, exit_data_size: usize, exit_data: ?*const anyopaque) callconv(cc) Status, + /// Retrieves the list of protocol interface GUIDs that are installed on a handle in a buffer allocated from the pool. + /// + /// The caller owns the returned pool memory, it should be freed with `freePool`. + pub fn protocolsPerHandle( + self: *const BootServices, + /// The handle for the protocol interface that is being queried. + handle: Handle, + ) ![]const *const Guid { + var protocol_buffer: [*]const *const Guid = undefined; + var protocol_buffer_count: usize = 0; + try self._protocolsPerHandle(handle, &protocol_buffer, &protocol_buffer_count).err(); + return protocol_buffer[0..protocol_buffer_count]; + } - /// Unloads an image. - unloadImage: *const fn (image_handle: Handle) callconv(cc) Status, + /// Returns an array of handles that support the requested protocol in a buffer allocated from the pool. + /// + /// The caller owns the returned pool memory, it should be freed with `freePool`. + pub fn locateHandleBuffer( + self: *const BootServices, + /// The type of search to perform. + search_type: LocateSearchType, + ) ![]Handle { + var handle_buffer: [*]Handle = undefined; + var num_handles: usize = 0; - /// Terminates all boot services. - exitBootServices: *const fn (image_handle: Handle, map_key: usize) callconv(cc) Status, + switch (search_type) { + .all => try self._locateHandleBuffer(search_type, null, null, &num_handles, &handle_buffer).err(), + .by_notify => |search_key| self._locateHandleBuffer(search_type, null, search_key, &num_handles, &handle_buffer), + .by_protocol => |protocol| self._locateHandleBuffer(search_type, protocol, null, &num_handles, &handle_buffer), + } - /// Returns a monotonically increasing count for the platform. - getNextMonotonicCount: *const fn (count: *u64) callconv(cc) Status, + return handle_buffer[0..num_handles]; + } - /// Induces a fine-grained stall. - stall: *const fn (microseconds: usize) callconv(cc) Status, + /// Returns the first protocol instance that matches the given protocol. + pub fn locateProtocol( + self: *const BootServices, + /// The GUID of the protocol. + protocol: *align(8) const Guid, + /// An optional registration key returned from `registerProtocolNotify()`. + registration: ?*const anyopaque, + ) !?ProtocolInterface { + var interface: ?ProtocolInterface = undefined; + switch (self._locateProtocol(protocol, registration, &interface)) { + .success => return interface, + .not_found => return null, + else => |status| return status.err(), + } + } - /// Sets the system's watchdog timer. - setWatchdogTimer: *const fn (timeout: usize, watchdogCode: u64, data_size: usize, watchdog_data: ?[*]const u16) callconv(cc) Status, + /// Installs one or more protocol interfaces into the boot services environment. + pub fn installMultipleProtocolInterfaces( + self: *const BootServices, + /// The handle to install the protocol interface on. + /// + /// If null, a new handle is created and returned. + handle: ?Handle, + /// A list of protocol GUIDs and their corresponding interfaces. + comptime protocols: []const Guid, + ) !Handle { + var new_handle: ?Handle = handle; - /// Connects one or more drives to a controller. - connectController: *const fn (controller_handle: Handle, driver_image_handle: ?Handle, remaining_device_path: ?*DevicePathProtocol, recursive: bool) callconv(cc) Status, + try @call(.auto, self._installMultipleProtocolInterfaces, .{&new_handle} ++ protocols).err(); - // Disconnects one or more drivers from a controller - disconnectController: *const fn (controller_handle: Handle, driver_image_handle: ?Handle, child_handle: ?Handle) callconv(cc) Status, + return new_handle; + } - /// Queries a handle to determine if it supports a specified protocol. - openProtocol: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: *?*anyopaque, agent_handle: ?Handle, controller_handle: ?Handle, attributes: OpenProtocolAttributes) callconv(cc) Status, + /// Uninstalls one or more protocol interfaces from the boot services environment. + /// + /// This action is atomic, if it fails, all protocols will remain installed. + /// + /// Will error if any of the protocols are not installed on the handle. + pub fn uninstallMultipleProtocolInterfaces( + self: *const BootServices, + /// The handle to uninstall the protocol interface from. + handle: Handle, + /// A list of protocol GUIDs and their corresponding interfaces. + comptime protocols: []const Guid, + ) !void { + try @call(.auto, self._uninstallMultipleProtocolInterfaces, .{&handle} ++ protocols).err(); + } - /// Closes a protocol on a handle that was opened using openProtocol(). - closeProtocol: *const fn (handle: Handle, protocol: *align(8) const Guid, agentHandle: Handle, controller_handle: ?Handle) callconv(cc) Status, + /// Loads an EFI image into memory. + /// + /// One of `device_path` or `source_buffer` must be non-null. + pub fn loadImage( + self: *const BootServices, + /// If true, indicates the request originates from the boot manager, and that the boot manager is attempting to + /// load the image as a boot selection. Ignored when `source_buffer` is non-null. + boot_policy: bool, + /// The caller's image handle. + parent_image_handle: Handle, + /// The device path of the image. + device_path: ?*const DevicePathProtocol, + /// A pointer to a memory location with a copy of the image to be loaded. + source_buffer: ?[*]const u8, + /// The size of the image's data. + source_size: usize, + ) !Handle { + var image_handle: ?Handle = null; + try self._loadImage(boot_policy, parent_image_handle, device_path, source_buffer, source_size, &image_handle).err(); + return image_handle; + } - /// Retrieves the list of agents that currently have a protocol interface opened. - openProtocolInformation: *const fn (handle: Handle, protocol: *align(8) const Guid, entry_buffer: *[*]ProtocolInformationEntry, entry_count: *usize) callconv(cc) Status, + pub const ImageReturn = struct { Status, []align(2) const u8 }; - /// Retrieves the list of protocol interface GUIDs that are installed on a handle in a buffer allocated from pool. - protocolsPerHandle: *const fn (handle: Handle, protocol_buffer: *[*]*align(8) const Guid, protocol_buffer_count: *usize) callconv(cc) Status, + /// Transfers control to a loaded image's entry point. + /// + /// Returns the exit data as bytes. It will begin with a null terminated UCS2 string, optionally followed by a blob + /// of binary data. + pub fn startImage( + self: *const BootServices, + /// The image handle. + image_handle: Handle, + ) ImageReturn { + var exit_data_size: usize = 0; + var exit_data: *[*]align(2) u8 = null; + const status = self._startImage(image_handle, &exit_data_size, &exit_data); - /// Returns an array of handles that support the requested protocol in a buffer allocated from pool. - locateHandleBuffer: *const fn (search_type: LocateSearchType, protocol: ?*align(8) const Guid, search_key: ?*const anyopaque, num_handles: *usize, buffer: *[*]Handle) callconv(cc) Status, + return .{ status, exit_data[0..exit_data_size] }; + } - /// Returns the first protocol instance that matches the given protocol. - locateProtocol: *const fn (protocol: *align(8) const Guid, registration: ?*const anyopaque, interface: *?*anyopaque) callconv(cc) Status, + /// Unloads an image. + pub fn unloadImage( + self: *const BootServices, + /// The image handle. + image_handle: Handle, + ) Status { + return self._unloadImage(image_handle); + } - /// Installs one or more protocol interfaces into the boot services environment - // TODO: use callconv(cc) instead once that works - installMultipleProtocolInterfaces: *const fn (handle: *Handle, ...) callconv(.C) Status, + /// Terminates a loaded EFI image and returns control to boot services. + /// + /// This function will not return when `image_handle` is the current image handle. + pub fn exit( + self: *const BootServices, + /// The image handle. + image_handle: Handle, + /// The image's exit status. + exit_status: Status, + /// The size of the exit data. + exit_data_size: usize, + /// The exit data. This must begin with a null terminated UCS2 string. + exit_data: ?[*]align(2) const u8, + ) !void { + try self._exit(image_handle, exit_status, exit_data_size, exit_data).err(); + } - /// Removes one or more protocol interfaces into the boot services environment - // TODO: use callconv(cc) instead once that works - uninstallMultipleProtocolInterfaces: *const fn (handle: *Handle, ...) callconv(.C) Status, + /// Terminates all boot services. + pub fn exitBootServices( + self: *const BootServices, + /// The image handle. + image_handle: Handle, + /// The key returned from `getMemoryMap()`. Stored in `MemoryMap.key`. + map_key: MemoryMap.Key, + ) !void { + try self._exitBootServices(image_handle, map_key).err(); + } - /// Computes and returns a 32-bit CRC for a data buffer. - calculateCrc32: *const fn (data: [*]const u8, data_size: usize, *u32) callconv(cc) Status, + /// Sets the system's watchdog timer. + pub fn setWatchdogTimer( + self: *const BootServices, + /// The number of seconds to set the watchdog timer to. + timeout: usize, + /// The numeric code to log on timeout. + watchdog_code: u64, + /// The size of the watchdog timer's data. + watchdog_data_size: usize, + /// A data buffer pointing to a null terminated UCS2 string. Optionally followed by a blob of binary data. + watchdog_data: ?[*]const u8, + ) !void { + try self._setWatchdogTimer(timeout, watchdog_code, watchdog_data_size, watchdog_data).err(); + } - /// Copies the contents of one buffer to another buffer - copyMem: *const fn (dest: [*]u8, src: [*]const u8, len: usize) callconv(cc) void, + /// Induces a fine-grained stall. + pub fn stall( + self: *const BootServices, + /// The number of microseconds to stall for. + microseconds: usize, + ) void { + _ = self._stall(microseconds); + } - /// Fills a buffer with a specified value - setMem: *const fn (buffer: [*]u8, size: usize, value: u8) callconv(cc) void, + /// Returns the next monotonic count. + pub fn getNextMonotonicCount( + self: *const BootServices, + ) !u64 { + var count: u64 = 0; + try self._getNextMonotonicCount(&count).err(); + return count; + } - /// Creates an event in a group. - createEventEx: *const fn (type: u32, notify_tpl: usize, notify_func: EfiEventNotify, notify_ctx: *const anyopaque, event_group: *align(8) const Guid, event: *Event) callconv(cc) Status, + /// Adds, updates, or removes a configuration table from the EFI System Table. + pub fn installConfigurationTable( + self: *const BootServices, + /// The GUID of the configuration table. + guid: *align(8) const Guid, + /// A pointer to the configuration table. + table: ?*const anyopaque, + ) !void { + try self._installConfigurationTable(guid, table).err(); + } /// 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 { + pub fn openProtocolSt(self: *const BootServices, comptime protocol: type, handle: Handle) !*protocol { if (!@hasDecl(protocol, "guid")) @compileError("Protocol is missing guid!"); @@ -189,16 +786,338 @@ pub const BootServices = extern struct { } pub const signature: u64 = 0x56524553544f4f42; +}; +<<<<<<< HEAD +======= + +/// These types can be "ORed" together as needed. +pub const EventKind = struct { + /// The event is a timer event and may be used in a call to `setTimer()`. Note that timers only function during boot + /// services time. + pub const timer: u32 = 0x80000000; + + /// The event is allocated from runtime memory. If an event is to be signaled after the call to `exitBootServices()` + /// the event’s data structure and notification function need to be allocated from runtime memory. + pub const runtime: u32 = 0x40000000; + + /// If an event of this type is not already in the signaled state, then the event’s *notify_func* will be + /// queued at the event’s *notify_tpl* whenever the event is being waited on via `waitForEvent()` or `checkEvent()`. + pub const notify_wait: u32 = 0x00000100; + + /// The event’s *notify_func* is queued whenever the event is signaled. + pub const notify_signal: u32 = 0x00000200; + + /// This event is of type `notify_signal`. It should not be combined with any other event types. This event type + /// is functionally equivalent to the `EventGroup.exit_boot_services` event group. + pub const signal_exit_boot_services: u32 = 0x00000201; + + /// This event is of type `notify_signal`. It should not be combined with any other event types. The event is to be + /// notified by the system when `setVirtualAddressMap()` is performed. + pub const signal_virtual_address_change: u32 = 0x60000202; +}; + +pub const EventGroup = struct {}; + +pub const TaskPriorityLevel = enum(usize) { + /// This is the lowest priority level. It is the level of execution which occurs when no event notifications are + /// pending and which interacts with the user. User I/O (and blocking on User I/O) can be performed at this level. + /// The boot manager executes at this level and passes control to other UEFI applications at this level. + application = 4, + + /// Interrupts code executing below TPL_CALLBACK level. Long term operations (such as file system operations and + /// disk I/O) can occur at this level + callback = 8, + + /// Interrupts code executing below TPL_NOTIFY level. Blocking is not allowed at this level. Code executes to + /// completion and returns. If code requires more processing, it needs to signal an event to wait to obtain control + /// again at whatever level it requires. This level is typically used to process low level IO to or from a device. + notify = 16, + + /// Interrupts code executing below TPL_HIGH_LEVEL This is the highest priority level. It is not interruptible + /// (interrupts are disabled) and is used sparingly by firmware to synchronize operations that need to be accessible + /// from any priority level. For example, it must be possible to signal events while executing at any priority level. + /// Therefore, firmware manipulates the internal event structure while at this priority level. + high_level = 31, +}; + +pub const EventNotify = *const fn (event: Event, ctx: *anyopaque) callconv(cc) void; +pub const EventNotifyContext = *const anyopaque; + +pub const TimerKind = union(Enum) { + pub const Enum = enum(u32) { + /// The timer is canceled. + cancel = 0, + + /// The timer is a periodic timer. + periodic = 1, + + /// The timer is a relative timer. + relative = 2, + }; + + /// The timer is to be canceled. + cancel: void, + + /// The timer is to be signaled periodically at `trigger_time` ns intervals. The event timer does not need to be + /// reset for each notification. + periodic: u64, + + /// The timer is to be signaled after `trigger_time` ns. + relative: u64, +}; + +pub const MemoryMap = struct { + pub const Key = enum(usize) { _ }; + + map: *const anyopaque, + + /// The length of the memory map in bytes. + size: usize, + + /// The key for the current memory map. + key: Key, + + /// The size of each memory descriptor in the memory map. + descriptor_size: usize, + + /// The version of the memory map. + descriptor_version: u32, + + /// An iterator over the memory map. + pub const Iterator = struct { + map: *const MemoryMap, + + /// The current index of the iterator. + index: usize = 0, + + /// Returns the next memory descriptor in the map. + pub fn next(iter: *Iterator) ?*const MemoryDescriptor { + const offset = iter.index * iter.map.descriptor_size; + + // ensure the next descriptor is within the map + if (offset + iter.map.descriptor_size > iter.map.size) + return null; + + const addr = @intFromPtr(iter.map.map) + offset; + iter.index += 1; + + return @ptrFromInt(addr); + } + }; + + /// Returns an iterator over the memory map. + pub fn iterator(self: *const MemoryMap) Iterator { + return Iterator{ .map = self }; + } +}; + +pub const MemoryType = enum(u32) { + /// Not usable. + reserved, + + /// The code portions of a loaded application. + loader_code, + + /// The data portions of a loaded application and the default data allocation type used by an application to + /// allocate pool memory. + loader_data, + + /// The code portions of a loaded Boot Services Driver. + boot_services_code, + + /// The data portions of a loaded Boot Services Driver, and the default data allocation type used by a Boot + /// Services Driver to allocate pool memory. + boot_services_data, + + /// The code portions of a loaded Runtime Services Driver. + runtime_services_code, + + /// The data portions of a loaded Runtime Services Driver and the default data allocation type used by a Runtime + /// Services Driver to allocate pool memory. + runtime_services_data, + + /// Free (unallocated) memory. + conventional, + + /// Memory in which errors have been detected. + unusable, + + /// Memory that holds the ACPI tables. + acpi_reclaim, + + /// Address space reserved for use by the firmware. + acpi_nvs, + + /// Used by system firmware to request that a memory-mapped IO region be mapped by the OS to a virtual address so + /// it can be accessed by EFI runtime services. + memory_mapped_io, + + /// System memory-mapped IO region that is used to translate memory cycles to IO cycles by the processor. + memory_mapped_io_port_space, + + /// Address space reserved by the firmware for code that is part of the processor. + pal_code, + + /// A memory region that operates as `conventional`, but additionally supports byte-addressable non-volatility. + persistent, + + /// A memory region that represents unaccepted memory that must be accepted by the boot target before it can be used. + /// For platforms that support unaccepted memory, all unaccepted valid memory will be reported in the memory map. + /// Unreported memory addresses must be treated as non-present memory. + unaccepted, + + _, +}; + +pub const MemoryDescriptorAttribute = packed struct(u64) { + /// The memory region supports being configured as not cacheable. + non_cacheable: bool, + + /// The memory region supports being configured as write combining. + write_combining: bool, + + /// The memory region supports being configured as cacheable with a "write-through". Writes that hit in the cache + /// will also be written to main memory. + write_through: bool, + + /// The memory region supports being configured as cacheable with a "write-back". Reads and writes that hit in the + /// cache do not propagate to main memory. Dirty data is written back to main memory when a new cache line is + /// allocated. + write_back: bool, + + /// The memory region supports being configured as not cacheable, exported, and supports the "fetch and add" + /// semaphore mechanism. + non_cacheable_exported: bool, + + _pad1: u7 = 0, + + /// The memory region supports being configured as write-protected by system hardware. This is typically used as a + /// cacheability attribute today. The memory region supports being configured as cacheable with a "write protected" + /// policy. Reads come from cache lines when possible, and read misses cause cache fills. Writes are propagated to + /// the system bus and cause corresponding cache lines on all processors to be invalidated. + write_protect: bool, + + /// The memory region supports being configured as read-protected by system hardware. + read_protect: bool, + + /// The memory region supports being configured so it is protected by system hardware from executing code. + execute_protect: bool, + + /// The memory region refers to persistent memory. + non_volatile: bool, + + /// The memory region provides higher reliability relative to other memory in the system. If all memory has the same + /// reliability, then this bit is not used. + more_reliable: bool, + + /// The memory region supports making this memory range read-only by system hardware. + read_only: bool, + + /// The memory region is earmarked for specific purposes such as for specific device drivers or applications. This + /// attribute serves as a hint to the OS to avoid allocation this memory for core OS data or code that cannot be + /// relocated. Prolonged use of this memory for purposes other than the intended purpose may result in suboptimal + /// platform performance. + specific_purpose: bool, + + /// The memory region is capable of being protected with the CPU's memory cryptographic capabilities. + cpu_crypto: bool, + + _pad2: u24 = 0, + + /// When `memory_isa_valid` is set, this field contains ISA specific cacheability attributes not covered above. + memory_isa: u16, + + _pad3: u2 = 0, + + /// If set, then `memory_isa` is valid. + memory_isa_valid: bool, + + /// This memory must be given a virtual mapping by the operating system when `setVirtualAddressMap()` is called. + memory_runtime: bool, +}; + +pub const MemoryDescriptor = extern struct { + type: MemoryType, + physical_start: PhysicalAddress, + virtual_start: VirtualAddress, + number_of_pages: u64, + attribute: MemoryDescriptorAttribute, +}; + +pub const LocateSearchType = union(Enum) { + pub const Enum = enum(u32) { + all, + by_notify, + by_protocol, + }; + + all: void, + by_notify: *const anyopaque, + by_protocol: *align(8) const Guid, +}; + +pub const OpenProtocolAttributes = packed struct(u32) { + /// Query whether a protocol interface is supported on a handle. If yes, then the protocol interface is returned. + by_handle_protocol: bool = false, + + /// Used by a driver to get a protocol interface from a handle. Care must be taken when using this mode because the + /// driver that opens the protocol interface in this manner will not be informed if the protocol interface is + /// uninstalled or reinstalled. The caller is also not required to close the protocol interface. + get_protocol: bool = false, + + /// Used by a driver to test for the existence of a protocol interface on a handle. The returned interface will always + /// be `null`. This mode can be used to determine if a driver is present in the handle's driver stack. + test_protocol: bool = false, + + /// Used by bus drivers to show that a protocol interface is being used by one of the child controllers of the bus. + /// This information is used by `connectController` to recursively connect all child controllers and by + /// `disconnectController` to get the list of child controllers that a bus driver created. + by_child_controller: bool = false, + + /// Used by a driver to gain access to a protocol interface. When this mode is used, the driver's `stop()` function + /// will be called by `disconnectController` if the protocol interface is reinstalled or uninstalled. Once a + /// protocol interface is opened by a driver with this attribute, no other drivers can open that protocol interface + /// with this attribute. + by_driver: bool = false, + + /// Used to gain exclusive access to a protocol interface. If any drivers have the protocol interface opened with + /// the `by_driver` attribute, then an attempt will be made to remove them by calling the driver's `stop()` function. + exclusive: bool = false, + + reserved: u26 = 0, +}; + +pub const ProtocolInformationEntry = extern struct { + agent_handle: ?Handle, + controller_handle: ?Handle, + attributes: OpenProtocolAttributes, + open_count: u32, +}; + +pub const EfiInterfaceType = enum(u32) { + native, +}; + +pub const AllocateType = union(Enum) { + pub const Enum = enum(u32) { + /// Allocate any available range of pages that satisfies the request. + any = 0, + + /// Allocate any available range of pages whose uppermost address is less than or equal to a specified + /// address. + max_address = 1, + + /// Allocate pages at a specified address. + at_address = 2, + }; + + /// Allocate any available range of pages that satisfies the request. + any: void, + + /// Allocate any available range of pages whose uppermost address is less than or equal to a specified address. + max_address: PhysicalAddress, - pub const event_timer: u32 = 0x80000000; - pub const event_runtime: u32 = 0x40000000; - pub const event_notify_wait: u32 = 0x00000100; - pub const event_notify_signal: u32 = 0x00000200; - pub const event_signal_exit_boot_services: u32 = 0x00000201; - pub const event_signal_virtual_address_change: u32 = 0x00000202; - - pub const tpl_application: usize = 4; - pub const tpl_callback: usize = 8; - pub const tpl_notify: usize = 16; - pub const tpl_high_level: usize = 31; + /// Allocate pages at a specified address. + at_address: PhysicalAddress, }; +>>>>>>> 908d1f3d3f (std.os.uefi: add zig-like bindings for boot services) diff --git a/lib/std/os/uefi/tables/header.zig b/lib/std/os/uefi/tables/header.zig new file mode 100644 index 000000000000..018fa11759ce --- /dev/null +++ b/lib/std/os/uefi/tables/header.zig @@ -0,0 +1,28 @@ +const uefi = @import("../../uefi.zig"); + +pub const TableHeader = extern struct { + signature: u64, + revision: u32, + + /// The size, in bytes, of the entire table including the TableHeader + header_size: u32, + crc32: u32, + reserved: u32, + + pub fn validate(self: *const TableHeader, signature: u64) bool { + if (self.reserved != 0) return false; + if (self.signature != signature) return false; + + const byte_ptr: [*]const u8 = @ptrCast(self); + + var crc = uefi.Crc32.init(); + crc.update(byte_ptr[0..16]); + crc.update(&.{ 0, 0, 0, 0 }); // crc32 field replaced with 0 + crc.update(byte_ptr[20..self.header_size]); + return crc.finish() == self.crc32; + } + + pub fn isAtLeastRevision(self: *const TableHeader, major: u16, minor: u16) bool { + return self.revision >= (@as(u32, major) << 16) | minor; + } +}; diff --git a/lib/std/os/uefi/tables/table_header.zig b/lib/std/os/uefi/tables/table_header.zig deleted file mode 100644 index d5d40942327c..000000000000 --- a/lib/std/os/uefi/tables/table_header.zig +++ /dev/null @@ -1,9 +0,0 @@ -pub const TableHeader = extern struct { - signature: u64, - revision: u32, - - /// The size, in bytes, of the entire table including the TableHeader - header_size: u32, - crc32: u32, - reserved: u32, -}; From 755af01b70817308f91e9ac7c3935f5db3d09d12 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 20 Dec 2023 15:19:01 -0600 Subject: [PATCH 03/23] std.os.uefi: bind the rest of the system table functions, clean up imports and move to bits.zig --- lib/std/os/uefi.zig | 219 +--- lib/std/os/uefi/bits.zig | 394 ++++++ lib/std/os/uefi/device_path.zig | 1138 ++++++++++------- lib/std/os/uefi/hii.zig | 5 +- lib/std/os/uefi/pool_allocator.zig | 2 +- lib/std/os/uefi/table.zig | 13 + .../uefi/{tables => table}/boot_services.zig | 824 ++++++------ lib/std/os/uefi/table/configuration.zig | 199 +++ lib/std/os/uefi/{tables => table}/header.zig | 16 +- lib/std/os/uefi/table/runtime_services.zig | 412 ++++++ lib/std/os/uefi/table/system.zig | 65 + lib/std/os/uefi/tables.zig | 137 -- .../os/uefi/tables/configuration_table.zig | 80 -- lib/std/os/uefi/tables/runtime_services.zig | 72 -- lib/std/os/uefi/tables/system_table.zig | 48 - 15 files changed, 2165 insertions(+), 1459 deletions(-) create mode 100644 lib/std/os/uefi/bits.zig create mode 100644 lib/std/os/uefi/table.zig rename lib/std/os/uefi/{tables => table}/boot_services.zig (63%) create mode 100644 lib/std/os/uefi/table/configuration.zig rename lib/std/os/uefi/{tables => table}/header.zig (52%) create mode 100644 lib/std/os/uefi/table/runtime_services.zig create mode 100644 lib/std/os/uefi/table/system.zig delete mode 100644 lib/std/os/uefi/tables.zig delete mode 100644 lib/std/os/uefi/tables/configuration_table.zig delete mode 100644 lib/std/os/uefi/tables/runtime_services.zig delete mode 100644 lib/std/os/uefi/tables/system_table.zig diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index eaca768ebb90..5eeba1cdbd0d 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -4,6 +4,7 @@ const std = @import("../std.zig"); pub const protocol = @import("uefi/protocol.zig"); pub const DevicePath = @import("uefi/device_path.zig").DevicePath; pub const hii = @import("uefi/hii.zig"); +pub const bits = @import("uefi/bits.zig"); /// Status codes returned by EFI interfaces pub const Status = @import("uefi/status.zig").Status; @@ -17,227 +18,11 @@ pub const pool_allocator = @import("uefi/pool_allocator.zig").pool_allocator; pub const raw_pool_allocator = @import("uefi/pool_allocator.zig").raw_pool_allocator; /// The EFI image's handle that is passed to its entry point. -pub var handle: Handle = undefined; +pub var handle: bits.Handle = undefined; /// A pointer to the EFI System Table that is passed to the EFI image's entry point. pub var system_table: *tables.SystemTable = undefined; -/// A handle to an event structure. -pub const Event = *opaque {}; - -/// The calling convention used for all external functions part of the UEFI API. -pub const cc = switch (@import("builtin").target.cpu.arch) { - .x86_64 => .Win64, - else => .C, -}; - -pub const Crc32 = std.hash.crc.Crc32IsoHdlc; - -pub const MacAddress = extern struct { - address: [32]u8, -}; - -pub const Ipv4Address = extern struct { - address: [4]u8, -}; - -pub const Ipv6Address = extern struct { - address: [16]u8, -}; - -/// GUIDs are align(8) unless otherwise specified. -pub const Guid = extern struct { - time_low: u32, - time_mid: u16, - time_high_and_version: u16, - clock_seq_high_and_reserved: u8, - clock_seq_low: u8, - node: [6]u8, - - /// Format GUID into hexadecimal lowercase xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format - pub fn format( - self: @This(), - comptime f: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) !void { - _ = options; - if (f.len == 0) { - const fmt = std.fmt.fmtSliceHexLower; - - const time_low = @byteSwap(self.time_low); - const time_mid = @byteSwap(self.time_mid); - const time_high_and_version = @byteSwap(self.time_high_and_version); - - return std.fmt.format(writer, "{:0>8}-{:0>4}-{:0>4}-{:0>2}{:0>2}-{:0>12}", .{ - fmt(std.mem.asBytes(&time_low)), - fmt(std.mem.asBytes(&time_mid)), - fmt(std.mem.asBytes(&time_high_and_version)), - fmt(std.mem.asBytes(&self.clock_seq_high_and_reserved)), - fmt(std.mem.asBytes(&self.clock_seq_low)), - fmt(std.mem.asBytes(&self.node)), - }); - } else { - std.fmt.invalidFmtError(f, self); - } - } - - pub fn eql(a: std.os.uefi.Guid, b: std.os.uefi.Guid) bool { - return a.time_low == b.time_low and - a.time_mid == b.time_mid and - a.time_high_and_version == b.time_high_and_version and - a.clock_seq_high_and_reserved == b.clock_seq_high_and_reserved and - a.clock_seq_low == b.clock_seq_low and - std.mem.eql(u8, &a.node, &b.node); - } -}; - -/// An EFI Handle represents a collection of related interfaces. -pub const Handle = *opaque {}; - -/// This structure represents time information. -pub const Time = extern struct { - /// 1900 - 9999 - year: u16, - - /// 1 - 12 - month: u8, - - /// 1 - 31 - day: u8, - - /// 0 - 23 - hour: u8, - - /// 0 - 59 - minute: u8, - - /// 0 - 59 - second: u8, - - /// 0 - 999999999 - nanosecond: u32, - - /// The time's offset in minutes from UTC. - /// Allowed values are -1440 to 1440 or unspecified_timezone - timezone: i16, - daylight: packed struct { - _pad1: u6, - - /// If true, the time has been adjusted for daylight savings time. - in_daylight: bool, - - /// If true, the time is affected by daylight savings time. - adjust_daylight: bool, - }, - - /// Time is to be interpreted as local time - pub const unspecified_timezone: i16 = 0x7ff; - - fn daysInYear(year: u16, maxMonth: u4) u32 { - const leapYear: std.time.epoch.YearLeapKind = if (std.time.epoch.isLeapYear(year)) .leap else .not_leap; - var days: u32 = 0; - var month: u4 = 0; - while (month < maxMonth) : (month += 1) { - days += std.time.epoch.getDaysInMonth(leapYear, @enumFromInt(month + 1)); - } - return days; - } - - pub fn toEpoch(self: std.os.uefi.Time) u64 { - var year: u16 = 0; - var days: u32 = 0; - - while (year < (self.year - 1971)) : (year += 1) { - days += daysInYear(year + 1970, 12); - } - - days += daysInYear(self.year, @as(u4, @intCast(self.month)) - 1) + self.day; - const hours = self.hour + (days * 24); - const minutes = self.minute + (hours * 60); - const seconds = self.second + (minutes * std.time.s_per_min); - return self.nanosecond + (seconds * std.time.ns_per_s); - } -}; - -/// Capabilities of the clock device -pub const TimeCapabilities = extern struct { - /// Resolution in Hz - resolution: u32, - - /// Accuracy in an error rate of 1e-6 parts per million. - accuracy: u32, - - /// If true, a time set operation clears the device's time below the resolution level. - sets_to_zero: bool, -}; - -/// File Handle as specified in the EFI Shell Spec -pub const FileHandle = *opaque {}; - -test "GUID formatting" { - const bytes = [_]u8{ 137, 60, 203, 50, 128, 128, 124, 66, 186, 19, 80, 73, 135, 59, 194, 135 }; - const guid: Guid = @bitCast(bytes); - - const str = try std.fmt.allocPrint(std.testing.allocator, "{}", .{guid}); - defer std.testing.allocator.free(str); - - try std.testing.expect(std.mem.eql(u8, str, "32cb3c89-8080-427c-ba13-5049873bc287")); -} - -pub const FileInfo = extern struct { - size: u64, - file_size: u64, - physical_size: u64, - create_time: Time, - last_access_time: Time, - modification_time: Time, - attribute: u64, - - pub fn getFileName(self: *const FileInfo) [*:0]const u16 { - return @ptrCast(@alignCast(@as([*]const u8, @ptrCast(self)) + @sizeOf(FileInfo))); - } - - pub const efi_file_read_only: u64 = 0x0000000000000001; - pub const efi_file_hidden: u64 = 0x0000000000000002; - pub const efi_file_system: u64 = 0x0000000000000004; - pub const efi_file_reserved: u64 = 0x0000000000000008; - pub const efi_file_directory: u64 = 0x0000000000000010; - pub const efi_file_archive: u64 = 0x0000000000000020; - pub const efi_file_valid_attr: u64 = 0x0000000000000037; - - pub const guid align(8) = Guid{ - .time_low = 0x09576e92, - .time_mid = 0x6d3f, - .time_high_and_version = 0x11d2, - .clock_seq_high_and_reserved = 0x8e, - .clock_seq_low = 0x39, - .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, - }; -}; - -pub const FileSystemInfo = extern struct { - size: u64, - read_only: bool, - volume_size: u64, - free_space: u64, - block_size: u32, - _volume_label: u16, - - pub fn getVolumeLabel(self: *const FileSystemInfo) [*:0]const u16 { - return @as([*:0]const u16, @ptrCast(&self._volume_label)); - } - - pub const guid align(8) = Guid{ - .time_low = 0x09576e93, - .time_mid = 0x6d3f, - .time_high_and_version = 0x11d2, - .clock_seq_high_and_reserved = 0x8e, - .clock_seq_low = 0x39, - .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, - }; -}; - test { _ = tables; _ = protocol; diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig new file mode 100644 index 000000000000..5cc136cc83ae --- /dev/null +++ b/lib/std/os/uefi/bits.zig @@ -0,0 +1,394 @@ +const std = @import("../../std.zig"); + +const time = std.time; + +/// The calling convention used for all external functions part of the UEFI API. +pub const cc = switch (@import("builtin").target.cpu.arch) { + .x86_64 => .Win64, + else => .C, +}; + +/// A pointer in physical address space. +pub const PhysicalAddress = u64; + +/// A pointer in virtual address space. +pub const VirtualAddress = u64; + +/// An EFI Handle represents a collection of related interfaces. +pub const Handle = *opaque {}; + +/// A handle to an event structure. +pub const Event = *opaque {}; + +/// File Handle as specified in the EFI Shell Spec +pub const FileHandle = *opaque {}; + +pub const Crc32 = std.hash.crc.Crc32IsoHdlc; + +pub const MacAddress = extern struct { + address: [32]u8, +}; + +pub const Ipv4Address = extern struct { + address: [4]u8, +}; + +pub const Ipv6Address = extern struct { + address: [16]u8, +}; + +/// This structure represents time information. +pub const Time = extern struct { + /// 1900 - 9999 + year: u16, + + /// 1 - 12 + month: u8, + + /// 1 - 31 + day: u8, + + /// 0 - 23 + hour: u8, + + /// 0 - 59 + minute: u8, + + /// 0 - 59 + second: u8, + + /// 0 - 999999999 + nanosecond: u32, + + /// The time's offset in minutes from UTC. + /// Allowed values are -1440 to 1440 or unspecified_timezone + timezone: i16, + daylight: packed struct { + _pad1: u6, + + /// If true, the time has been adjusted for daylight savings time. + in_daylight: bool, + + /// If true, the time is affected by daylight savings time. + adjust_daylight: bool, + }, + + /// Time is to be interpreted as local time + pub const unspecified_timezone: i16 = 0x7ff; + + /// Returns the time in seconds since 1900-01-01 00:00:00. + pub fn toEpochSeconds(self: Time) u64 { + var year: u16 = 1900; + var days: u32 = 0; + + while (year < self.year) : (year += 1) { + days += time.epoch.getDaysInYear(year); + } + + var month: u8 = 1; + while (month < self.month) : (month += 1) { + const leap_kind: time.epoch.YearLeapKind = if (time.epoch.isLeapYear(self.year)) .leap else .not_leap; + + days += time.epoch.getDaysInMonth(leap_kind, @enumFromInt(month)); + } + + days += self.day - 1; + + return days * time.s_per_day + + @as(u32, self.hour) * time.s_per_hour + + @as(u16, self.minute) * time.s_per_min + + self.second; + } + + /// Returns the time in nanoseconds since 1900-01-01 00:00:00. + pub fn toEpochNanoseconds(self: Time) u128 { + return @as(u128, self.toEpochSeconds()) * time.ns_per_s + self.nanosecond; + } + + /// Returns the time in nanoseconds since 1970-01-01 00:00:00, or null if the time does not fit. + pub fn toUnixEpochNanoseconds(self: Time) ?u64 { + const nanoseconds = self.toEpochNanoseconds(); + + if (nanoseconds < time.epoch.unix_epoch_nanoseconds) { + return null; + } + + const unix = nanoseconds - time.epoch.unix_epoch_nanoseconds; + if (unix > std.math.maxInt(u64)) { + return null; + } + + return @intCast(unix); + } + + pub const unix_epoch = Time{ + .year = 1970, + .month = 1, + .day = 1, + .hour = 0, + .minute = 0, + .second = 0, + .nanosecond = 0, + .timezone = 0, + .daylight = .{ .in_daylight = false, .adjust_daylight = false }, + }; + + pub const unix_epoch_nanoseconds = unix_epoch.toEpochNanoseconds(); +}; + +/// Capabilities of the clock device +pub const TimeCapabilities = extern struct { + /// Resolution in Hz + resolution: u32, + + /// Accuracy in an error rate of 1e-6 parts per million. + accuracy: u32, + + /// If true, a time set operation clears the device's time below the resolution level. + sets_to_zero: bool, +}; + +pub const MemoryDescriptor = extern struct { + pub const Type = enum(u32) { + /// Not usable. + reserved, + + /// The code portions of a loaded application. + loader_code, + + /// The data portions of a loaded application and the default data allocation type used by an application to + /// allocate pool memory. + loader_data, + + /// The code portions of a loaded Boot Services Driver. + boot_services_code, + + /// The data portions of a loaded Boot Services Driver, and the default data allocation type used by a Boot + /// Services Driver to allocate pool memory. + boot_services_data, + + /// The code portions of a loaded Runtime Services Driver. + runtime_services_code, + + /// The data portions of a loaded Runtime Services Driver and the default data allocation type used by a Runtime + /// Services Driver to allocate pool memory. + runtime_services_data, + + /// Free (unallocated) memory. + conventional, + + /// Memory in which errors have been detected. + unusable, + + /// Memory that holds the ACPI tables. + acpi_reclaim, + + /// Address space reserved for use by the firmware. + acpi_nvs, + + /// Used by system firmware to request that a memory-mapped IO region be mapped by the OS to a virtual address so + /// it can be accessed by EFI runtime services. + memory_mapped_io, + + /// System memory-mapped IO region that is used to translate memory cycles to IO cycles by the processor. + memory_mapped_io_port_space, + + /// Address space reserved by the firmware for code that is part of the processor. + pal_code, + + /// A memory region that operates as `conventional`, but additionally supports byte-addressable non-volatility. + persistent, + + /// A memory region that represents unaccepted memory that must be accepted by the boot target before it can be used. + /// For platforms that support unaccepted memory, all unaccepted valid memory will be reported in the memory map. + /// Unreported memory addresses must be treated as non-present memory. + unaccepted, + + _, + }; + + pub const Attribute = packed struct(u64) { + /// The memory region supports being configured as not cacheable. + non_cacheable: bool, + + /// The memory region supports being configured as write combining. + write_combining: bool, + + /// The memory region supports being configured as cacheable with a "write-through". Writes that hit in the cache + /// will also be written to main memory. + write_through: bool, + + /// The memory region supports being configured as cacheable with a "write-back". Reads and writes that hit in the + /// cache do not propagate to main memory. Dirty data is written back to main memory when a new cache line is + /// allocated. + write_back: bool, + + /// The memory region supports being configured as not cacheable, exported, and supports the "fetch and add" + /// semaphore mechanism. + non_cacheable_exported: bool, + + _pad1: u7 = 0, + + /// The memory region supports being configured as write-protected by system hardware. This is typically used as a + /// cacheability attribute today. The memory region supports being configured as cacheable with a "write protected" + /// policy. Reads come from cache lines when possible, and read misses cause cache fills. Writes are propagated to + /// the system bus and cause corresponding cache lines on all processors to be invalidated. + write_protect: bool, + + /// The memory region supports being configured as read-protected by system hardware. + read_protect: bool, + + /// The memory region supports being configured so it is protected by system hardware from executing code. + execute_protect: bool, + + /// The memory region refers to persistent memory. + non_volatile: bool, + + /// The memory region provides higher reliability relative to other memory in the system. If all memory has the same + /// reliability, then this bit is not used. + more_reliable: bool, + + /// The memory region supports making this memory range read-only by system hardware. + read_only: bool, + + /// The memory region is earmarked for specific purposes such as for specific device drivers or applications. This + /// attribute serves as a hint to the OS to avoid allocation this memory for core OS data or code that cannot be + /// relocated. Prolonged use of this memory for purposes other than the intended purpose may result in suboptimal + /// platform performance. + specific_purpose: bool, + + /// The memory region is capable of being protected with the CPU's memory cryptographic capabilities. + cpu_crypto: bool, + + _pad2: u24 = 0, + + /// When `memory_isa_valid` is set, this field contains ISA specific cacheability attributes not covered above. + memory_isa: u16, + + _pad3: u2 = 0, + + /// If set, then `memory_isa` is valid. + memory_isa_valid: bool, + + /// This memory must be given a virtual mapping by the operating system when `setVirtualAddressMap()` is called. + memory_runtime: bool, + }; + + pub const revision: u32 = 1; + + type: Type, + physical_start: PhysicalAddress, + virtual_start: VirtualAddress, + number_of_pages: u64, + attribute: Attribute, +}; + +/// GUIDs are align(8) unless otherwise specified. +pub const Guid = extern struct { + time_low: u32, + time_mid: u16, + time_high_and_version: u16, + clock_seq_high_and_reserved: u8, + clock_seq_low: u8, + node: [6]u8, + + /// Format GUID into hexadecimal lowercase xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format + pub fn format( + self: @This(), + comptime f: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = options; + if (f.len == 0) { + const fmt = std.fmt.fmtSliceHexLower; + + const time_low = @byteSwap(self.time_low); + const time_mid = @byteSwap(self.time_mid); + const time_high_and_version = @byteSwap(self.time_high_and_version); + + return std.fmt.format(writer, "{:0>8}-{:0>4}-{:0>4}-{:0>2}{:0>2}-{:0>12}", .{ + fmt(std.mem.asBytes(&time_low)), + fmt(std.mem.asBytes(&time_mid)), + fmt(std.mem.asBytes(&time_high_and_version)), + fmt(std.mem.asBytes(&self.clock_seq_high_and_reserved)), + fmt(std.mem.asBytes(&self.clock_seq_low)), + fmt(std.mem.asBytes(&self.node)), + }); + } else { + std.fmt.invalidFmtError(f, self); + } + } + + pub fn eql(a: Guid, b: Guid) bool { + return a.time_low == b.time_low and + a.time_mid == b.time_mid and + a.time_high_and_version == b.time_high_and_version and + a.clock_seq_high_and_reserved == b.clock_seq_high_and_reserved and + a.clock_seq_low == b.clock_seq_low and + std.mem.eql(u8, &a.node, &b.node); + } + + test format { + const bytes = [_]u8{ 137, 60, 203, 50, 128, 128, 124, 66, 186, 19, 80, 73, 135, 59, 194, 135 }; + const guid: Guid = @bitCast(bytes); + + const str = try std.fmt.allocPrint(std.testing.allocator, "{}", .{guid}); + defer std.testing.allocator.free(str); + + try std.testing.expect(std.mem.eql(u8, str, "32cb3c89-8080-427c-ba13-5049873bc287")); + } +}; + +pub const FileInfo = extern struct { + size: u64, + file_size: u64, + physical_size: u64, + create_time: Time, + last_access_time: Time, + modification_time: Time, + attribute: u64, + + pub fn getFileName(self: *const FileInfo) [*:0]const u16 { + return @ptrCast(@alignCast(@as([*]const u8, @ptrCast(self)) + @sizeOf(FileInfo))); + } + + pub const efi_file_read_only: u64 = 0x0000000000000001; + pub const efi_file_hidden: u64 = 0x0000000000000002; + pub const efi_file_system: u64 = 0x0000000000000004; + pub const efi_file_reserved: u64 = 0x0000000000000008; + pub const efi_file_directory: u64 = 0x0000000000000010; + pub const efi_file_archive: u64 = 0x0000000000000020; + pub const efi_file_valid_attr: u64 = 0x0000000000000037; + + pub const guid align(8) = Guid{ + .time_low = 0x09576e92, + .time_mid = 0x6d3f, + .time_high_and_version = 0x11d2, + .clock_seq_high_and_reserved = 0x8e, + .clock_seq_low = 0x39, + .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, + }; +}; + +pub const FileSystemInfo = extern struct { + size: u64, + read_only: bool, + volume_size: u64, + free_space: u64, + block_size: u32, + _volume_label: u16, + + pub fn getVolumeLabel(self: *const FileSystemInfo) [*:0]const u16 { + return @as([*:0]const u16, @ptrCast(&self._volume_label)); + } + + pub const guid align(8) = Guid{ + .time_low = 0x09576e93, + .time_mid = 0x6d3f, + .time_high_and_version = 0x11d2, + .clock_seq_high_and_reserved = 0x8e, + .clock_seq_low = 0x39, + .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, + }; +}; diff --git a/lib/std/os/uefi/device_path.zig b/lib/std/os/uefi/device_path.zig index 55a3763d66c1..15507d4418aa 100644 --- a/lib/std/os/uefi/device_path.zig +++ b/lib/std/os/uefi/device_path.zig @@ -1,605 +1,805 @@ const std = @import("../../std.zig"); +const bits = @import("bits.zig"); + +const Guid = bits.Guid; + const assert = std.debug.assert; -const uefi = std.os.uefi; -const Guid = uefi.Guid; pub const DevicePath = union(Type) { - Hardware: Hardware, - Acpi: Acpi, - Messaging: Messaging, - Media: Media, - BiosBootSpecification: BiosBootSpecification, - End: End, + hardware: Hardware, + acpi: Acpi, + messaging: Messaging, + media: Media, + bios_boot_specification: BiosBootSpecification, + end: End, pub const Type = enum(u8) { - Hardware = 0x01, - Acpi = 0x02, - Messaging = 0x03, - Media = 0x04, - BiosBootSpecification = 0x05, - End = 0x7f, + hardware = 0x01, + acpi = 0x02, + messaging = 0x03, + media = 0x04, + bios_boot_specification = 0x05, + end = 0x7f, _, }; pub const Hardware = union(Subtype) { - Pci: *const PciDevicePath, - PcCard: *const PcCardDevicePath, - MemoryMapped: *const MemoryMappedDevicePath, - Vendor: *const VendorDevicePath, - Controller: *const ControllerDevicePath, - Bmc: *const BmcDevicePath, + pci: *const Pci, + pc_card: *const PcCard, + memory_mapped: *const MemoryMapped, + vendor: *const Vendor, + controller: *const Controller, + bmc: *const Bmc, pub const Subtype = enum(u8) { - Pci = 1, - PcCard = 2, - MemoryMapped = 3, - Vendor = 4, - Controller = 5, - Bmc = 6, + pci = 1, + pc_card = 2, + memory_mapped = 3, + vendor = 4, + controller = 5, + bmc = 6, _, }; - pub const PciDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// The Device Path for PCI defines the path to the PCI configuration space address for a PCI device. There is + /// one PCI Device Path entry for each device and function number that defines the path from the root PCI bus to + /// the device. Because the PCI bus number of a device may potentially change, a flat encoding of single PCI + /// Device Path entry cannot be used. + pub const Pci = extern struct { + type: Type = .hardware, + subtype: Subtype = .pci, + length: u16 align(1) = 6, + + /// PCI function number function: u8, + + /// PCI device number device: u8, + + comptime { + assert(6 == @sizeOf(Pci)); + assert(1 == @alignOf(Pci)); + + assert(0 == @offsetOf(Pci, "type")); + assert(1 == @offsetOf(Pci, "subtype")); + assert(2 == @offsetOf(Pci, "length")); + assert(4 == @offsetOf(Pci, "function")); + assert(5 == @offsetOf(Pci, "device")); + } }; - comptime { - assert(6 == @sizeOf(PciDevicePath)); - assert(1 == @alignOf(PciDevicePath)); - - assert(0 == @offsetOf(PciDevicePath, "type")); - assert(1 == @offsetOf(PciDevicePath, "subtype")); - assert(2 == @offsetOf(PciDevicePath, "length")); - assert(4 == @offsetOf(PciDevicePath, "function")); - assert(5 == @offsetOf(PciDevicePath, "device")); - } + pub const PcCard = extern struct { + type: Type = .hardware, + subtype: Subtype = .pc_card, + length: u16 align(1) = 5, - pub const PcCardDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// Function number (0 = first function) function_number: u8, + + comptime { + assert(5 == @sizeOf(PcCard)); + assert(1 == @alignOf(PcCard)); + + assert(0 == @offsetOf(PcCard, "type")); + assert(1 == @offsetOf(PcCard, "subtype")); + assert(2 == @offsetOf(PcCard, "length")); + assert(4 == @offsetOf(PcCard, "function_number")); + } }; - comptime { - assert(5 == @sizeOf(PcCardDevicePath)); - assert(1 == @alignOf(PcCardDevicePath)); + pub const MemoryMapped = extern struct { + type: Type = .hardware, + subtype: Subtype = .memory_mapped, + length: u16 align(1) = 24, - assert(0 == @offsetOf(PcCardDevicePath, "type")); - assert(1 == @offsetOf(PcCardDevicePath, "subtype")); - assert(2 == @offsetOf(PcCardDevicePath, "length")); - assert(4 == @offsetOf(PcCardDevicePath, "function_number")); - } + /// Memory type + memory_type: bits.MemoryDescriptor.Type align(1), - pub const MemoryMappedDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - memory_type: u32 align(1), + /// Starting memory address start_address: u64 align(1), + + /// Ending memory address end_address: u64 align(1), + + comptime { + assert(24 == @sizeOf(MemoryMapped)); + assert(1 == @alignOf(MemoryMapped)); + + assert(0 == @offsetOf(MemoryMapped, "type")); + assert(1 == @offsetOf(MemoryMapped, "subtype")); + assert(2 == @offsetOf(MemoryMapped, "length")); + assert(4 == @offsetOf(MemoryMapped, "memory_type")); + assert(8 == @offsetOf(MemoryMapped, "start_address")); + assert(16 == @offsetOf(MemoryMapped, "end_address")); + } }; - comptime { - assert(24 == @sizeOf(MemoryMappedDevicePath)); - assert(1 == @alignOf(MemoryMappedDevicePath)); - - assert(0 == @offsetOf(MemoryMappedDevicePath, "type")); - assert(1 == @offsetOf(MemoryMappedDevicePath, "subtype")); - assert(2 == @offsetOf(MemoryMappedDevicePath, "length")); - assert(4 == @offsetOf(MemoryMappedDevicePath, "memory_type")); - assert(8 == @offsetOf(MemoryMappedDevicePath, "start_address")); - assert(16 == @offsetOf(MemoryMappedDevicePath, "end_address")); - } + /// The Vendor Device Path allows the creation of vendor-defined Device Paths. A vendor must allocate a Vendor + /// GUID for a Device Path. The Vendor GUID can then be used to define the contents on the data bytes that + /// follow in the Vendor Device Path node. + pub const Vendor = extern struct { + type: Type = .hardware, + subtype: Subtype = .vendor, + length: u16 align(1), // 20 + x - pub const VendorDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// Vendor GUID that defines the data that follows. vendor_guid: Guid align(1), - }; - comptime { - assert(20 == @sizeOf(VendorDevicePath)); - assert(1 == @alignOf(VendorDevicePath)); + /// Vendor-specific data + pub fn data(self: *const Vendor) []const u8 { + const ptr = @as([*:0]const u8, @ptrCast(self)); - assert(0 == @offsetOf(VendorDevicePath, "type")); - assert(1 == @offsetOf(VendorDevicePath, "subtype")); - assert(2 == @offsetOf(VendorDevicePath, "length")); - assert(4 == @offsetOf(VendorDevicePath, "vendor_guid")); - } + return ptr[@sizeOf(Vendor)..self.length]; + } - pub const ControllerDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + comptime { + assert(20 == @sizeOf(Vendor)); + assert(1 == @alignOf(Vendor)); + + assert(0 == @offsetOf(Vendor, "type")); + assert(1 == @offsetOf(Vendor, "subtype")); + assert(2 == @offsetOf(Vendor, "length")); + assert(4 == @offsetOf(Vendor, "vendor_guid")); + } + }; + + pub const Controller = extern struct { + type: Type = .hardware, + subtype: Subtype = .controller, + length: u16 align(1) = 8, + + /// Controller number controller_number: u32 align(1), + + comptime { + assert(8 == @sizeOf(Controller)); + assert(1 == @alignOf(Controller)); + + assert(0 == @offsetOf(Controller, "type")); + assert(1 == @offsetOf(Controller, "subtype")); + assert(2 == @offsetOf(Controller, "length")); + assert(4 == @offsetOf(Controller, "controller_number")); + } }; - comptime { - assert(8 == @sizeOf(ControllerDevicePath)); - assert(1 == @alignOf(ControllerDevicePath)); + /// The Device Path for a Baseboard Management Controller (BMC) host interface. + pub const Bmc = extern struct { + pub const InterfaceType = enum(u8) { + unknown = 0, - assert(0 == @offsetOf(ControllerDevicePath, "type")); - assert(1 == @offsetOf(ControllerDevicePath, "subtype")); - assert(2 == @offsetOf(ControllerDevicePath, "length")); - assert(4 == @offsetOf(ControllerDevicePath, "controller_number")); - } + /// Keyboard controller style + kcs = 1, - pub const BmcDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - interface_type: u8, + /// Server management interface chip + smic = 2, + + /// Block transfer + bt = 3, + + _, + }; + + type: Type = .hardware, + subtype: Subtype = .bmc, + length: u16 align(1) = 13, + + /// The Baseboard Management Controller (BMC) host interface type + interface_type: InterfaceType, + + /// Base address (either memory-mapped or I/O) of the BMC. If the least significant bit is a 1, the address + /// is in I/O space; otherwise, the address is memory-mapped. Refer to the IPMI specification for details. base_address: u64 align(1), - }; - comptime { - assert(13 == @sizeOf(BmcDevicePath)); - assert(1 == @alignOf(BmcDevicePath)); - - assert(0 == @offsetOf(BmcDevicePath, "type")); - assert(1 == @offsetOf(BmcDevicePath, "subtype")); - assert(2 == @offsetOf(BmcDevicePath, "length")); - assert(4 == @offsetOf(BmcDevicePath, "interface_type")); - assert(5 == @offsetOf(BmcDevicePath, "base_address")); - } + comptime { + assert(13 == @sizeOf(Bmc)); + assert(1 == @alignOf(Bmc)); + + assert(0 == @offsetOf(Bmc, "type")); + assert(1 == @offsetOf(Bmc, "subtype")); + assert(2 == @offsetOf(Bmc, "length")); + assert(4 == @offsetOf(Bmc, "interface_type")); + assert(5 == @offsetOf(Bmc, "base_address")); + } + }; }; pub const Acpi = union(Subtype) { - Acpi: *const BaseAcpiDevicePath, - ExpandedAcpi: *const ExpandedAcpiDevicePath, - Adr: *const AdrDevicePath, + base: *const BaseAcpi, + expanded: *const ExpandedAcpi, + adr: *const Adr, + nvdimm: *const Nvdimm, pub const Subtype = enum(u8) { - Acpi = 1, - ExpandedAcpi = 2, - Adr = 3, + base = 1, + expanded = 2, + adr = 3, + nvdimm = 4, _, }; - pub const BaseAcpiDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + pub const BaseAcpi = extern struct { + type: Type = .acpi, + subtype: Subtype = .base, + length: u16 align(1) = 12, + + /// Device’s PnP hardware ID stored in a numeric 32-bit compressed EISA-type ID. This value must match the + /// corresponding _HID in the ACPI name space. hid: u32 align(1), + + /// Unique ID that is required by ACPI if two devices have the same _HID. This value must also match the + /// corresponding _UID/_HID pair in the ACPI name space. uid: u32 align(1), - }; - comptime { - assert(12 == @sizeOf(BaseAcpiDevicePath)); - assert(1 == @alignOf(BaseAcpiDevicePath)); - - assert(0 == @offsetOf(BaseAcpiDevicePath, "type")); - assert(1 == @offsetOf(BaseAcpiDevicePath, "subtype")); - assert(2 == @offsetOf(BaseAcpiDevicePath, "length")); - assert(4 == @offsetOf(BaseAcpiDevicePath, "hid")); - assert(8 == @offsetOf(BaseAcpiDevicePath, "uid")); - } + comptime { + assert(12 == @sizeOf(BaseAcpi)); + assert(1 == @alignOf(BaseAcpi)); - pub const ExpandedAcpiDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, + assert(0 == @offsetOf(BaseAcpi, "type")); + assert(1 == @offsetOf(BaseAcpi, "subtype")); + assert(2 == @offsetOf(BaseAcpi, "length")); + assert(4 == @offsetOf(BaseAcpi, "hid")); + assert(8 == @offsetOf(BaseAcpi, "uid")); + } + }; + + pub const ExpandedAcpi = extern struct { + type: DevicePath.Type = .acpi, + subtype: Subtype = .expanded, length: u16 align(1), + + /// Device’s PnP hardware ID stored in a numeric 32-bit compressed EISA-type ID. This value must match the + /// corresponding _HID in the ACPI name space. hid: u32 align(1), + + /// Unique ID that is required by ACPI if two devices have the same _HID. This value must also match the + /// corresponding _UID/_HID pair in the ACPI name space. uid: u32 align(1), + + /// Device’s compatible PnP hardware ID stored in a numeric 32-bit compressed EISA-type ID. This value must + /// match at least one of the compatible device IDs returned by the corresponding _CID in the ACPI name space. cid: u32 align(1), - // variable length u16[*:0] strings - // hid_str, uid_str, cid_str + + /// Device’s PnP hardware ID stored as a null-terminated ASCII string. This value must match the corresponding + /// _HID in the ACPI namespace. If the length of this string not including the null-terminator is 0, then + /// the _HID field is used. If the length of this null-terminated string is greater than 0, then this field + /// supersedes the _HID field. + pub fn hidStr(self: *const ExpandedAcpi) [:0]const u8 { + const ptr = @as([*:0]const u8, @ptrCast(self)) + @sizeOf(ExpandedAcpi); + + return std.mem.span(ptr); + } + + /// Unique ID that is required by ACPI if two devices have the same _HID. This value must also match the + /// corresponding _UID/_HID pair in the ACPI name space. This value is stored as a null-terminated ASCII + /// string. If the length of this string not including the null-terminator is 0, then the _UID field is used. + /// If the length of this null-terminated string is greater than 0, then this field supersedes the _UID field. + pub fn uidStr(self: *const ExpandedAcpi) [:0]const u8 { + const hid = self.hidStr(); + const ptr = hid.ptr + hid.len + 2; + + return std.mem.span(ptr); + } + + /// Device’s compatible PnP hardware ID stored as a null-terminated ASCII string. This value must match at + /// least one of the compatible device IDs returned by the corresponding _CID in the ACPI namespace. If the + /// length of this string not including the null-terminator is 0, then the _CID field is used. If the length + /// of this null-terminated string is greater than 0, then this field supersedes the _CID field. + pub fn cidStr(self: *const ExpandedAcpi) [:0]const u8 { + const uid = self.uidStr(); + const ptr = uid.ptr + uid.len + 2; + + return std.mem.span(ptr); + } + + comptime { + assert(16 == @sizeOf(ExpandedAcpi)); + assert(1 == @alignOf(ExpandedAcpi)); + + assert(0 == @offsetOf(ExpandedAcpi, "type")); + assert(1 == @offsetOf(ExpandedAcpi, "subtype")); + assert(2 == @offsetOf(ExpandedAcpi, "length")); + assert(4 == @offsetOf(ExpandedAcpi, "hid")); + assert(8 == @offsetOf(ExpandedAcpi, "uid")); + assert(12 == @offsetOf(ExpandedAcpi, "cid")); + } }; - comptime { - assert(16 == @sizeOf(ExpandedAcpiDevicePath)); - assert(1 == @alignOf(ExpandedAcpiDevicePath)); - - assert(0 == @offsetOf(ExpandedAcpiDevicePath, "type")); - assert(1 == @offsetOf(ExpandedAcpiDevicePath, "subtype")); - assert(2 == @offsetOf(ExpandedAcpiDevicePath, "length")); - assert(4 == @offsetOf(ExpandedAcpiDevicePath, "hid")); - assert(8 == @offsetOf(ExpandedAcpiDevicePath, "uid")); - assert(12 == @offsetOf(ExpandedAcpiDevicePath, "cid")); - } + /// The _ADR device path is used to contain video output device attributes to support the Graphics Output + /// Protocol. The device path can contain multiple _ADR entries if multiple video output devices are displaying + /// the same output. + pub const Adr = extern struct { + type: DevicePath.Type = .acpi, + subtype: Subtype = .adr, + length: u16 align(1), // 4 + 4*x + + /// _ADR value. For video output devices the value of this field comes from Table B-2 ACPI 3.0 specification. + /// At least one _ADR value is required. + pub fn adrs(self: *const Adr) []align(1) const u32 { + const ptr = @as([*]const u32, @ptrCast(self)); + + const entries = @divExact(self.length, @sizeOf(u32)); + return ptr[1..entries]; + } - pub const AdrDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - adr: u32 align(1), + comptime { + assert(4 == @sizeOf(Adr)); + assert(1 == @alignOf(Adr)); - // multiple adr entries can optionally follow - pub fn adrs(self: *const AdrDevicePath) []align(1) const u32 { - // self.length is a minimum of 8 with one adr which is size 4. - const entries = (self.length - 4) / @sizeOf(u32); - return @as([*]align(1) const u32, @ptrCast(&self.adr))[0..entries]; + assert(0 == @offsetOf(Adr, "type")); + assert(1 == @offsetOf(Adr, "subtype")); + assert(2 == @offsetOf(Adr, "length")); } }; - comptime { - assert(8 == @sizeOf(AdrDevicePath)); - assert(1 == @alignOf(AdrDevicePath)); + pub const Nvdimm = extern struct { + type: DevicePath.Type = .acpi, + subtype: Subtype = .nvdimm, + length: u16 align(1) = 8, - assert(0 == @offsetOf(AdrDevicePath, "type")); - assert(1 == @offsetOf(AdrDevicePath, "subtype")); - assert(2 == @offsetOf(AdrDevicePath, "length")); - assert(4 == @offsetOf(AdrDevicePath, "adr")); - } + /// NFIT device handle + handle: u32, + }; }; pub const Messaging = union(Subtype) { - Atapi: *const AtapiDevicePath, - Scsi: *const ScsiDevicePath, - FibreChannel: *const FibreChannelDevicePath, - FibreChannelEx: *const FibreChannelExDevicePath, - @"1394": *const F1394DevicePath, - Usb: *const UsbDevicePath, - Sata: *const SataDevicePath, - UsbWwid: *const UsbWwidDevicePath, - Lun: *const DeviceLogicalUnitDevicePath, - UsbClass: *const UsbClassDevicePath, - I2o: *const I2oDevicePath, - MacAddress: *const MacAddressDevicePath, - Ipv4: *const Ipv4DevicePath, - Ipv6: *const Ipv6DevicePath, - Vlan: *const VlanDevicePath, - InfiniBand: *const InfiniBandDevicePath, - Uart: *const UartDevicePath, - Vendor: *const VendorDefinedDevicePath, + atapi: *const Atapi, + scsi: *const Scsi, + fibre_channel: *const FibreChannel, + fibre_channel_ex: *const FibreChannelEx, + @"1394": *const F1394, + usb: *const Usb, + sata: *const Sata, + usb_wwid: *const UsbWwid, + lun: *const DeviceLogicalUnit, + usb_class: *const UsbClass, + i2o: *const I2o, + mac_address: *const MacAddress, + ipv4: *const Ipv4, + ipv6: *const Ipv6, + vlan: *const Vlan, + infiniband: *const InfiniBand, + uart: *const UartDevicePath, + vendor: *const Vendor, pub const Subtype = enum(u8) { - Atapi = 1, - Scsi = 2, - FibreChannel = 3, - FibreChannelEx = 21, + atapi = 1, + scsi = 2, + fibre_channel = 3, + fibre_channel_ex = 21, @"1394" = 4, - Usb = 5, - Sata = 18, - UsbWwid = 16, - Lun = 17, - UsbClass = 15, - I2o = 6, - MacAddress = 11, - Ipv4 = 12, - Ipv6 = 13, - Vlan = 20, - InfiniBand = 9, - Uart = 14, - Vendor = 10, + usb = 5, + sata = 18, + usb_wwid = 16, + lun = 17, + usb_class = 15, + i2o = 6, + mac_address = 11, + ipv4 = 12, + ipv6 = 13, + vlan = 20, + infiniband = 9, + uart = 14, + vendor = 10, _, }; - pub const AtapiDevicePath = extern struct { + pub const Atapi = extern struct { const Role = enum(u8) { - Master = 0, - Slave = 1, + master = 0, + slave = 1, }; const Rank = enum(u8) { - Primary = 0, - Secondary = 1, + primary = 0, + secondary = 1, }; - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + type: Type = .messaging, + subtype: Subtype = .atapi, + length: u16 align(1) = 8, + + /// Primary or secondary channel primary_secondary: Rank, + + /// Master or slave device slave_master: Role, + + /// Logical unit number logical_unit_number: u16 align(1), + + comptime { + assert(8 == @sizeOf(Atapi)); + assert(1 == @alignOf(Atapi)); + + assert(0 == @offsetOf(Atapi, "type")); + assert(1 == @offsetOf(Atapi, "subtype")); + assert(2 == @offsetOf(Atapi, "length")); + assert(4 == @offsetOf(Atapi, "primary_secondary")); + assert(5 == @offsetOf(Atapi, "slave_master")); + assert(6 == @offsetOf(Atapi, "logical_unit_number")); + } }; - comptime { - assert(8 == @sizeOf(AtapiDevicePath)); - assert(1 == @alignOf(AtapiDevicePath)); - - assert(0 == @offsetOf(AtapiDevicePath, "type")); - assert(1 == @offsetOf(AtapiDevicePath, "subtype")); - assert(2 == @offsetOf(AtapiDevicePath, "length")); - assert(4 == @offsetOf(AtapiDevicePath, "primary_secondary")); - assert(5 == @offsetOf(AtapiDevicePath, "slave_master")); - assert(6 == @offsetOf(AtapiDevicePath, "logical_unit_number")); - } + pub const Scsi = extern struct { + type: Type = .messaging, + subtype: Subtype = .scsi, + length: u16 align(1) = 8, - pub const ScsiDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// Target ID on the SCSI bus target_id: u16 align(1), + + /// Logical unit number logical_unit_number: u16 align(1), + + comptime { + assert(8 == @sizeOf(Scsi)); + assert(1 == @alignOf(Scsi)); + + assert(0 == @offsetOf(Scsi, "type")); + assert(1 == @offsetOf(Scsi, "subtype")); + assert(2 == @offsetOf(Scsi, "length")); + assert(4 == @offsetOf(Scsi, "target_id")); + assert(6 == @offsetOf(Scsi, "logical_unit_number")); + } }; - comptime { - assert(8 == @sizeOf(ScsiDevicePath)); - assert(1 == @alignOf(ScsiDevicePath)); - - assert(0 == @offsetOf(ScsiDevicePath, "type")); - assert(1 == @offsetOf(ScsiDevicePath, "subtype")); - assert(2 == @offsetOf(ScsiDevicePath, "length")); - assert(4 == @offsetOf(ScsiDevicePath, "target_id")); - assert(6 == @offsetOf(ScsiDevicePath, "logical_unit_number")); - } + pub const FibreChannel = extern struct { + type: Type = .messaging, + subtype: Subtype = .fibre_channel, + length: u16 align(1) = 24, + reserved: u32 align(1) = 0, - pub const FibreChannelDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - reserved: u32 align(1), + /// World Wide Name world_wide_name: u64 align(1), + + /// Logical unit number logical_unit_number: u64 align(1), + + comptime { + assert(24 == @sizeOf(FibreChannel)); + assert(1 == @alignOf(FibreChannel)); + + assert(0 == @offsetOf(FibreChannel, "type")); + assert(1 == @offsetOf(FibreChannel, "subtype")); + assert(2 == @offsetOf(FibreChannel, "length")); + assert(4 == @offsetOf(FibreChannel, "reserved")); + assert(8 == @offsetOf(FibreChannel, "world_wide_name")); + assert(16 == @offsetOf(FibreChannel, "logical_unit_number")); + } }; - comptime { - assert(24 == @sizeOf(FibreChannelDevicePath)); - assert(1 == @alignOf(FibreChannelDevicePath)); - - assert(0 == @offsetOf(FibreChannelDevicePath, "type")); - assert(1 == @offsetOf(FibreChannelDevicePath, "subtype")); - assert(2 == @offsetOf(FibreChannelDevicePath, "length")); - assert(4 == @offsetOf(FibreChannelDevicePath, "reserved")); - assert(8 == @offsetOf(FibreChannelDevicePath, "world_wide_name")); - assert(16 == @offsetOf(FibreChannelDevicePath, "logical_unit_number")); - } + pub const FibreChannelEx = extern struct { + type: Type = .messaging, + subtype: Subtype = .fibre_channel_ex, + length: u16 align(1) = 24, + reserved: u32 align(1) = 0, - pub const FibreChannelExDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - reserved: u32 align(1), - world_wide_name: u64 align(1), - logical_unit_number: u64 align(1), + /// End Device Port Name + world_wide_name: [8]u8, + + /// Logical unit number + logical_unit_number: [8]u8, + + comptime { + assert(24 == @sizeOf(FibreChannelEx)); + assert(1 == @alignOf(FibreChannelEx)); + + assert(0 == @offsetOf(FibreChannelEx, "type")); + assert(1 == @offsetOf(FibreChannelEx, "subtype")); + assert(2 == @offsetOf(FibreChannelEx, "length")); + assert(4 == @offsetOf(FibreChannelEx, "reserved")); + assert(8 == @offsetOf(FibreChannelEx, "world_wide_name")); + assert(16 == @offsetOf(FibreChannelEx, "logical_unit_number")); + } }; - comptime { - assert(24 == @sizeOf(FibreChannelExDevicePath)); - assert(1 == @alignOf(FibreChannelExDevicePath)); - - assert(0 == @offsetOf(FibreChannelExDevicePath, "type")); - assert(1 == @offsetOf(FibreChannelExDevicePath, "subtype")); - assert(2 == @offsetOf(FibreChannelExDevicePath, "length")); - assert(4 == @offsetOf(FibreChannelExDevicePath, "reserved")); - assert(8 == @offsetOf(FibreChannelExDevicePath, "world_wide_name")); - assert(16 == @offsetOf(FibreChannelExDevicePath, "logical_unit_number")); - } + pub const F1394 = extern struct { + type: Type = .messaging, + subtype: Subtype = .@"1394", + length: u16 align(1) = 16, + reserved: u32 align(1) = 0, - pub const F1394DevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - reserved: u32 align(1), + /// 1394 GUID guid: u64 align(1), + + comptime { + assert(16 == @sizeOf(F1394)); + assert(1 == @alignOf(F1394)); + + assert(0 == @offsetOf(F1394, "type")); + assert(1 == @offsetOf(F1394, "subtype")); + assert(2 == @offsetOf(F1394, "length")); + assert(4 == @offsetOf(F1394, "reserved")); + assert(8 == @offsetOf(F1394, "guid")); + } }; - comptime { - assert(16 == @sizeOf(F1394DevicePath)); - assert(1 == @alignOf(F1394DevicePath)); - - assert(0 == @offsetOf(F1394DevicePath, "type")); - assert(1 == @offsetOf(F1394DevicePath, "subtype")); - assert(2 == @offsetOf(F1394DevicePath, "length")); - assert(4 == @offsetOf(F1394DevicePath, "reserved")); - assert(8 == @offsetOf(F1394DevicePath, "guid")); - } + pub const Usb = extern struct { + type: Type = .messaging, + subtype: Subtype = .usb, + length: u16 align(1) = 6, - pub const UsbDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// USB parent port number parent_port_number: u8, + + /// USB interface number interface_number: u8, + + comptime { + assert(6 == @sizeOf(Usb)); + assert(1 == @alignOf(Usb)); + + assert(0 == @offsetOf(Usb, "type")); + assert(1 == @offsetOf(Usb, "subtype")); + assert(2 == @offsetOf(Usb, "length")); + assert(4 == @offsetOf(Usb, "parent_port_number")); + assert(5 == @offsetOf(Usb, "interface_number")); + } }; - comptime { - assert(6 == @sizeOf(UsbDevicePath)); - assert(1 == @alignOf(UsbDevicePath)); - - assert(0 == @offsetOf(UsbDevicePath, "type")); - assert(1 == @offsetOf(UsbDevicePath, "subtype")); - assert(2 == @offsetOf(UsbDevicePath, "length")); - assert(4 == @offsetOf(UsbDevicePath, "parent_port_number")); - assert(5 == @offsetOf(UsbDevicePath, "interface_number")); - } + pub const Sata = extern struct { + type: DevicePath.Type = .messaging, + subtype: Subtype = .sata, + length: u16 align(1) = 10, - pub const SataDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// The HBA port number that facilitates the connection to the device or a port multiplier. The value 0xFFFF + /// is reserved. hba_port_number: u16 align(1), + + /// The port multiplier port number that facilitates the connection to the device. Must be set to 0xFFFF if + /// the device is directly connected to the HBA. port_multiplier_port_number: u16 align(1), + + /// The logical unit number of the device. logical_unit_number: u16 align(1), + + comptime { + assert(10 == @sizeOf(Sata)); + assert(1 == @alignOf(Sata)); + + assert(0 == @offsetOf(Sata, "type")); + assert(1 == @offsetOf(Sata, "subtype")); + assert(2 == @offsetOf(Sata, "length")); + assert(4 == @offsetOf(Sata, "hba_port_number")); + assert(6 == @offsetOf(Sata, "port_multiplier_port_number")); + assert(8 == @offsetOf(Sata, "logical_unit_number")); + } }; - comptime { - assert(10 == @sizeOf(SataDevicePath)); - assert(1 == @alignOf(SataDevicePath)); - - assert(0 == @offsetOf(SataDevicePath, "type")); - assert(1 == @offsetOf(SataDevicePath, "subtype")); - assert(2 == @offsetOf(SataDevicePath, "length")); - assert(4 == @offsetOf(SataDevicePath, "hba_port_number")); - assert(6 == @offsetOf(SataDevicePath, "port_multiplier_port_number")); - assert(8 == @offsetOf(SataDevicePath, "logical_unit_number")); - } + /// This device path describes a USB device using its serial number. + pub const UsbWwid = extern struct { + type: Type = .messaging, + subtype: Subtype = .usb_wwid, + length: u16 align(1), // 10 + 2*x - pub const UsbWwidDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// USB interface number interface_number: u16 align(1), + + /// USB device vendor ID device_vendor_id: u16 align(1), + + /// USB device product ID device_product_id: u16 align(1), - pub fn serial_number(self: *const UsbWwidDevicePath) []align(1) const u16 { - const serial_len = (self.length - @sizeOf(UsbWwidDevicePath)) / @sizeOf(u16); - return @as([*]align(1) const u16, @ptrCast(@as([*]const u8, @ptrCast(self)) + @sizeOf(UsbWwidDevicePath)))[0..serial_len]; + /// Last 64-or-fewer UTF-16 characters of the USB serial number. + pub fn serial(self: *const UsbWwid) []align(1) const u16 { + // includes the 5 u16s that come before the serial number + const serial_len = @divExact(self.length, @sizeOf(u16)); + const ptr = @as([*]const u16, @ptrCast(self)); + + return ptr[5..serial_len]; + } + + comptime { + assert(10 == @sizeOf(UsbWwid)); + assert(1 == @alignOf(UsbWwid)); + + assert(0 == @offsetOf(UsbWwid, "type")); + assert(1 == @offsetOf(UsbWwid, "subtype")); + assert(2 == @offsetOf(UsbWwid, "length")); + assert(4 == @offsetOf(UsbWwid, "interface_number")); + assert(6 == @offsetOf(UsbWwid, "device_vendor_id")); + assert(8 == @offsetOf(UsbWwid, "device_product_id")); } }; - comptime { - assert(10 == @sizeOf(UsbWwidDevicePath)); - assert(1 == @alignOf(UsbWwidDevicePath)); - - assert(0 == @offsetOf(UsbWwidDevicePath, "type")); - assert(1 == @offsetOf(UsbWwidDevicePath, "subtype")); - assert(2 == @offsetOf(UsbWwidDevicePath, "length")); - assert(4 == @offsetOf(UsbWwidDevicePath, "interface_number")); - assert(6 == @offsetOf(UsbWwidDevicePath, "device_vendor_id")); - assert(8 == @offsetOf(UsbWwidDevicePath, "device_product_id")); - } + pub const DeviceLogicalUnit = extern struct { + type: Type = .messaging, + subtype: Subtype = .lun, + length: u16 align(1) = 5, - pub const DeviceLogicalUnitDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// Logical unit number lun: u8, - }; - comptime { - assert(5 == @sizeOf(DeviceLogicalUnitDevicePath)); - assert(1 == @alignOf(DeviceLogicalUnitDevicePath)); + comptime { + assert(5 == @sizeOf(DeviceLogicalUnit)); + assert(1 == @alignOf(DeviceLogicalUnit)); - assert(0 == @offsetOf(DeviceLogicalUnitDevicePath, "type")); - assert(1 == @offsetOf(DeviceLogicalUnitDevicePath, "subtype")); - assert(2 == @offsetOf(DeviceLogicalUnitDevicePath, "length")); - assert(4 == @offsetOf(DeviceLogicalUnitDevicePath, "lun")); - } + assert(0 == @offsetOf(DeviceLogicalUnit, "type")); + assert(1 == @offsetOf(DeviceLogicalUnit, "subtype")); + assert(2 == @offsetOf(DeviceLogicalUnit, "length")); + assert(4 == @offsetOf(DeviceLogicalUnit, "lun")); + } + }; + + pub const UsbClass = extern struct { + type: Type = .messaging, + subtype: Subtype = .usb_class, + length: u16 align(1) = 11, - pub const UsbClassDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), vendor_id: u16 align(1), + product_id: u16 align(1), + device_class: u8, + device_subclass: u8, + device_protocol: u8, + + comptime { + assert(11 == @sizeOf(UsbClass)); + assert(1 == @alignOf(UsbClass)); + + assert(0 == @offsetOf(UsbClass, "type")); + assert(1 == @offsetOf(UsbClass, "subtype")); + assert(2 == @offsetOf(UsbClass, "length")); + assert(4 == @offsetOf(UsbClass, "vendor_id")); + assert(6 == @offsetOf(UsbClass, "product_id")); + assert(8 == @offsetOf(UsbClass, "device_class")); + assert(9 == @offsetOf(UsbClass, "device_subclass")); + assert(10 == @offsetOf(UsbClass, "device_protocol")); + } }; - comptime { - assert(11 == @sizeOf(UsbClassDevicePath)); - assert(1 == @alignOf(UsbClassDevicePath)); - - assert(0 == @offsetOf(UsbClassDevicePath, "type")); - assert(1 == @offsetOf(UsbClassDevicePath, "subtype")); - assert(2 == @offsetOf(UsbClassDevicePath, "length")); - assert(4 == @offsetOf(UsbClassDevicePath, "vendor_id")); - assert(6 == @offsetOf(UsbClassDevicePath, "product_id")); - assert(8 == @offsetOf(UsbClassDevicePath, "device_class")); - assert(9 == @offsetOf(UsbClassDevicePath, "device_subclass")); - assert(10 == @offsetOf(UsbClassDevicePath, "device_protocol")); - } + pub const I2o = extern struct { + type: Type = .messaging, + subtype: Subtype = .i2o, + length: u16 align(1) = 8, - pub const I2oDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// Target ID for the device tid: u32 align(1), + + comptime { + assert(8 == @sizeOf(I2o)); + assert(1 == @alignOf(I2o)); + + assert(0 == @offsetOf(I2o, "type")); + assert(1 == @offsetOf(I2o, "subtype")); + assert(2 == @offsetOf(I2o, "length")); + assert(4 == @offsetOf(I2o, "tid")); + } }; - comptime { - assert(8 == @sizeOf(I2oDevicePath)); - assert(1 == @alignOf(I2oDevicePath)); + pub const MacAddress = extern struct { + type: Type = .messaging, + subtype: Subtype = .mac_address, + length: u16 align(1) = 37, - assert(0 == @offsetOf(I2oDevicePath, "type")); - assert(1 == @offsetOf(I2oDevicePath, "subtype")); - assert(2 == @offsetOf(I2oDevicePath, "length")); - assert(4 == @offsetOf(I2oDevicePath, "tid")); - } + /// Network interface MAC address, padded with zeros + mac_address: bits.MacAddress, - pub const MacAddressDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - mac_address: uefi.MacAddress, + /// Network interface type, see RFC 3232. if_type: u8, - }; - comptime { - assert(37 == @sizeOf(MacAddressDevicePath)); - assert(1 == @alignOf(MacAddressDevicePath)); - - assert(0 == @offsetOf(MacAddressDevicePath, "type")); - assert(1 == @offsetOf(MacAddressDevicePath, "subtype")); - assert(2 == @offsetOf(MacAddressDevicePath, "length")); - assert(4 == @offsetOf(MacAddressDevicePath, "mac_address")); - assert(36 == @offsetOf(MacAddressDevicePath, "if_type")); - } + comptime { + assert(37 == @sizeOf(MacAddress)); + assert(1 == @alignOf(MacAddress)); + + assert(0 == @offsetOf(MacAddress, "type")); + assert(1 == @offsetOf(MacAddress, "subtype")); + assert(2 == @offsetOf(MacAddress, "length")); + assert(4 == @offsetOf(MacAddress, "mac_address")); + assert(36 == @offsetOf(MacAddress, "if_type")); + } + }; - pub const Ipv4DevicePath = extern struct { + pub const Ipv4 = extern struct { pub const IpType = enum(u8) { - Dhcp = 0, - Static = 1, + dhcp = 0, + static = 1, }; - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - local_ip_address: uefi.Ipv4Address align(1), - remote_ip_address: uefi.Ipv4Address align(1), + type: Type = .messaging, + subtype: Subtype = .ipv4, + length: u16 align(1) = 27, + + /// Local IP address + local_ip_address: bits.Ipv4Address align(1), + + /// Remote IP address + remote_ip_address: bits.Ipv4Address align(1), + + /// Local port number local_port: u16 align(1), + + /// Remote port number remote_port: u16 align(1), + + /// Network protocol, see RFC 3232 network_protocol: u16 align(1), + + /// If the address was assigned statically or via DHCP static_ip_address: IpType, - gateway_ip_address: u32 align(1), - subnet_mask: u32 align(1), - }; - comptime { - assert(27 == @sizeOf(Ipv4DevicePath)); - assert(1 == @alignOf(Ipv4DevicePath)); - - assert(0 == @offsetOf(Ipv4DevicePath, "type")); - assert(1 == @offsetOf(Ipv4DevicePath, "subtype")); - assert(2 == @offsetOf(Ipv4DevicePath, "length")); - assert(4 == @offsetOf(Ipv4DevicePath, "local_ip_address")); - assert(8 == @offsetOf(Ipv4DevicePath, "remote_ip_address")); - assert(12 == @offsetOf(Ipv4DevicePath, "local_port")); - assert(14 == @offsetOf(Ipv4DevicePath, "remote_port")); - assert(16 == @offsetOf(Ipv4DevicePath, "network_protocol")); - assert(18 == @offsetOf(Ipv4DevicePath, "static_ip_address")); - assert(19 == @offsetOf(Ipv4DevicePath, "gateway_ip_address")); - assert(23 == @offsetOf(Ipv4DevicePath, "subnet_mask")); - } + /// Gateway IP address + gateway_ip_address: bits.Ipv4Address align(1), + + /// Subnet mask + subnet_mask: bits.Ipv4Address align(1), + + comptime { + assert(27 == @sizeOf(Ipv4)); + assert(1 == @alignOf(Ipv4)); + + assert(0 == @offsetOf(Ipv4, "type")); + assert(1 == @offsetOf(Ipv4, "subtype")); + assert(2 == @offsetOf(Ipv4, "length")); + assert(4 == @offsetOf(Ipv4, "local_ip_address")); + assert(8 == @offsetOf(Ipv4, "remote_ip_address")); + assert(12 == @offsetOf(Ipv4, "local_port")); + assert(14 == @offsetOf(Ipv4, "remote_port")); + assert(16 == @offsetOf(Ipv4, "network_protocol")); + assert(18 == @offsetOf(Ipv4, "static_ip_address")); + assert(19 == @offsetOf(Ipv4, "gateway_ip_address")); + assert(23 == @offsetOf(Ipv4, "subnet_mask")); + } + }; - pub const Ipv6DevicePath = extern struct { + pub const Ipv6 = extern struct { pub const Origin = enum(u8) { - Manual = 0, - AssignedStateless = 1, - AssignedStateful = 2, + manual = 0, + assigned_stateless = 1, + assigned_stateful = 2, }; - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - local_ip_address: uefi.Ipv6Address, - remote_ip_address: uefi.Ipv6Address, + type: Type = .messaging, + subtype: Subtype = .ipv6, + length: u16 align(1) = 60, + + /// Local IP address + local_ip_address: bits.Ipv6Address, + + /// Remote IP address + remote_ip_address: bits.Ipv6Address, + + /// Local port number local_port: u16 align(1), + + /// Remote port number remote_port: u16 align(1), + + /// Network protocol, see RFC 3232 protocol: u16 align(1), + + /// If the address was assigned manually or via autoconfiguration ip_address_origin: Origin, + + /// Prefix length prefix_length: u8, - gateway_ip_address: uefi.Ipv6Address, - }; - comptime { - assert(60 == @sizeOf(Ipv6DevicePath)); - assert(1 == @alignOf(Ipv6DevicePath)); - - assert(0 == @offsetOf(Ipv6DevicePath, "type")); - assert(1 == @offsetOf(Ipv6DevicePath, "subtype")); - assert(2 == @offsetOf(Ipv6DevicePath, "length")); - assert(4 == @offsetOf(Ipv6DevicePath, "local_ip_address")); - assert(20 == @offsetOf(Ipv6DevicePath, "remote_ip_address")); - assert(36 == @offsetOf(Ipv6DevicePath, "local_port")); - assert(38 == @offsetOf(Ipv6DevicePath, "remote_port")); - assert(40 == @offsetOf(Ipv6DevicePath, "protocol")); - assert(42 == @offsetOf(Ipv6DevicePath, "ip_address_origin")); - assert(43 == @offsetOf(Ipv6DevicePath, "prefix_length")); - assert(44 == @offsetOf(Ipv6DevicePath, "gateway_ip_address")); - } + /// Gateway IP address + gateway_ip_address: bits.Ipv6Address, + + comptime { + assert(60 == @sizeOf(Ipv6)); + assert(1 == @alignOf(Ipv6)); + + assert(0 == @offsetOf(Ipv6, "type")); + assert(1 == @offsetOf(Ipv6, "subtype")); + assert(2 == @offsetOf(Ipv6, "length")); + assert(4 == @offsetOf(Ipv6, "local_ip_address")); + assert(20 == @offsetOf(Ipv6, "remote_ip_address")); + assert(36 == @offsetOf(Ipv6, "local_port")); + assert(38 == @offsetOf(Ipv6, "remote_port")); + assert(40 == @offsetOf(Ipv6, "protocol")); + assert(42 == @offsetOf(Ipv6, "ip_address_origin")); + assert(43 == @offsetOf(Ipv6, "prefix_length")); + assert(44 == @offsetOf(Ipv6, "gateway_ip_address")); + } + }; - pub const VlanDevicePath = extern struct { + pub const Vlan = extern struct { type: DevicePath.Type, subtype: Subtype, length: u16 align(1), @@ -607,16 +807,16 @@ pub const DevicePath = union(Type) { }; comptime { - assert(6 == @sizeOf(VlanDevicePath)); - assert(1 == @alignOf(VlanDevicePath)); + assert(6 == @sizeOf(Vlan)); + assert(1 == @alignOf(Vlan)); - assert(0 == @offsetOf(VlanDevicePath, "type")); - assert(1 == @offsetOf(VlanDevicePath, "subtype")); - assert(2 == @offsetOf(VlanDevicePath, "length")); - assert(4 == @offsetOf(VlanDevicePath, "vlan_id")); + assert(0 == @offsetOf(Vlan, "type")); + assert(1 == @offsetOf(Vlan, "subtype")); + assert(2 == @offsetOf(Vlan, "length")); + assert(4 == @offsetOf(Vlan, "vlan_id")); } - pub const InfiniBandDevicePath = extern struct { + pub const InfiniBand = extern struct { pub const ResourceFlags = packed struct(u32) { pub const ControllerType = enum(u1) { Ioc = 0, @@ -641,21 +841,21 @@ pub const DevicePath = union(Type) { service_id: u64 align(1), target_port_id: u64 align(1), device_id: u64 align(1), - }; - comptime { - assert(48 == @sizeOf(InfiniBandDevicePath)); - assert(1 == @alignOf(InfiniBandDevicePath)); - - assert(0 == @offsetOf(InfiniBandDevicePath, "type")); - assert(1 == @offsetOf(InfiniBandDevicePath, "subtype")); - assert(2 == @offsetOf(InfiniBandDevicePath, "length")); - assert(4 == @offsetOf(InfiniBandDevicePath, "resource_flags")); - assert(8 == @offsetOf(InfiniBandDevicePath, "port_gid")); - assert(24 == @offsetOf(InfiniBandDevicePath, "service_id")); - assert(32 == @offsetOf(InfiniBandDevicePath, "target_port_id")); - assert(40 == @offsetOf(InfiniBandDevicePath, "device_id")); - } + comptime { + assert(48 == @sizeOf(InfiniBand)); + assert(1 == @alignOf(InfiniBand)); + + assert(0 == @offsetOf(InfiniBand, "type")); + assert(1 == @offsetOf(InfiniBand, "subtype")); + assert(2 == @offsetOf(InfiniBand, "length")); + assert(4 == @offsetOf(InfiniBand, "resource_flags")); + assert(8 == @offsetOf(InfiniBand, "port_gid")); + assert(24 == @offsetOf(InfiniBand, "service_id")); + assert(32 == @offsetOf(InfiniBand, "target_port_id")); + assert(40 == @offsetOf(InfiniBand, "device_id")); + } + }; pub const UartDevicePath = extern struct { pub const Parity = enum(u8) { @@ -700,7 +900,7 @@ pub const DevicePath = union(Type) { assert(18 == @offsetOf(UartDevicePath, "stop_bits")); } - pub const VendorDefinedDevicePath = extern struct { + pub const Vendor = extern struct { type: DevicePath.Type, subtype: Subtype, length: u16 align(1), @@ -708,13 +908,13 @@ pub const DevicePath = union(Type) { }; comptime { - assert(20 == @sizeOf(VendorDefinedDevicePath)); - assert(1 == @alignOf(VendorDefinedDevicePath)); + assert(20 == @sizeOf(Vendor)); + assert(1 == @alignOf(Vendor)); - assert(0 == @offsetOf(VendorDefinedDevicePath, "type")); - assert(1 == @offsetOf(VendorDefinedDevicePath, "subtype")); - assert(2 == @offsetOf(VendorDefinedDevicePath, "length")); - assert(4 == @offsetOf(VendorDefinedDevicePath, "vendor_guid")); + assert(0 == @offsetOf(Vendor, "type")); + assert(1 == @offsetOf(Vendor, "subtype")); + assert(2 == @offsetOf(Vendor, "length")); + assert(4 == @offsetOf(Vendor, "vendor_guid")); } }; diff --git a/lib/std/os/uefi/hii.zig b/lib/std/os/uefi/hii.zig index 0b22e026b59b..3008862da510 100644 --- a/lib/std/os/uefi/hii.zig +++ b/lib/std/os/uefi/hii.zig @@ -1,7 +1,6 @@ -const uefi = @import("std").os.uefi; -const Guid = uefi.Guid; +const bits = @import("bits.zig"); -pub const Handle = *opaque {}; +const Guid = bits.Guid; /// The header found at the start of each package. pub const PackageHeader = packed struct(u32) { diff --git a/lib/std/os/uefi/pool_allocator.zig b/lib/std/os/uefi/pool_allocator.zig index aa9798c6e5fb..649fd8485884 100644 --- a/lib/std/os/uefi/pool_allocator.zig +++ b/lib/std/os/uefi/pool_allocator.zig @@ -1,4 +1,4 @@ -const std = @import("std"); +const std = @import("../../std.zig"); const mem = std.mem; const uefi = std.os.uefi; diff --git a/lib/std/os/uefi/table.zig b/lib/std/os/uefi/table.zig new file mode 100644 index 000000000000..f1f4d56d2deb --- /dev/null +++ b/lib/std/os/uefi/table.zig @@ -0,0 +1,13 @@ +pub const Header = @import("table/header.zig").Header; +pub const BootServices = @import("table/boot_services.zig").BootServices; +pub const RuntimeServices = @import("table/runtime_services.zig").RuntimeServices; +pub const SystemTable = @import("table/system.zig").SystemTable; + +const configuration_table = @import("table/configuration.zig"); +pub const ConfigurationTable = configuration_table.ConfigurationTable; +pub const RtPropertiesTable = configuration_table.RtPropertiesTable; +pub const MemoryAttributesTable = configuration_table.MemoryAttributesTable; + +test { + @import("std").testing.refAllDeclsRecursive(@This()); +} diff --git a/lib/std/os/uefi/tables/boot_services.zig b/lib/std/os/uefi/table/boot_services.zig similarity index 63% rename from lib/std/os/uefi/tables/boot_services.zig rename to lib/std/os/uefi/table/boot_services.zig index a518bf5c5910..94a8d98758d0 100644 --- a/lib/std/os/uefi/tables/boot_services.zig +++ b/lib/std/os/uefi/table/boot_services.zig @@ -1,31 +1,18 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Event = uefi.Event; -const Guid = uefi.Guid; -const Handle = uefi.Handle; -const Status = uefi.Status; -const TableHeader = uefi.tables.TableHeader; -const DevicePathProtocol = uefi.protocol.DevicePath; -const AllocateType = uefi.tables.AllocateType; -const MemoryType = uefi.tables.MemoryType; -const MemoryDescriptor = uefi.tables.MemoryDescriptor; -const TimerDelay = uefi.tables.TimerDelay; -const EfiInterfaceType = uefi.tables.EfiInterfaceType; -const LocateSearchType = uefi.tables.LocateSearchType; -const OpenProtocolAttributes = uefi.tables.OpenProtocolAttributes; -const ProtocolInformationEntry = uefi.tables.ProtocolInformationEntry; -const EfiEventNotify = uefi.tables.EfiEventNotify; -const cc = uefi.cc; -const Guid = uefi.Guid; -const Status = uefi.Status; - -const DevicePathProtocol = uefi.protocol.DevicePath; - -const Event = uefi.Event; -const Handle = uefi.Handle; - -const PhysicalAddress = u64; -const VirtualAddress = u64; +const std = @import("../../../std.zig"); + +const bits = @import("../bits.zig"); +const table = @import("../table.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; +const DevicePathProtocol = @import("../protocol.zig").DevicePath; + +const Guid = bits.Guid; +const Event = bits.Event; +const Handle = bits.Handle; +const PhysicalAddress = bits.PhysicalAddress; +const VirtualAddress = bits.VirtualAddress; + const ProtocolInterface = *const anyopaque; const RegistrationValue = *const anyopaque; @@ -41,15 +28,15 @@ const RegistrationValue = *const anyopaque; /// /// As the boot_services table may grow with new UEFI versions, it is important to check hdr.header_size. pub const BootServices = extern struct { - hdr: uefi.tables.TableHeader, + hdr: table.Header, _raiseTpl: *const fn (new_tpl: TaskPriorityLevel) callconv(cc) TaskPriorityLevel, _restoreTpl: *const fn (old_tpl: TaskPriorityLevel) callconv(cc) void, - _allocatePages: *const fn (alloc_type: AllocateType, mem_type: MemoryType, pages: usize, memory: PhysicalAddress) callconv(cc) Status, + _allocatePages: *const fn (alloc_type: AllocateType, mem_type: bits.MemoryDescriptor.Type, pages: usize, memory: PhysicalAddress) callconv(cc) Status, _freePages: *const fn (memory: [*]align(4096) u8, pages: usize) callconv(cc) Status, - _getMemoryMap: *const fn (mmap_size: *usize, mmap: ?*const anyopaque, mapKey: *MemoryMap.Key, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status, - _allocatePool: *const fn (pool_type: MemoryType, size: usize, buffer: *[*]align(8) u8) callconv(cc) Status, + _getMemoryMap: *const fn (mmap_size: *usize, mmap: ?*anyopaque, mapKey: *MemoryMap.Key, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status, + _allocatePool: *const fn (pool_type: bits.MemoryDescriptor.Type, size: usize, buffer: *[*]align(8) u8) callconv(cc) Status, _freePool: *const fn (buffer: [*]align(8) u8) callconv(cc) Status, _createEvent: *const fn (type: u32, notify_tpl: TaskPriorityLevel, notify_func: ?EventNotify, notifyCtx: ?EventNotifyContext, event: *Event) callconv(cc) Status, @@ -110,6 +97,38 @@ pub const BootServices = extern struct { // Following introduced in UEFI 2.0 _createEventEx: *const fn (type: u32, notify_tpl: TaskPriorityLevel, notify_func: ?EventNotify, notify_ctx: ?EventNotifyContext, event_group: ?*align(8) const Guid, event: *Event) callconv(cc) Status, + /// Function pointer type of event notification callbacks. + pub const EventNotify = *const fn (event: Event, ctx: *anyopaque) callconv(cc) void; + + /// The context type passed to event notification callbacks. + pub const EventNotifyContext = *const anyopaque; + + /// These types can be "ORed" together as needed. + pub const EventKind = struct { + /// The event is a timer event and may be used in a call to `setTimer()`. Note that timers only function during boot + /// services time. + pub const timer: u32 = 0x80000000; + + /// The event is allocated from runtime memory. If an event is to be signaled after the call to `exitBootServices()` + /// the event’s data structure and notification function need to be allocated from runtime memory. + pub const runtime: u32 = 0x40000000; + + /// If an event of this type is not already in the signaled state, then the event’s *notify_func* will be + /// queued at the event’s *notify_tpl* whenever the event is being waited on via `waitForEvent()` or `checkEvent()`. + pub const notify_wait: u32 = 0x00000100; + + /// The event’s *notify_func* is queued whenever the event is signaled. + pub const notify_signal: u32 = 0x00000200; + + /// This event is of type `notify_signal`. It should not be combined with any other event types. This event type + /// is functionally equivalent to the `EventGroup.exit_boot_services` event group. + pub const signal_exit_boot_services: u32 = 0x00000201; + + /// This event is of type `notify_signal`. It should not be combined with any other event types. The event is to be + /// notified by the system when `setVirtualAddressMap()` is performed. + pub const signal_virtual_address_change: u32 = 0x60000202; + }; + /// Creates an event. pub fn createEvent( self: *const BootServices, @@ -127,6 +146,90 @@ pub const BootServices = extern struct { return event; } + /// A set of common event group GUIDs. + pub const EventGroup = struct { + /// This event group is notified by the system when `exitBootServices()` is invoked after notifying + /// `before_exit_boot_services` event group. The notification function is not allow to use the Memory Allocation + /// Services. The notification must not depend on timer events since timer services will be deactivated before + /// this group is dispatched. + pub const exit_boot_services align(8) = Guid{ + .time_low = 0x27abf055, + .time_mid = 0xb1b8, + .time_high_and_version = 0x4c26, + .clock_seq_high_and_reserved = 0x80, + .clock_seq_low = 0x48, + .node = [_]u8{ 0x74, 0x8f, 0x37, 0xba, 0xa2, 0xdf }, + }; + + /// This event group is notified by the system ExitBootServices() is invoked right before notifying + /// `exit_boot_services` event group. The event presents the last opportunity to use firmware interfaces in the + /// boot environment. The notification function must not depend on any kind of delayed processing because the + /// firmware deactivates timer services right after dispatching this event group. + pub const before_exit_boot_services align(8) = Guid{ + .time_low = 0x8be0e274, + .time_mid = 0x3970, + .time_high_and_version = 0x4b44, + .clock_seq_high_and_reserved = 0x80, + .clock_seq_low = 0xc5, + .node = [_]u8{ 0x1a, 0xb9, 0x50, 0x2f, 0x3b, 0xfc }, + }; + + /// This event group is notified by the system when `setVirtualAddressMap()` is invoked. + pub const virtual_address_change align(8) = Guid{ + .time_low = 0x13fa7698, + .time_mid = 0xc831, + .time_high_and_version = 0x49c7, + .clock_seq_high_and_reserved = 0x87, + .clock_seq_low = 0xea, + .node = [_]u8{ 0x8f, 0x43, 0xfc, 0xc2, 0x51, 0x96 }, + }; + + /// This event group is notified by the system when the memory map has changed. The notification function should + /// not use Memory Allocation Services to avoid reentrancy issues. + pub const memory_map_change align(8) = Guid{ + .time_low = 0x78bee926, + .time_mid = 0x692f, + .time_high_and_version = 0x48fd, + .clock_seq_high_and_reserved = 0x9e, + .clock_seq_low = 0xdb, + .node = [_]u8{ 0x01, 0x42, 0x2e, 0xf0, 0xd7, 0xab }, + }; + + /// This event group is notified by the system right before notifying `after_ready_to_boot`event group when the + /// Boot Manager is about to load and execute a boot option or a platform or OS recovery option. The event group + /// presents the last chance to modify device or system configuration prior to passing control to a boot option. + pub const ready_to_boot align(8) = Guid{ + .time_low = 0x7ce88fb3, + .time_mid = 0x4bd7, + .time_high_and_version = 0x4679, + .clock_seq_high_and_reserved = 0x87, + .clock_seq_low = 0xa8, + .node = [_]u8{ 0xa8, 0xd8, 0xde, 0xe5, 0x0d, 0x2b }, + }; + + /// This event group is notified by the system immediately after notifying `ready_to_boot` event group when the + /// Boot Manager is about to load and execute a boot option or a platform or OS recovery option. The event group + /// presents the last chance to survey device or system configuration prior to passing control to a boot option. + pub const after_ready_to_boot align(8) = Guid{ + .time_low = 0x3a2a00ad, + .time_mid = 0x98b9, + .time_high_and_version = 0x4cdf, + .clock_seq_high_and_reserved = 0xa4, + .clock_seq_low = 0x78, + .node = [_]u8{ 0x70, 0x27, 0x77, 0xf1, 0xc1, 0x0b }, + }; + + /// This event group is notified by the system when `resetSystem()` is invoked and the system is about to be reset. + pub const reset_system align(8) = Guid{ + .time_low = 0x62da6a56, + .time_mid = 0x13fb, + .time_high_and_version = 0x485a, + .clock_seq_high_and_reserved = 0xa8, + .clock_seq_low = 0xda, + .node = [_]u8{ 0xa3, 0xdd, 0x79, 0x12, 0xcb, 0x6b }, + }; + }; + /// Creates an event in a group. pub fn createEventEx( self: *const BootServices, @@ -196,6 +299,30 @@ pub const BootServices = extern struct { } } + /// How the timer is to be set. + pub const TimerKind = union(Enum) { + pub const Enum = enum(u32) { + /// The timer is canceled. + cancel = 0, + + /// The timer is a periodic timer. + periodic = 1, + + /// The timer is a relative timer. + relative = 2, + }; + + /// The timer is to be canceled. + cancel: void, + + /// The timer is to be signaled periodically at `trigger_time` ns intervals. The event timer does not need to be + /// reset for each notification. + periodic: u64, + + /// The timer is to be signaled after `trigger_time` ns. + relative: u64, + }; + /// Sets the type of timer and the trigger time for a timer event. /// /// Timers only have 100ns time resolution. @@ -213,8 +340,33 @@ pub const BootServices = extern struct { } } + /// The priority level of a task. The higher the number, the higher the priority. + pub const TaskPriorityLevel = enum(usize) { + /// This is the lowest priority level. It is the level of execution which occurs when no event notifications are + /// pending and which interacts with the user. User I/O (and blocking on User I/O) can be performed at this level. + /// The boot manager executes at this level and passes control to other UEFI applications at this level. + application = 4, + + /// Interrupts code executing below TPL_CALLBACK level. Long term operations (such as file system operations and + /// disk I/O) can occur at this level + callback = 8, + + /// Interrupts code executing below TPL_NOTIFY level. Blocking is not allowed at this level. Code executes to + /// completion and returns. If code requires more processing, it needs to signal an event to wait to obtain control + /// again at whatever level it requires. This level is typically used to process low level IO to or from a device. + notify = 16, + + /// Interrupts code executing below TPL_HIGH_LEVEL This is the highest priority level. It is not interruptible + /// (interrupts are disabled) and is used sparingly by firmware to synchronize operations that need to be accessible + /// from any priority level. For example, it must be possible to signal events while executing at any priority level. + /// Therefore, firmware manipulates the internal event structure while at this priority level. + high_level = 31, + }; + /// Raises a task's priority level and returns its previous level. /// + /// The new TPL *must* be higher than the current TPL, otherwise the system may exhibit indeterminate behavior. + /// /// The caller must restore the task priority level with `restoreTPL()` to the previous level before /// returning control to the system. pub fn raiseTpl( @@ -234,6 +386,29 @@ pub const BootServices = extern struct { self._restoreTpl(old_tpl); } + pub const AllocateType = union(Enum) { + pub const Enum = enum(u32) { + /// Allocate any available range of pages that satisfies the request. + any = 0, + + /// Allocate any available range of pages whose uppermost address is less than or equal to a specified + /// address. + max_address = 1, + + /// Allocate pages at a specified address. + at_address = 2, + }; + + /// Allocate any available range of pages that satisfies the request. + any: void, + + /// Allocate any available range of pages whose uppermost address is less than or equal to a specified address. + max_address: PhysicalAddress, + + /// Allocate pages at a specified address. + at_address: PhysicalAddress, + }; + /// Allocates memory pages from the system. /// /// The memory returned is physical memory, apply the virtual address map to get the correct virtual address. @@ -242,7 +417,7 @@ pub const BootServices = extern struct { /// The type of allocation to perform. alloc_type: AllocateType, /// The type of memory to allocate. - mem_type: MemoryType, + mem_type: bits.MemoryDescriptor.Type, /// The number of contiguous 4 KiB pages to allocate. pages: usize, ) ![]align(4096) u8 { @@ -271,6 +446,81 @@ pub const BootServices = extern struct { _ = self._freePages(memory.ptr, @divExact(memory.len, 4096)); } + /// A structure containing all necessary information about a memory map, and providing the methods required to + /// access it correctly. + pub const MemoryMap = struct { + pub const Key = enum(usize) { _ }; + + map: *anyopaque, + + /// The length of the memory map in bytes. + size: usize, + + /// The key for the current memory map. + key: Key, + + /// The size of each memory descriptor in the memory map. + descriptor_size: usize, + + /// The version of the memory map. + descriptor_version: u32, + + /// An iterator over the memory map. + pub const Iterator = struct { + map: *const MemoryMap, + + /// The current index of the iterator. + index: usize = 0, + + /// Returns the next memory descriptor in the map. + pub fn next(iter: *Iterator) ?*bits.MemoryDescriptor { + const offset = iter.index * iter.map.descriptor_size; + + // ensure the next descriptor is within the map + if (offset + iter.map.descriptor_size > iter.map.size) + return null; + + const addr = @intFromPtr(iter.map.map) + offset; + iter.index += 1; + + return @ptrFromInt(addr); + } + }; + + /// Creates a memory map from a list of memory descriptors. + pub fn initFromList(list: []bits.MemoryDescriptor) MemoryMap { + return .{ + .map = @ptrCast(list.ptr), + .size = list.len * @sizeOf(bits.MemoryDescriptor), + .key = 0, + .descriptor_size = @sizeOf(bits.MemoryDescriptor), + .descriptor_version = bits.MemoryDescriptor.revision, + }; + } + + /// Returns an iterator over the memory map. + pub fn iterator(self: *const MemoryMap) Iterator { + return Iterator{ .map = self }; + } + + /// Returns a pointer to the memory descriptor at the given index. + pub fn at(self: *const MemoryMap, index: usize) ?*bits.MemoryDescriptor { + const offset = index * self.descriptor_size; + + // ensure the descriptor is within the map + if (offset + self.descriptor_size > self.size) + return null; + + const addr = @intFromPtr(self.map) + offset; + return @ptrFromInt(addr); + } + + /// Returns the number of memory descriptors in the map. + pub fn size(self: *const MemoryMap) usize { + return self.size / self.descriptor_size; + } + }; + /// Returns the size of the current memory map. /// /// It is recommended to call this in a loop until it returns `null`. @@ -318,7 +568,7 @@ pub const BootServices = extern struct { pub fn allocatePool( self: *const BootServices, /// The type of pool to allocate. - pool_type: MemoryType, + pool_type: bits.MemoryDescriptor.Type, /// The number of bytes to allocate. size: usize, ) ![]align(8) u8 { @@ -341,6 +591,10 @@ pub const BootServices = extern struct { _ = self._freePool(buffer.ptr); } + pub const EfiInterfaceType = enum(u32) { + native, + }; + /// Installs a protocol interface on a device handle. If the handle does not exist, it is created and added to the /// list of handles in the system. /// @@ -350,14 +604,14 @@ pub const BootServices = extern struct { /// The handle to install the protocol interface on. handle: ?Handle, /// The GUID of the protocol. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, /// The type of interface to install. interface_type: EfiInterfaceType, /// The interface to install. Can only be `null` if `protocol` refers to a protocol that has no interface. interface: ?ProtocolInterface, ) !Handle { var new_handle: ?Handle = handle; - try self._installProtocolInterface(&new_handle, protocol, interface_type, interface).err(); + try self._installProtocolInterface(&new_handle, protocol_guid, interface_type, interface).err(); return new_handle; } @@ -371,11 +625,11 @@ pub const BootServices = extern struct { /// The handle to uninstall the protocol interface from. handle: Handle, /// The GUID of the protocol. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, /// The interface to uninstall. Can only be `null` if `protocol` refers to a protocol that has no interface. interface: ?ProtocolInterface, ) !void { - try self._uninstallProtocolInterface(handle, protocol, interface).err(); + try self._uninstallProtocolInterface(handle, protocol_guid, interface).err(); } /// Reinstalls a protocol interface on a device handle. @@ -386,46 +640,68 @@ pub const BootServices = extern struct { /// The handle to reinstall the protocol interface on. handle: Handle, /// The GUID of the protocol. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, /// The old interface to uninstall. Can only be `null` if `protocol` refers to a protocol that has no interface. old_interface: ?ProtocolInterface, /// The new interface to install. Can only be `null` if `protocol` refers to a protocol that has no interface. new_interface: ?ProtocolInterface, ) !void { - try self._reinstallProtocolInterface(handle, protocol, old_interface, new_interface).err(); + try self._reinstallProtocolInterface(handle, protocol_guid, old_interface, new_interface).err(); } /// Creates an event that is to be signaled whenever an interface is installed for a specified protocol. pub fn registerProtocolNotify( self: *const BootServices, /// The GUID of the protocol. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, /// The event to signal when the protocol is installed. event: Event, /// A pointer to a memory location to receive the registration value. THe value must be saved and used by the /// notification function to retrieve the list of handles that have added or removed the protocol. registration: **const anyopaque, ) !void { - try self._registerProtocolNotify(protocol, event, registration).err(); + try self._registerProtocolNotify(protocol_guid, event, registration).err(); } + /// The type of search to perform. + pub const LocateSearchType = union(Enum) { + pub const Enum = enum(u32) { + all, + by_notify, + by_protocol, + }; + + /// Returns all handles in the system + all: void, + + /// Returns the next handle that is new for the registration. Only one handle is returned at a time, starting + /// with the first and the caller must loop until no more handles are returned. + by_notify: *const anyopaque, + + /// Returns all handles that support this protocol. + by_protocol: *align(8) const Guid, + }; + /// Returns the size in bytes of the buffer that is required to hold the list of handles that support a specified /// protocol. + /// + /// Returns null if no handles match the search. pub fn locateHandleSize( self: *const BootServices, /// The type of search to perform. search_type: LocateSearchType, - ) !usize { + ) !?usize { var buffer_size: usize = 0; const status = switch (search_type) { .all => self._locateHandle(search_type, null, null, &buffer_size, null), .by_notify => |search_key| self._locateHandle(search_type, null, search_key, &buffer_size, null), - .by_protocol => |protocol| self._locateHandle(search_type, protocol, null, &buffer_size, null), + .by_protocol => |protocol_guid| self._locateHandle(search_type, protocol_guid, null, &buffer_size, null), }; switch (status) { .buffer_too_small => return buffer_size, + .not_found => return null, else => return status.err(), } } @@ -433,44 +709,82 @@ pub const BootServices = extern struct { /// Returns an array of handles that support a specified protocol. /// /// Use `locateHandleSize()` to determine the size of the buffer to allocate. + /// + /// Returns null if no handles match the search. pub fn locateHandle( self: *const BootServices, /// The type of search to perform. search_type: LocateSearchType, /// The buffer in which to return the array of handles. buffer: [*]u8, - ) ![]Handle { + ) !?[]Handle { var handle_buffer: [*]Handle = @ptrCast(buffer.ptr); var buffer_size: usize = buffer.len; - switch (search_type) { - .all => try self._locateHandle(search_type, null, null, &buffer_size, &handle_buffer).err(), + const status = switch (search_type) { + .all => self._locateHandle(search_type, null, null, &buffer_size, &handle_buffer), .by_notify => |search_key| self._locateHandle(search_type, null, search_key, &buffer_size, &handle_buffer), - .by_protocol => |protocol| self._locateHandle(search_type, protocol, null, &buffer_size, &handle_buffer), - } + .by_protocol => |protocol_guid| self._locateHandle(search_type, protocol_guid, null, &buffer_size, &handle_buffer), + }; - return buffer[0..@divExact(buffer_size, @sizeOf(Handle))]; + switch (status) { + .success => return handle_buffer[0..@divExact(buffer_size, @sizeOf(Handle))], + .not_found => return null, + else => return status.err(), + } } + /// A tuple of a located device path containing the handle to the device and the remaining device path. pub const LocatedDevicePath = struct { ?Handle, *const DevicePathProtocol }; /// Locates the handle to a device on the device path that supports the specified protocol. pub fn locateDevicePath( self: *const BootServices, /// The GUID of the protocol. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, /// The device path to search for. device_path: *const DevicePathProtocol, ) !LocatedDevicePath { var handle: ?Handle = null; var path: *const DevicePathProtocol = device_path; - switch (self._locateDevicePath(protocol, &handle, &path)) { + switch (self._locateDevicePath(protocol_guid, &handle, &path)) { .success => return .{ handle, path }, .not_found => return .{ null, path }, else => |status| return status.err(), } } + pub const OpenProtocolAttributes = packed struct(u32) { + /// Query whether a protocol interface is supported on a handle. If yes, then the protocol interface is returned. + by_handle_protocol: bool = false, + + /// Used by a driver to get a protocol interface from a handle. Care must be taken when using this mode because the + /// driver that opens the protocol interface in this manner will not be informed if the protocol interface is + /// uninstalled or reinstalled. The caller is also not required to close the protocol interface. + get_protocol: bool = false, + + /// Used by a driver to test for the existence of a protocol interface on a handle. The returned interface will always + /// be `null`. This mode can be used to determine if a driver is present in the handle's driver stack. + test_protocol: bool = false, + + /// Used by bus drivers to show that a protocol interface is being used by one of the child controllers of the bus. + /// This information is used by `connectController` to recursively connect all child controllers and by + /// `disconnectController` to get the list of child controllers that a bus driver created. + by_child_controller: bool = false, + + /// Used by a driver to gain access to a protocol interface. When this mode is used, the driver's `stop()` function + /// will be called by `disconnectController` if the protocol interface is reinstalled or uninstalled. Once a + /// protocol interface is opened by a driver with this attribute, no other drivers can open that protocol interface + /// with this attribute. + by_driver: bool = false, + + /// Used to gain exclusive access to a protocol interface. If any drivers have the protocol interface opened with + /// the `by_driver` attribute, then an attempt will be made to remove them by calling the driver's `stop()` function. + exclusive: bool = false, + + reserved: u26 = 0, + }; + /// Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the handle, /// it opens the protocol on behalf of the calling agent. pub fn openProtocol( @@ -478,7 +792,7 @@ pub const BootServices = extern struct { /// The handle for the protocol interface that is being opened. handle: Handle, /// The GUID of the protocol to open. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, /// The handle of the agent that is opening the protocol interface specified by `protocol`. agent_handle: ?Handle, /// The handle of the controller that requires the protocol interface. @@ -487,7 +801,7 @@ pub const BootServices = extern struct { attributes: OpenProtocolAttributes, ) !?ProtocolInterface { var interface: ?ProtocolInterface = undefined; - try self._openProtocol(handle, protocol, &interface, agent_handle, controller_handle, attributes).err(); + try self._openProtocol(handle, protocol_guid, &interface, agent_handle, controller_handle, attributes).err(); return interface; } @@ -497,16 +811,23 @@ pub const BootServices = extern struct { /// The handle for the protocol interface that was previously opened with `openProtocol()`. handle: Handle, /// The GUID of the protocol to close. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, /// The handle of the agent that is closing the protocol interface specified by `protocol`. agent_handle: Handle, /// The handle of the controller that required the protocol interface. controller_handle: ?Handle, ) void { // any error here arises from user error (ie. closing a protocol that was not supported) or a firmware bug - _ = self._closeProtocol(handle, protocol, agent_handle, controller_handle); + _ = self._closeProtocol(handle, protocol_guid, agent_handle, controller_handle); } + pub const ProtocolInformationEntry = extern struct { + agent_handle: ?Handle, + controller_handle: ?Handle, + attributes: OpenProtocolAttributes, + open_count: u32, + }; + /// Retrieves the list of agents that currently have a protocol interface opened in a buffer allocated from the pool. /// Returns `null` if the handle does not support the requested protocol. /// @@ -516,11 +837,11 @@ pub const BootServices = extern struct { /// The handle for the protocol interface that is being queried. handle: Handle, /// The GUID of the protocol to list. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, ) !?[]const ProtocolInformationEntry { var entry_buffer: [*]const ProtocolInformationEntry = undefined; var entry_count: usize = 0; - switch (self._openProtocolInformation(handle, protocol, &entry_buffer, &entry_count)) { + switch (self._openProtocolInformation(handle, protocol_guid, &entry_buffer, &entry_count)) { .success => return entry_buffer[0..entry_count], .not_found => return null, else => |status| return status.err(), @@ -589,7 +910,7 @@ pub const BootServices = extern struct { switch (search_type) { .all => try self._locateHandleBuffer(search_type, null, null, &num_handles, &handle_buffer).err(), .by_notify => |search_key| self._locateHandleBuffer(search_type, null, search_key, &num_handles, &handle_buffer), - .by_protocol => |protocol| self._locateHandleBuffer(search_type, protocol, null, &num_handles, &handle_buffer), + .by_protocol => |protocol_guid| self._locateHandleBuffer(search_type, protocol_guid, null, &num_handles, &handle_buffer), } return handle_buffer[0..num_handles]; @@ -599,12 +920,12 @@ pub const BootServices = extern struct { pub fn locateProtocol( self: *const BootServices, /// The GUID of the protocol. - protocol: *align(8) const Guid, + protocol_guid: *align(8) const Guid, /// An optional registration key returned from `registerProtocolNotify()`. registration: ?*const anyopaque, ) !?ProtocolInterface { var interface: ?ProtocolInterface = undefined; - switch (self._locateProtocol(protocol, registration, &interface)) { + switch (self._locateProtocol(protocol_guid, registration, &interface)) { .success => return interface, .not_found => return null, else => |status| return status.err(), @@ -701,12 +1022,14 @@ pub const BootServices = extern struct { image_handle: Handle, /// The image's exit status. exit_status: Status, - /// The size of the exit data. - exit_data_size: usize, /// The exit data. This must begin with a null terminated UCS2 string. - exit_data: ?[*]align(2) const u8, + exit_data: ?[]align(2) const u8, ) !void { - try self._exit(image_handle, exit_status, exit_data_size, exit_data).err(); + if (exit_data) |data| { + try self._exit(image_handle, exit_status, data.len, data.ptr).err(); + } else { + try self._exit(image_handle, exit_status, 0, null).err(); + } } /// Terminates all boot services. @@ -727,12 +1050,14 @@ pub const BootServices = extern struct { timeout: usize, /// The numeric code to log on timeout. watchdog_code: u64, - /// The size of the watchdog timer's data. - watchdog_data_size: usize, /// A data buffer pointing to a null terminated UCS2 string. Optionally followed by a blob of binary data. - watchdog_data: ?[*]const u8, + watchdog_data: ?[]align(2) const u8, ) !void { - try self._setWatchdogTimer(timeout, watchdog_code, watchdog_data_size, watchdog_data).err(); + if (watchdog_data) |data| { + try self._setWatchdogTimer(timeout, watchdog_code, data.len, data.ptr).err(); + } else { + try self._setWatchdogTimer(timeout, watchdog_code, 0, null).err(); + } } /// Induces a fine-grained stall. @@ -759,365 +1084,10 @@ pub const BootServices = extern struct { /// The GUID of the configuration table. guid: *align(8) const Guid, /// A pointer to the configuration table. - table: ?*const anyopaque, + config_table: ?*const anyopaque, ) !void { - try self._installConfigurationTable(guid, table).err(); - } - - /// Opens a protocol with a structure as the loaded image for a UEFI application - pub fn openProtocolSt(self: *const BootServices, comptime protocol: type, handle: Handle) !*protocol { - if (!@hasDecl(protocol, "guid")) - @compileError("Protocol is missing guid!"); - - var ptr: ?*protocol = undefined; - - try self.openProtocol( - handle, - &protocol.guid, - @as(*?*anyopaque, @ptrCast(&ptr)), - // Invoking handle (loaded image) - uefi.handle, - // Control handle (null as not a driver) - null, - uefi.tables.OpenProtocolAttributes{ .by_handle_protocol = true }, - ).err(); - - return ptr.?; + try self._installConfigurationTable(guid, config_table).err(); } pub const signature: u64 = 0x56524553544f4f42; -}; -<<<<<<< HEAD -======= - -/// These types can be "ORed" together as needed. -pub const EventKind = struct { - /// The event is a timer event and may be used in a call to `setTimer()`. Note that timers only function during boot - /// services time. - pub const timer: u32 = 0x80000000; - - /// The event is allocated from runtime memory. If an event is to be signaled after the call to `exitBootServices()` - /// the event’s data structure and notification function need to be allocated from runtime memory. - pub const runtime: u32 = 0x40000000; - - /// If an event of this type is not already in the signaled state, then the event’s *notify_func* will be - /// queued at the event’s *notify_tpl* whenever the event is being waited on via `waitForEvent()` or `checkEvent()`. - pub const notify_wait: u32 = 0x00000100; - - /// The event’s *notify_func* is queued whenever the event is signaled. - pub const notify_signal: u32 = 0x00000200; - - /// This event is of type `notify_signal`. It should not be combined with any other event types. This event type - /// is functionally equivalent to the `EventGroup.exit_boot_services` event group. - pub const signal_exit_boot_services: u32 = 0x00000201; - - /// This event is of type `notify_signal`. It should not be combined with any other event types. The event is to be - /// notified by the system when `setVirtualAddressMap()` is performed. - pub const signal_virtual_address_change: u32 = 0x60000202; -}; - -pub const EventGroup = struct {}; - -pub const TaskPriorityLevel = enum(usize) { - /// This is the lowest priority level. It is the level of execution which occurs when no event notifications are - /// pending and which interacts with the user. User I/O (and blocking on User I/O) can be performed at this level. - /// The boot manager executes at this level and passes control to other UEFI applications at this level. - application = 4, - - /// Interrupts code executing below TPL_CALLBACK level. Long term operations (such as file system operations and - /// disk I/O) can occur at this level - callback = 8, - - /// Interrupts code executing below TPL_NOTIFY level. Blocking is not allowed at this level. Code executes to - /// completion and returns. If code requires more processing, it needs to signal an event to wait to obtain control - /// again at whatever level it requires. This level is typically used to process low level IO to or from a device. - notify = 16, - - /// Interrupts code executing below TPL_HIGH_LEVEL This is the highest priority level. It is not interruptible - /// (interrupts are disabled) and is used sparingly by firmware to synchronize operations that need to be accessible - /// from any priority level. For example, it must be possible to signal events while executing at any priority level. - /// Therefore, firmware manipulates the internal event structure while at this priority level. - high_level = 31, -}; - -pub const EventNotify = *const fn (event: Event, ctx: *anyopaque) callconv(cc) void; -pub const EventNotifyContext = *const anyopaque; - -pub const TimerKind = union(Enum) { - pub const Enum = enum(u32) { - /// The timer is canceled. - cancel = 0, - - /// The timer is a periodic timer. - periodic = 1, - - /// The timer is a relative timer. - relative = 2, - }; - - /// The timer is to be canceled. - cancel: void, - - /// The timer is to be signaled periodically at `trigger_time` ns intervals. The event timer does not need to be - /// reset for each notification. - periodic: u64, - - /// The timer is to be signaled after `trigger_time` ns. - relative: u64, -}; - -pub const MemoryMap = struct { - pub const Key = enum(usize) { _ }; - - map: *const anyopaque, - - /// The length of the memory map in bytes. - size: usize, - - /// The key for the current memory map. - key: Key, - - /// The size of each memory descriptor in the memory map. - descriptor_size: usize, - - /// The version of the memory map. - descriptor_version: u32, - - /// An iterator over the memory map. - pub const Iterator = struct { - map: *const MemoryMap, - - /// The current index of the iterator. - index: usize = 0, - - /// Returns the next memory descriptor in the map. - pub fn next(iter: *Iterator) ?*const MemoryDescriptor { - const offset = iter.index * iter.map.descriptor_size; - - // ensure the next descriptor is within the map - if (offset + iter.map.descriptor_size > iter.map.size) - return null; - - const addr = @intFromPtr(iter.map.map) + offset; - iter.index += 1; - - return @ptrFromInt(addr); - } - }; - - /// Returns an iterator over the memory map. - pub fn iterator(self: *const MemoryMap) Iterator { - return Iterator{ .map = self }; - } -}; - -pub const MemoryType = enum(u32) { - /// Not usable. - reserved, - - /// The code portions of a loaded application. - loader_code, - - /// The data portions of a loaded application and the default data allocation type used by an application to - /// allocate pool memory. - loader_data, - - /// The code portions of a loaded Boot Services Driver. - boot_services_code, - - /// The data portions of a loaded Boot Services Driver, and the default data allocation type used by a Boot - /// Services Driver to allocate pool memory. - boot_services_data, - - /// The code portions of a loaded Runtime Services Driver. - runtime_services_code, - - /// The data portions of a loaded Runtime Services Driver and the default data allocation type used by a Runtime - /// Services Driver to allocate pool memory. - runtime_services_data, - - /// Free (unallocated) memory. - conventional, - - /// Memory in which errors have been detected. - unusable, - - /// Memory that holds the ACPI tables. - acpi_reclaim, - - /// Address space reserved for use by the firmware. - acpi_nvs, - - /// Used by system firmware to request that a memory-mapped IO region be mapped by the OS to a virtual address so - /// it can be accessed by EFI runtime services. - memory_mapped_io, - - /// System memory-mapped IO region that is used to translate memory cycles to IO cycles by the processor. - memory_mapped_io_port_space, - - /// Address space reserved by the firmware for code that is part of the processor. - pal_code, - - /// A memory region that operates as `conventional`, but additionally supports byte-addressable non-volatility. - persistent, - - /// A memory region that represents unaccepted memory that must be accepted by the boot target before it can be used. - /// For platforms that support unaccepted memory, all unaccepted valid memory will be reported in the memory map. - /// Unreported memory addresses must be treated as non-present memory. - unaccepted, - - _, -}; - -pub const MemoryDescriptorAttribute = packed struct(u64) { - /// The memory region supports being configured as not cacheable. - non_cacheable: bool, - - /// The memory region supports being configured as write combining. - write_combining: bool, - - /// The memory region supports being configured as cacheable with a "write-through". Writes that hit in the cache - /// will also be written to main memory. - write_through: bool, - - /// The memory region supports being configured as cacheable with a "write-back". Reads and writes that hit in the - /// cache do not propagate to main memory. Dirty data is written back to main memory when a new cache line is - /// allocated. - write_back: bool, - - /// The memory region supports being configured as not cacheable, exported, and supports the "fetch and add" - /// semaphore mechanism. - non_cacheable_exported: bool, - - _pad1: u7 = 0, - - /// The memory region supports being configured as write-protected by system hardware. This is typically used as a - /// cacheability attribute today. The memory region supports being configured as cacheable with a "write protected" - /// policy. Reads come from cache lines when possible, and read misses cause cache fills. Writes are propagated to - /// the system bus and cause corresponding cache lines on all processors to be invalidated. - write_protect: bool, - - /// The memory region supports being configured as read-protected by system hardware. - read_protect: bool, - - /// The memory region supports being configured so it is protected by system hardware from executing code. - execute_protect: bool, - - /// The memory region refers to persistent memory. - non_volatile: bool, - - /// The memory region provides higher reliability relative to other memory in the system. If all memory has the same - /// reliability, then this bit is not used. - more_reliable: bool, - - /// The memory region supports making this memory range read-only by system hardware. - read_only: bool, - - /// The memory region is earmarked for specific purposes such as for specific device drivers or applications. This - /// attribute serves as a hint to the OS to avoid allocation this memory for core OS data or code that cannot be - /// relocated. Prolonged use of this memory for purposes other than the intended purpose may result in suboptimal - /// platform performance. - specific_purpose: bool, - - /// The memory region is capable of being protected with the CPU's memory cryptographic capabilities. - cpu_crypto: bool, - - _pad2: u24 = 0, - - /// When `memory_isa_valid` is set, this field contains ISA specific cacheability attributes not covered above. - memory_isa: u16, - - _pad3: u2 = 0, - - /// If set, then `memory_isa` is valid. - memory_isa_valid: bool, - - /// This memory must be given a virtual mapping by the operating system when `setVirtualAddressMap()` is called. - memory_runtime: bool, -}; - -pub const MemoryDescriptor = extern struct { - type: MemoryType, - physical_start: PhysicalAddress, - virtual_start: VirtualAddress, - number_of_pages: u64, - attribute: MemoryDescriptorAttribute, -}; - -pub const LocateSearchType = union(Enum) { - pub const Enum = enum(u32) { - all, - by_notify, - by_protocol, - }; - - all: void, - by_notify: *const anyopaque, - by_protocol: *align(8) const Guid, -}; - -pub const OpenProtocolAttributes = packed struct(u32) { - /// Query whether a protocol interface is supported on a handle. If yes, then the protocol interface is returned. - by_handle_protocol: bool = false, - - /// Used by a driver to get a protocol interface from a handle. Care must be taken when using this mode because the - /// driver that opens the protocol interface in this manner will not be informed if the protocol interface is - /// uninstalled or reinstalled. The caller is also not required to close the protocol interface. - get_protocol: bool = false, - - /// Used by a driver to test for the existence of a protocol interface on a handle. The returned interface will always - /// be `null`. This mode can be used to determine if a driver is present in the handle's driver stack. - test_protocol: bool = false, - - /// Used by bus drivers to show that a protocol interface is being used by one of the child controllers of the bus. - /// This information is used by `connectController` to recursively connect all child controllers and by - /// `disconnectController` to get the list of child controllers that a bus driver created. - by_child_controller: bool = false, - - /// Used by a driver to gain access to a protocol interface. When this mode is used, the driver's `stop()` function - /// will be called by `disconnectController` if the protocol interface is reinstalled or uninstalled. Once a - /// protocol interface is opened by a driver with this attribute, no other drivers can open that protocol interface - /// with this attribute. - by_driver: bool = false, - - /// Used to gain exclusive access to a protocol interface. If any drivers have the protocol interface opened with - /// the `by_driver` attribute, then an attempt will be made to remove them by calling the driver's `stop()` function. - exclusive: bool = false, - - reserved: u26 = 0, -}; - -pub const ProtocolInformationEntry = extern struct { - agent_handle: ?Handle, - controller_handle: ?Handle, - attributes: OpenProtocolAttributes, - open_count: u32, -}; - -pub const EfiInterfaceType = enum(u32) { - native, -}; - -pub const AllocateType = union(Enum) { - pub const Enum = enum(u32) { - /// Allocate any available range of pages that satisfies the request. - any = 0, - - /// Allocate any available range of pages whose uppermost address is less than or equal to a specified - /// address. - max_address = 1, - - /// Allocate pages at a specified address. - at_address = 2, - }; - - /// Allocate any available range of pages that satisfies the request. - any: void, - - /// Allocate any available range of pages whose uppermost address is less than or equal to a specified address. - max_address: PhysicalAddress, - - /// Allocate pages at a specified address. - at_address: PhysicalAddress, -}; ->>>>>>> 908d1f3d3f (std.os.uefi: add zig-like bindings for boot services) +}; \ No newline at end of file diff --git a/lib/std/os/uefi/table/configuration.zig b/lib/std/os/uefi/table/configuration.zig new file mode 100644 index 000000000000..fd76d4243558 --- /dev/null +++ b/lib/std/os/uefi/table/configuration.zig @@ -0,0 +1,199 @@ +const bits = @import("../bits.zig"); + +const Guid = bits.Guid; + +pub const ConfigurationTable = extern struct { + vendor_guid: Guid, + vendor_table: *const anyopaque, + + pub const acpi_20_table align(8) = Guid{ + .time_low = 0x8868e871, + .time_mid = 0xe4f1, + .time_high_and_version = 0x11d3, + .clock_seq_high_and_reserved = 0xbc, + .clock_seq_low = 0x22, + .node = [_]u8{ 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81 }, + }; + + pub const acpi_10_table align(8) = Guid{ + .time_low = 0xeb9d2d30, + .time_mid = 0x2d88, + .time_high_and_version = 0x11d3, + .clock_seq_high_and_reserved = 0x9a, + .clock_seq_low = 0x16, + .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, + }; + + pub const sal_system_table align(8) = Guid{ + .time_low = 0xeb9d2d32, + .time_mid = 0x2d88, + .time_high_and_version = 0x113d, + .clock_seq_high_and_reserved = 0x9a, + .clock_seq_low = 0x16, + .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, + }; + + pub const smbios_table align(8) = Guid{ + .time_low = 0xeb9d2d31, + .time_mid = 0x2d88, + .time_high_and_version = 0x11d3, + .clock_seq_high_and_reserved = 0x9a, + .clock_seq_low = 0x16, + .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, + }; + + pub const smbios3_table align(8) = Guid{ + .time_low = 0xf2fd1544, + .time_mid = 0x9794, + .time_high_and_version = 0x4a2c, + .clock_seq_high_and_reserved = 0x99, + .clock_seq_low = 0x2e, + .node = [_]u8{ 0xe5, 0xbb, 0xcf, 0x20, 0xe3, 0x94 }, + }; + + pub const mps_table align(8) = Guid{ + .time_low = 0xeb9d2d2f, + .time_mid = 0x2d88, + .time_high_and_version = 0x11d3, + .clock_seq_high_and_reserved = 0x9a, + .clock_seq_low = 0x16, + .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, + }; + + pub const json_config_data_table align(8) = Guid{ + .time_low = 0x87367f87, + .time_mid = 0x1119, + .time_high_and_version = 0x41ce, + .clock_seq_high_and_reserved = 0xaa, + .clock_seq_low = 0xec, + .node = [_]u8{ 0x8b, 0xe0, 0x11, 0x1f, 0x55, 0x8a }, + }; + + pub const json_capsule_data_table align(8) = Guid{ + .time_low = 0x35e7a725, + .time_mid = 0x8dd2, + .time_high_and_version = 0x4cac, + .clock_seq_high_and_reserved = 0x80, + .clock_seq_low = 0x11, + .node = [_]u8{ 0x33, 0xcd, 0xa8, 0x10, 0x90, 0x56 }, + }; + + pub const json_capsule_result_table align(8) = Guid{ + .time_low = 0xdbc461c3, + .time_mid = 0xb3de, + .time_high_and_version = 0x422a, + .clock_seq_high_and_reserved = 0xb9, + .clock_seq_low = 0xb4, + .node = [_]u8{ 0x98, 0x86, 0xfd, 0x49, 0xa1, 0xe5 }, + }; +}; + +/// This table should be published by a platform if it no longer supports all EFI runtime services once +/// `exitBootServices()` has been called by the OS. Note that this is merely a hint to the OS, which it is free to +/// ignore, as any unsupported runtime services will return `error.Unsupported` if called. +pub const RtPropertiesTable = extern struct { + pub const Supported = packed struct(u32) { + get_time: bool, + set_time: bool, + get_wakeup_time: bool, + set_wakeup_time: bool, + get_variable: bool, + get_next_variable_name: bool, + set_variable: bool, + set_virtual_address_map: bool, + convert_pointer: bool, + get_next_high_monotonic_count: bool, + reset_system: bool, + update_capsule: bool, + query_capsule_capabilities: bool, + query_variable_info: bool, + + _pad1: u18 = 0, + }; + + /// Version of the table. Must be `1`. + version: u16 = 0x1, + + /// Length of the table, in bytes. Must be `8`. + length: u16 = 8, + + /// Bitmask of supported runtime services. + supported: Supported, + + pub const guid align(8) = Guid{ + .time_low = 0xeb66918a, + .time_mid = 0x7eef, + .time_high_and_version = 0x402a, + .clock_seq_high_and_reserved = 0x84, + .clock_seq_low = 0x2e, + .node = [_]u8{ 0x93, 0x1d, 0x21, 0xc3, 0x8a, 0xe9 }, + }; +}; + +/// When published by the firmware, this table provides additional information about regions within the run-time memory +/// blocks defined in `MemoryDescriptor` entries. +pub const MemoryAttributesTable = extern struct { + pub const Flags = packed struct(u32) { + /// Implies that runtime code includes the forward control flow guard instruction. + rt_forward_cfg: bool, + + _pad1: u31 = 0, + }; + + /// Version of the table. Must be `2`. + version: u32 = 0x2, + + /// The number of `MemoryDescriptor`s following this table. + entries: u32, + + /// The size of each `MemoryDescriptor` in bytes. + descriptor_size: u32, + + flags: Flags, + + /// An iterator over the memory descriptors. + pub const Iterator = struct { + table: *const MemoryAttributesTable, + + /// The current index of the iterator. + index: usize = 0, + + /// Returns the next memory descriptor in the table. + pub fn next(iter: *Iterator) ?*bits.MemoryDescriptor { + if (iter.index >= iter.table.entries) + return null; + + const offset = iter.index * iter.table.descriptor_size; + + const addr = @intFromPtr(iter.table) + @sizeOf(MemoryAttributesTable) + offset; + iter.index += 1; + + return @ptrFromInt(addr); + } + }; + + /// Returns an iterator over the memory map. + pub fn iterator(self: *const MemoryAttributesTable) Iterator { + return Iterator{ .table = self }; + } + + /// Returns a pointer to the memory descriptor at the given index. + pub fn at(self: *const MemoryAttributesTable, index: usize) ?*bits.MemoryDescriptor { + if (index >= self.entries) + return null; + + const offset = index * self.descriptor_size; + + const addr = @intFromPtr(self.table) + @sizeOf(self) + offset; + return @ptrFromInt(addr); + } + + pub const guid align(8) = Guid{ + .time_low = 0xdcfa911d, + .time_mid = 0x26eb, + .time_high_and_version = 0x469f, + .clock_seq_high_and_reserved = 0xa2, + .clock_seq_low = 0x20, + .node = [_]u8{ 0x38, 0xb7, 0xdc, 0x46, 0x12, 0x20 }, + }; +}; diff --git a/lib/std/os/uefi/tables/header.zig b/lib/std/os/uefi/table/header.zig similarity index 52% rename from lib/std/os/uefi/tables/header.zig rename to lib/std/os/uefi/table/header.zig index 018fa11759ce..8a93933c6597 100644 --- a/lib/std/os/uefi/tables/header.zig +++ b/lib/std/os/uefi/table/header.zig @@ -1,7 +1,11 @@ -const uefi = @import("../../uefi.zig"); +const bits = @import("../bits.zig"); -pub const TableHeader = extern struct { +pub const Header = extern struct { signature: u64, + + /// The revision of the EFI Specification to which this table conforms. + /// + /// Encoded as `(major << 16) | (minor * 10) | (patch)`. revision: u32, /// The size, in bytes, of the entire table including the TableHeader @@ -9,20 +13,22 @@ pub const TableHeader = extern struct { crc32: u32, reserved: u32, - pub fn validate(self: *const TableHeader, signature: u64) bool { + /// Validates the table by ensuring that the signature and CRC fields are correct. + pub fn validate(self: *const Header, signature: u64) bool { if (self.reserved != 0) return false; if (self.signature != signature) return false; const byte_ptr: [*]const u8 = @ptrCast(self); - var crc = uefi.Crc32.init(); + var crc = bits.Crc32.init(); crc.update(byte_ptr[0..16]); crc.update(&.{ 0, 0, 0, 0 }); // crc32 field replaced with 0 crc.update(byte_ptr[20..self.header_size]); return crc.finish() == self.crc32; } - pub fn isAtLeastRevision(self: *const TableHeader, major: u16, minor: u16) bool { + /// Checks if the table is at least the specified revision. + pub fn isAtLeastRevision(self: *const Header, major: u16, minor: u16) bool { return self.revision >= (@as(u32, major) << 16) | minor; } }; diff --git a/lib/std/os/uefi/table/runtime_services.zig b/lib/std/os/uefi/table/runtime_services.zig new file mode 100644 index 000000000000..c7619bf0964f --- /dev/null +++ b/lib/std/os/uefi/table/runtime_services.zig @@ -0,0 +1,412 @@ +const std = @import("../../../std.zig"); + +const bits = @import("../bits.zig"); +const table = @import("../table.zig"); + +const cc = bits.cc; +const Guid = bits.Guid; +const Status = @import("../status.zig").Status; + +const MemoryMap = table.BootServices.MemoryMap; + +/// Runtime services are provided by the firmware before and after exitBootServices has been called. +/// +/// As the runtime_services table may grow with new UEFI versions, it is important to check hdr.header_size. +/// +/// Some functions may not be supported. Check the RuntimeServicesSupported variable using getVariable. +/// getVariable is one of the functions that may not be supported. +/// +/// Some functions may not be called while other functions are running. +pub const RuntimeServices = extern struct { + hdr: table.Header, + + _getTime: *const fn (time: *bits.Time, capabilities: ?*bits.TimeCapabilities) callconv(cc) Status, + _setTime: *const fn (time: *bits.Time) callconv(cc) Status, + _getWakeupTime: *const fn (enabled: *bool, pending: *bool, time: *bits.Time) callconv(cc) Status, + _setWakeupTime: *const fn (enable: bool, time: ?*const bits.Time) callconv(cc) Status, + + _setVirtualAddressMap: *const fn (mmap_size: usize, descriptor_size: usize, descriptor_version: u32, virtual_map: *const anyopaque) callconv(cc) Status, + _convertPointer: *const fn (debug_disposition: usize, address: *?*anyopaque) callconv(cc) Status, + + _getVariable: *const fn (var_name: [*:0]const u16, vendor_guid: *align(8) const Guid, attributes: ?*u32, data_size: *usize, data: ?[*]u8) callconv(cc) Status, + _getNextVariableName: *const fn (var_name_size: *usize, var_name: [*:0]u16, vendor_guid: *align(8) Guid) callconv(cc) Status, + _setVariable: *const fn (var_name: [*:0]const u16, vendor_guid: *align(8) const Guid, attributes: u32, data_size: usize, data: *anyopaque) callconv(cc) Status, + + _getNextHighMonotonicCount: *const fn (high_count: *u32) callconv(cc) Status, + _resetSystem: *const fn (reset_type: ResetType, reset_status: Status, data_size: usize, reset_data: ?*const anyopaque) callconv(cc) noreturn, + + _updateCapsule: *const fn (capsule_header_array: [*]*CapsuleHeader, capsule_count: usize, scatter_gather_list: bits.PhysicalAddress) callconv(cc) Status, + _queryCapsuleCapabilities: *const fn (capsule_header_array: [*]*CapsuleHeader, capsule_count: usize, maximum_capsule_size: *usize, resetType: *ResetType) callconv(cc) Status, + + _queryVariableInfo: *const fn (attributes: *u32, maximum_variable_storage_size: *u64, remaining_variable_storage_size: *u64, maximum_variable_size: *u64) callconv(cc) Status, + + /// A tuple of EFI variable data and its attributes. + pub const Variable = struct { []u8, VariableAttributes }; + + /// Returns the size required to hold the value of a variable. + pub fn getVariableSize( + self: *const RuntimeServices, + /// A null terminated string that is the name of the vendor's variable. + variable_name: [*:0]const u16, + /// A unique identifier for the vendor. + vendor_guid: *align(8) const Guid, + ) !?usize { + var data_size: usize = 0; + + switch (self._getVariable(variable_name, vendor_guid, null, &data_size, null)) { + .success => return data_size, + .not_found => return null, + else => |status| return status.err(), + } + } + + /// Returns the value of a variable. + /// + /// Use `getVariableSize()` to determine the size of the buffer to allocate. + pub fn getVariable( + self: *const RuntimeServices, + /// A null terminated string that is the name of the vendor's variable. + variable_name: [*:0]const u16, + /// A unique identifier for the vendor. + vendor_guid: *align(8) const Guid, + ) !?Variable { + var attributes: u32 = 0; + var data_size: usize = 0; + var data: ?[*]u8 = null; + + switch (self._getVariable(variable_name, vendor_guid, &attributes, &data_size, data)) { + .success => return .{ data[0..data_size], attributes }, + .not_found => return null, + else => |status| return status.err(), + } + } + + /// Enumerates the current variable names. + /// + /// Returns `false` if there are no more variables. + /// + /// You must handle `error.BufferTooSmall` by calling `getNextVariableName()` again with a buffer fit to hold + /// the modified value stored in `variable_name_size` in bytes. + pub fn getNextVariableName( + self: *const RuntimeServices, + /// The size of the buffer in bytes that `variable_name` points to. + variable_name_size: *usize, + /// On input, supplies the last variable name that was returned by `getNextVariableName()`. + /// On output, returns the name of the next variable. + variable_name: [*:0]u16, + /// On input, supplies the last vendor GUID that was returned by `getNextVariableName()`. + /// On output, returns the GUID of the next vendor. + vendor_guid: *align(8) Guid, + ) !bool { + switch (self._getNextVariableName(variable_name_size, variable_name, vendor_guid)) { + .success => return true, + .not_found => return false, + else => |status| return status.err(), + } + } + + /// The attributes of a EFI variable. + pub const VariableAttributes = packed struct(u32) { + /// Variable is stored in fixed hardware that has a limited storage capacity; sometimes a severly limited capacity. + /// This should only be used when absolutely necessary. In addition, if software uses a nonvolatile variable it should + /// use a variable that is only accessible at boot services time if possible. + non_volatile: bool, + + /// Variable is visible during boot services time. Must be set when `runtime_access` is set. + boot_service_access: bool, + + /// Variable is visible during runtime services time. After exiting boot services, the contents of this variable + /// remain available. + runtime_access: bool, + + /// Variable has defined meaning as a hardware error record. + hardware_error_record: bool, + + /// Previously "authenticated_write_access", now deprecated and reserved. + reserved: u1, + + /// Variable uses the EFI_VARIABLE_AUTHENTICATION_2 structure. Mutually exclusive with `enhanced_authenticated_access`. + time_based_authenticated_write_access: bool, + + /// Variable writes are appended instead of overwriting. + append_write: bool, + + /// The variable payload begins with a EFI_VARIABLE_AUTHENTICATION_3 structure, and potentially more structures as + /// indicated by fields of this structure. Mutually exclusive with `time_based_authenticated_write_access`. + enhanced_authenticated_access: bool, + + _pad1: u24, + }; + + /// Sets, modifies or deletes the value of a variable. + pub fn setVariable( + self: *const RuntimeServices, + /// A null terminated string that is the name of the vendor's variable. + variable_name: [*:0]const u16, + /// A unique identifier for the vendor. + vendor_guid: *align(8) const Guid, + /// The attributes to set for the variable. + attributes: VariableAttributes, + /// The data to set. + data: []u8, + ) !void { + switch (self._setVariable(variable_name, vendor_guid, attributes, data.len, data.ptr)) { + .success, .not_found => return, + else => |status| return status.err(), + } + } + + /// Returns the current time and date information. + pub fn getTime( + self: *const RuntimeServices, + ) !bits.Time { + var time: bits.Time = undefined; + switch (self._getTime(&time, null)) { + .success => return time, + else => |status| return status.err(), + } + } + + /// Returns the time-keeping capabilities of the hardware platform. + pub fn getTimeCapabilities( + self: *const RuntimeServices, + ) !bits.TimeCapabilities { + var time: bits.Time = undefined; + var capabilities: bits.TimeCapabilities = undefined; + switch (self._getTime(&time, &capabilities)) { + .success => return capabilities, + else => |status| return status.err(), + } + } + + /// The state of the wakeup alarm clock. + pub const Alarm = struct { + /// Indicates if the alarm is enabled or disabled. + enabled: bool, + + /// Indicates if the alarm signal is pending and requires acknowledgement. + pending: bool, + + /// The current alarm setting. Resolution is defined to be one second. + time: bits.Time, + }; + + /// Returns the current wakeup alarm clock setting. + pub fn getWakeupTime( + self: *const RuntimeServices, + ) !Alarm { + var enabled: bool = false; + var pending: bool = false; + var time: bits.Time = undefined; + switch (self._getWakeupTime(&enabled, &pending, &time)) { + .success => return .{ .enabled = enabled, .pending = pending, .time = time }, + else => |status| return status.err(), + } + } + + /// Sets the current wakeup alarm clock setting. + pub fn setWakeupTime( + self: *const RuntimeServices, + /// The new alarm setting. Resolution is defined to be one second. + /// If `null`, the alarm is disabled. + time: ?bits.Time, + ) !void { + const enabled: bool = time != null; + switch (self._setWakeupTime(enabled, if (time) |*t| &t else null)) { + .success => return, + else => |status| return status.err(), + } + } + + /// Changes the runtime addresssing mode of EFI firmware from physical to virtual. + /// + /// This can only be called at runtime (ie. after `exitBootServices()` has been called). + pub fn setVirtualAddressMap( + self: *const RuntimeServices, + /// The memory map of the address space. + map: MemoryMap, + ) !void { + switch (self._setVirtualAddressMap(map.size, map.descriptor_size, map.descriptor_version, map.map)) { + .success => return, + else => |status| return status.err(), + } + } + + /// Converts a pointer from physical to virtual addressing mode. + pub fn convertPointer( + self: *const RuntimeServices, + /// The pointer to convert. + pointer: anytype, + ) !@TypeOf(pointer) { + const T = @TypeOf(pointer); + const info = @typeInfo(T); + + if (info != .Pointer and (info != .Optional or @typeInfo(info.Optional.child) != .Pointer)) { + @compileError("convertPointer() cannot convert non-pointer type " ++ @typeName(T)); + } + + // If the pointer is nullable, we need to set `EFI_OPTIONAL_PTR`. + const disposition: usize = if (info == .Optional) 0x1 else 0x0; + + // This pointer needs to be the least qualified form because that's how we need to pass it to UEFI. + // This is completely safe, because UEFI never dereferences the pointer. + var address: ?*anyopaque = @volatileCast(@constCast(@ptrCast(pointer))); + + switch (self._convertPointer(disposition, &address)) { + // Requalify the pointer and cast it back to the type we originally had. + .success => return @alignCast(@ptrCast(address)), + else => |status| return status.err(), + } + } + + pub const ResetType = enum(u32) { + /// Sets all circuitry in within the system to its initial state. This is tantamount to a system power cycle. + cold, + + /// The processors are set to their initial state and pending cycles are not corrupted. + /// + /// If this is not supported, a `cold` reset must be used. + warm, + + /// Causes the system to enter a power state equivalent to the ACPI G2/S5 or G3 states. + /// + /// If this is not supported, the system will exhibit the same behaviour as `cold`. + shutdown, + + /// Causes the system to perform a reset defined by a GUID provided after the string in the `reset_data` parameter. + platform, + }; + + /// Resets the entire platform. + pub fn resetSystem( + self: *const RuntimeServices, + /// The type of reset to perform. + reset_type: ResetType, + /// The status code for the reset. + reset_status: Status, + /// A null terminated string that describes the reset, followed by optional binary data or a GUID. + reset_data: ?[]align(2) u8, + ) noreturn { + if (reset_data) |data| { + self._resetSystem(reset_type, reset_status, data.len, data.ptr); + } else { + self._resetSystem(reset_type, reset_status, 0, null); + } + } + + /// Returns the next high 32 bits of the platform's monotonic counter. + /// + /// This value is non-volatile and is incremented whenever the system resets, `getNextHighMonotonicCount()` is called, + /// or when the lower 32 bits (accessible during boot services) of the monotonic counter wrap around. + pub fn getNextHighMonotonicCount( + self: *const RuntimeServices, + ) !u32 { + var high_count: u32 = undefined; + switch (self._getNextHighMonotonicCount(&high_count)) { + .success => return high_count, + else => |status| return status.err(), + } + } + + pub const CapsuleHeader = extern struct { + pub const Flags = packed struct(u32) { + guid_defined: u16, + + /// Firmware will attempt to process or launch the capsule across a system reset. If the capsule is not + /// recognized or processing requires a reset that is not supported, then expect an error. + persist_across_reset: bool, + + /// If true, then `persist_across_reset` must be true. + /// + /// Firmware will coalese the capsule from the scatter list into a contiguous buffer and place a pointer to it + /// into the system table. Recognition is not required. If processing requires a reset that is not supported, + /// then expect an error. + populate_system_table: bool, + + /// If true, then `persist_across_reset` must be true. + /// + /// Firmware will attempt to process or launch the capsule across a system reset. If the capsule is not + /// recognized or processing requires a reset that is not supported, then expect an error. + /// + /// The firmware will initiate a reset which is compatible with the passed-in capsule request and will not + /// return to the caller. + initiate_reset: bool, + + _pad1: u13, + }; + + /// A GUID that defines the contents of the capsule. + capsule_guid: Guid align(8), + + /// The size in bytes of the capsule header. This may be larger than the size of the structure defined here since + /// `capsule_guid` may imply additional entries. + header_size: u32, + + /// The capsule flags. + flags: Flags, + + /// The size in bytes of the capsule, including the capsule header. + capsule_size: u32, + }; + + pub const CapsuleBlockDescriptor = extern struct { + /// Length in bytes of the data pointed to by `address`. + /// + /// If `length` is zero, then `address.continuation` is active. Otherwise, `address.block` is active. + length: u64, + address: extern union { + /// The physical address of the capsule data. + block: bits.PhysicalAddress, + + /// The physical address of another list of `CapsuleBlockDescriptor` structures. + /// + /// When zero, this indicates the end of the list + continuation: bits.PhysicalAddress, + }, + }; + + /// Passes capsules to the firmware with both virtual and physical mapping. Depending on the intended consumption, + /// the firmware may process the capsule immediately. If the payload should persist across a system reset, the + /// reset value returned from `queryCapsuleCapabilities()` must be passed into `resetSystem()` and will cause the + /// capsule to be processed by the firmware as part of the reset process. + pub fn updateCapsule( + self: *const RuntimeServices, + /// An array of pointers to capsule headers. + capsule_headers: []*const CapsuleHeader, + /// A physical pointer to a list of `CapsuleBlockDescriptor` structures that describe the location in physical + /// memory of a set of capsules. This list must be in the same order as the capsules pointed to by + /// `capsule_headers`. + scatter_gather_list: bits.PhysicalAddress, + ) !void { + try self._updateCapsule(capsule_headers.ptr, capsule_headers.len, scatter_gather_list).err(); + } + + /// A tuple of the maximum size in bytes of a capsule, and the reset type required to process the capsule. + pub const CapsuleCapabilities = struct { u64, ResetType }; + + /// Returns if the capsule can be supported via `updateCapsule()`. + pub fn queryCapsuleCapabilities( + self: *const RuntimeServices, + /// An array of pointers to capsule headers. + capsule_headers: []*const CapsuleHeader, + ) !CapsuleCapabilities { + var maximum_capsule_size: usize = undefined; + var reset_type: ResetType = undefined; + switch (self._queryCapsuleCapabilities(capsule_headers.ptr, capsule_headers.len, &maximum_capsule_size, &reset_type)) { + .success => return .{ maximum_capsule_size, reset_type }, + else => |status| return status.err(), + } + } + + pub const signature: u64 = 0x56524553544e5552; + + /// The EFI Global Variable vendor GUID. + pub const global_variable align(8) = Guid{ + .time_low = 0x8be4df61, + .time_mid = 0x93ca, + .time_high_and_version = 0x11d2, + .clock_seq_high_and_reserved = 0xaa, + .clock_seq_low = 0x0d, + .node = [_]u8{ 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c }, + }; +}; diff --git a/lib/std/os/uefi/table/system.zig b/lib/std/os/uefi/table/system.zig new file mode 100644 index 000000000000..10cbdc1d5cd6 --- /dev/null +++ b/lib/std/os/uefi/table/system.zig @@ -0,0 +1,65 @@ +const bits = @import("../bits.zig"); +const table = @import("../table.zig"); +const protocol = @import("../protocol.zig"); + +/// The EFI System Table contains pointers to the runtime and boot services tables. +/// +/// As the system_table may grow with new UEFI versions, it is important to check hdr.header_size. +pub const SystemTable = extern struct { + hdr: table.Header, + + /// A null-terminated string that identifies the vendor that produces the system firmware of the platform. + firmware_vendor: [*:0]u16, + + /// A vendor specific value that identifies the revision of the system firmware for the platform. + firmware_revision: u32, + + /// The handle for the active console input device. The handle must support the `SimpleTextInput` and + /// `SimpleTextInputEx` protocols, even if there is no active console. + /// + /// Only null after Boot Services have been exited. + console_in_handle: ?bits.Handle, + + /// A pointer to the `SimpleTextInput` protocol interface that is associated with `console_in_handle`. + /// + /// Only null after Boot Services have been exited. + con_in: ?*const protocol.SimpleTextInput, + + /// The handle for the active console output device. The handle must support the `SimpleTextOutput` protocol, + /// even if there is no active console. + /// + /// Only null after Boot Services have been exited. + console_out_handle: ?bits.Handle, + + /// A pointer to the `SimpleTextOutput` protocol interface that is associated with `console_out_handle`. + /// + /// Only null after Boot Services have been exited. + con_out: ?*const protocol.SimpleTextOutput, + + /// The handle for the active console output device. The handle must support the `SimpleTextOutput` protocol, + /// even if there is no active console. + /// + /// Only null after Boot Services have been exited. + standard_error_handle: ?bits.Handle, + + /// A pointer to the `SimpleTextOutput` protocol interface that is associated with `standard_error_handle`. + /// + /// Only null after Boot Services have been exited. + std_err: ?*const protocol.SimpleTextOutput, + + /// A pointer to the EFI Runtime Services Table. + runtime_services: *const table.RuntimeServices, + + /// A pointer to the EFI Boot Services Table. + /// + /// Only null after Boot Services have been exited. + boot_services: ?*const table.BootServices, + + /// The number of system configuration tables in the buffer configuration_table. + number_of_table_entries: usize, + + /// A pointer to the system configuration tables. + configuration_table: [*]table.ConfigurationTable, + + pub const signature: u64 = 0x5453595320494249; +}; diff --git a/lib/std/os/uefi/tables.zig b/lib/std/os/uefi/tables.zig deleted file mode 100644 index e4f651104c7a..000000000000 --- a/lib/std/os/uefi/tables.zig +++ /dev/null @@ -1,137 +0,0 @@ -pub const BootServices = @import("tables/boot_services.zig").BootServices; -pub const RuntimeServices = @import("tables/runtime_services.zig").RuntimeServices; -pub const ConfigurationTable = @import("tables/configuration_table.zig").ConfigurationTable; -pub const SystemTable = @import("tables/system_table.zig").SystemTable; -pub const TableHeader = @import("tables/table_header.zig").TableHeader; - -pub const EfiEventNotify = *const fn (event: Event, ctx: *anyopaque) callconv(cc) void; - -pub const TimerDelay = enum(u32) { - TimerCancel, - TimerPeriodic, - TimerRelative, -}; - -pub const MemoryType = enum(u32) { - ReservedMemoryType, - LoaderCode, - LoaderData, - BootServicesCode, - BootServicesData, - RuntimeServicesCode, - RuntimeServicesData, - ConventionalMemory, - UnusableMemory, - ACPIReclaimMemory, - ACPIMemoryNVS, - MemoryMappedIO, - MemoryMappedIOPortSpace, - PalCode, - PersistentMemory, - MaxMemoryType, - _, -}; - -pub const MemoryDescriptorAttribute = packed struct(u64) { - uc: bool, - wc: bool, - wt: bool, - wb: bool, - uce: bool, - _pad1: u7 = 0, - wp: bool, - rp: bool, - xp: bool, - nv: bool, - more_reliable: bool, - ro: bool, - sp: bool, - cpu_crypto: bool, - _pad2: u43 = 0, - memory_runtime: bool, -}; - -pub const MemoryDescriptor = extern struct { - type: MemoryType, - physical_start: u64, - virtual_start: u64, - number_of_pages: u64, - attribute: MemoryDescriptorAttribute, -}; - -pub const LocateSearchType = enum(u32) { - AllHandles, - ByRegisterNotify, - ByProtocol, -}; - -pub const OpenProtocolAttributes = packed struct(u32) { - by_handle_protocol: bool = false, - get_protocol: bool = false, - test_protocol: bool = false, - by_child_controller: bool = false, - by_driver: bool = false, - exclusive: bool = false, - reserved: u26 = 0, -}; - -pub const ProtocolInformationEntry = extern struct { - agent_handle: ?Handle, - controller_handle: ?Handle, - attributes: OpenProtocolAttributes, - open_count: u32, -}; - -pub const EfiInterfaceType = enum(u32) { - EfiNativeInterface, -}; - -pub const AllocateType = enum(u32) { - AllocateAnyPages, - AllocateMaxAddress, - AllocateAddress, -}; - -pub const EfiPhysicalAddress = u64; - -pub const CapsuleHeader = extern struct { - capsuleGuid: Guid align(8), - headerSize: u32, - flags: u32, - capsuleImageSize: u32, -}; - -pub const UefiCapsuleBlockDescriptor = extern struct { - length: u64, - address: extern union { - dataBlock: EfiPhysicalAddress, - continuationPointer: EfiPhysicalAddress, - }, -}; - -pub const ResetType = enum(u32) { - ResetCold, - ResetWarm, - ResetShutdown, - ResetPlatformSpecific, -}; - -pub const global_variable align(8) = Guid{ - .time_low = 0x8be4df61, - .time_mid = 0x93ca, - .time_high_and_version = 0x11d2, - .clock_seq_high_and_reserved = 0xaa, - .clock_seq_low = 0x0d, - .node = [_]u8{ 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c }, -}; - -test { - std.testing.refAllDeclsRecursive(@This()); -} - -const std = @import("std"); -const uefi = std.os.uefi; -const Handle = uefi.Handle; -const Event = uefi.Event; -const Guid = uefi.Guid; -const cc = uefi.cc; diff --git a/lib/std/os/uefi/tables/configuration_table.zig b/lib/std/os/uefi/tables/configuration_table.zig deleted file mode 100644 index 75ea2a215b13..000000000000 --- a/lib/std/os/uefi/tables/configuration_table.zig +++ /dev/null @@ -1,80 +0,0 @@ -const uefi = @import("std").os.uefi; -const Guid = uefi.Guid; - -pub const ConfigurationTable = extern struct { - vendor_guid: Guid, - vendor_table: *anyopaque, - - pub const acpi_20_table_guid align(8) = Guid{ - .time_low = 0x8868e871, - .time_mid = 0xe4f1, - .time_high_and_version = 0x11d3, - .clock_seq_high_and_reserved = 0xbc, - .clock_seq_low = 0x22, - .node = [_]u8{ 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81 }, - }; - pub const acpi_10_table_guid align(8) = Guid{ - .time_low = 0xeb9d2d30, - .time_mid = 0x2d88, - .time_high_and_version = 0x11d3, - .clock_seq_high_and_reserved = 0x9a, - .clock_seq_low = 0x16, - .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, - }; - pub const sal_system_table_guid align(8) = Guid{ - .time_low = 0xeb9d2d32, - .time_mid = 0x2d88, - .time_high_and_version = 0x113d, - .clock_seq_high_and_reserved = 0x9a, - .clock_seq_low = 0x16, - .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, - }; - pub const smbios_table_guid align(8) = Guid{ - .time_low = 0xeb9d2d31, - .time_mid = 0x2d88, - .time_high_and_version = 0x11d3, - .clock_seq_high_and_reserved = 0x9a, - .clock_seq_low = 0x16, - .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, - }; - pub const smbios3_table_guid align(8) = Guid{ - .time_low = 0xf2fd1544, - .time_mid = 0x9794, - .time_high_and_version = 0x4a2c, - .clock_seq_high_and_reserved = 0x99, - .clock_seq_low = 0x2e, - .node = [_]u8{ 0xe5, 0xbb, 0xcf, 0x20, 0xe3, 0x94 }, - }; - pub const mps_table_guid align(8) = Guid{ - .time_low = 0xeb9d2d2f, - .time_mid = 0x2d88, - .time_high_and_version = 0x11d3, - .clock_seq_high_and_reserved = 0x9a, - .clock_seq_low = 0x16, - .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, - }; - pub const json_config_data_table_guid align(8) = Guid{ - .time_low = 0x87367f87, - .time_mid = 0x1119, - .time_high_and_version = 0x41ce, - .clock_seq_high_and_reserved = 0xaa, - .clock_seq_low = 0xec, - .node = [_]u8{ 0x8b, 0xe0, 0x11, 0x1f, 0x55, 0x8a }, - }; - pub const json_capsule_data_table_guid align(8) = Guid{ - .time_low = 0x35e7a725, - .time_mid = 0x8dd2, - .time_high_and_version = 0x4cac, - .clock_seq_high_and_reserved = 0x80, - .clock_seq_low = 0x11, - .node = [_]u8{ 0x33, 0xcd, 0xa8, 0x10, 0x90, 0x56 }, - }; - pub const json_capsule_result_table_guid align(8) = Guid{ - .time_low = 0xdbc461c3, - .time_mid = 0xb3de, - .time_high_and_version = 0x422a, - .clock_seq_high_and_reserved = 0xb9, - .clock_seq_low = 0xb4, - .node = [_]u8{ 0x98, 0x86, 0xfd, 0x49, 0xa1, 0xe5 }, - }; -}; diff --git a/lib/std/os/uefi/tables/runtime_services.zig b/lib/std/os/uefi/tables/runtime_services.zig deleted file mode 100644 index 264bfa081338..000000000000 --- a/lib/std/os/uefi/tables/runtime_services.zig +++ /dev/null @@ -1,72 +0,0 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Guid = uefi.Guid; -const TableHeader = uefi.tables.TableHeader; -const Time = uefi.Time; -const TimeCapabilities = uefi.TimeCapabilities; -const Status = uefi.Status; -const MemoryDescriptor = uefi.tables.MemoryDescriptor; -const ResetType = uefi.tables.ResetType; -const CapsuleHeader = uefi.tables.CapsuleHeader; -const EfiPhysicalAddress = uefi.tables.EfiPhysicalAddress; -const cc = uefi.cc; - -/// Runtime services are provided by the firmware before and after exitBootServices has been called. -/// -/// As the runtime_services table may grow with new UEFI versions, it is important to check hdr.header_size. -/// -/// Some functions may not be supported. Check the RuntimeServicesSupported variable using getVariable. -/// getVariable is one of the functions that may not be supported. -/// -/// Some functions may not be called while other functions are running. -pub const RuntimeServices = extern struct { - hdr: TableHeader, - - /// Returns the current time and date information, and the time-keeping capabilities of the hardware platform. - getTime: *const fn (time: *uefi.Time, capabilities: ?*TimeCapabilities) callconv(cc) Status, - - /// Sets the current local time and date information - setTime: *const fn (time: *uefi.Time) callconv(cc) Status, - - /// Returns the current wakeup alarm clock setting - getWakeupTime: *const fn (enabled: *bool, pending: *bool, time: *uefi.Time) callconv(cc) Status, - - /// Sets the system wakeup alarm clock time - setWakeupTime: *const fn (enable: *bool, time: ?*uefi.Time) callconv(cc) Status, - - /// Changes the runtime addressing mode of EFI firmware from physical to virtual. - setVirtualAddressMap: *const fn (mmap_size: usize, descriptor_size: usize, descriptor_version: u32, virtual_map: [*]MemoryDescriptor) callconv(cc) Status, - - /// Determines the new virtual address that is to be used on subsequent memory accesses. - convertPointer: *const fn (debug_disposition: usize, address: **anyopaque) callconv(cc) Status, - - /// Returns the value of a variable. - getVariable: *const fn (var_name: [*:0]const u16, vendor_guid: *align(8) const Guid, attributes: ?*u32, data_size: *usize, data: ?*anyopaque) callconv(cc) Status, - - /// Enumerates the current variable names. - getNextVariableName: *const fn (var_name_size: *usize, var_name: [*:0]u16, vendor_guid: *align(8) Guid) callconv(cc) Status, - - /// Sets the value of a variable. - setVariable: *const fn (var_name: [*:0]const u16, vendor_guid: *align(8) const Guid, attributes: u32, data_size: usize, data: *anyopaque) callconv(cc) Status, - - /// Return the next high 32 bits of the platform's monotonic counter - getNextHighMonotonicCount: *const fn (high_count: *u32) callconv(cc) Status, - - /// Resets the entire platform. - resetSystem: *const fn (reset_type: ResetType, reset_status: Status, data_size: usize, reset_data: ?*const anyopaque) callconv(cc) noreturn, - - /// Passes capsules to the firmware with both virtual and physical mapping. - /// Depending on the intended consumption, the firmware may process the capsule immediately. - /// If the payload should persist across a system reset, the reset value returned from - /// `queryCapsuleCapabilities` must be passed into resetSystem and will cause the capsule - /// to be processed by the firmware as part of the reset process. - updateCapsule: *const fn (capsule_header_array: **CapsuleHeader, capsule_count: usize, scatter_gather_list: EfiPhysicalAddress) callconv(cc) Status, - - /// Returns if the capsule can be supported via `updateCapsule` - queryCapsuleCapabilities: *const fn (capsule_header_array: **CapsuleHeader, capsule_count: usize, maximum_capsule_size: *usize, resetType: ResetType) callconv(cc) Status, - - /// Returns information about the EFI variables - queryVariableInfo: *const fn (attributes: *u32, maximum_variable_storage_size: *u64, remaining_variable_storage_size: *u64, maximum_variable_size: *u64) callconv(cc) Status, - - pub const signature: u64 = 0x56524553544e5552; -}; diff --git a/lib/std/os/uefi/tables/system_table.zig b/lib/std/os/uefi/tables/system_table.zig deleted file mode 100644 index 9a7061873061..000000000000 --- a/lib/std/os/uefi/tables/system_table.zig +++ /dev/null @@ -1,48 +0,0 @@ -const uefi = @import("std").os.uefi; -const BootServices = uefi.tables.BootServices; -const ConfigurationTable = uefi.tables.ConfigurationTable; -const Handle = uefi.Handle; -const RuntimeServices = uefi.tables.RuntimeServices; -const SimpleTextInputProtocol = uefi.protocol.SimpleTextInput; -const SimpleTextOutputProtocol = uefi.protocol.SimpleTextOutput; -const TableHeader = uefi.tables.TableHeader; - -/// The EFI System Table contains pointers to the runtime and boot services tables. -/// -/// As the system_table may grow with new UEFI versions, it is important to check hdr.header_size. -/// -/// After successfully calling boot_services.exitBootServices, console_in_handle, -/// con_in, console_out_handle, con_out, standard_error_handle, std_err, and -/// boot_services should be set to null. After setting these attributes to null, -/// hdr.crc32 must be recomputed. -pub const SystemTable = extern struct { - hdr: TableHeader, - - /// A null-terminated string that identifies the vendor that produces the system firmware of the platform. - firmware_vendor: [*:0]u16, - firmware_revision: u32, - console_in_handle: ?Handle, - con_in: ?*SimpleTextInputProtocol, - console_out_handle: ?Handle, - con_out: ?*SimpleTextOutputProtocol, - standard_error_handle: ?Handle, - std_err: ?*SimpleTextOutputProtocol, - runtime_services: *RuntimeServices, - boot_services: ?*BootServices, - number_of_table_entries: usize, - configuration_table: [*]ConfigurationTable, - - pub const signature: u64 = 0x5453595320494249; - pub const revision_1_02: u32 = (1 << 16) | 2; - pub const revision_1_10: u32 = (1 << 16) | 10; - pub const revision_2_00: u32 = (2 << 16); - pub const revision_2_10: u32 = (2 << 16) | 10; - pub const revision_2_20: u32 = (2 << 16) | 20; - pub const revision_2_30: u32 = (2 << 16) | 30; - pub const revision_2_31: u32 = (2 << 16) | 31; - pub const revision_2_40: u32 = (2 << 16) | 40; - pub const revision_2_50: u32 = (2 << 16) | 50; - pub const revision_2_60: u32 = (2 << 16) | 60; - pub const revision_2_70: u32 = (2 << 16) | 70; - pub const revision_2_80: u32 = (2 << 16) | 80; -}; From 9cdd6dbbd61c39bfc4034b7c8beb5615cd9f48d8 Mon Sep 17 00:00:00 2001 From: Nameless Date: Tue, 26 Dec 2023 10:44:38 -0600 Subject: [PATCH 04/23] std.os.uefi: add missing device path nodes, fix style and add documentation --- lib/std/os/uefi/bits.zig | 5 + lib/std/os/uefi/device_path.zig | 1040 ++++++++++++++++------ lib/std/os/uefi/protocol/device_path.zig | 10 +- 3 files changed, 777 insertions(+), 278 deletions(-) diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig index 5cc136cc83ae..89b6953052be 100644 --- a/lib/std/os/uefi/bits.zig +++ b/lib/std/os/uefi/bits.zig @@ -37,6 +37,11 @@ pub const Ipv6Address = extern struct { address: [16]u8, }; +pub const IpAddress = extern union { + v4: Ipv4Address, + v6: Ipv6Address, +}; + /// This structure represents time information. pub const Time = extern struct { /// 1900 - 9999 diff --git a/lib/std/os/uefi/device_path.zig b/lib/std/os/uefi/device_path.zig index 15507d4418aa..01e5312700c5 100644 --- a/lib/std/os/uefi/device_path.zig +++ b/lib/std/os/uefi/device_path.zig @@ -23,6 +23,12 @@ pub const DevicePath = union(Type) { _, }; + pub const Any = extern struct { + type: Type, + subtype: u8, + length: u16 align(1), + }; + pub const Hardware = union(Subtype) { pci: *const Pci, pc_card: *const PcCard, @@ -243,7 +249,7 @@ pub const DevicePath = union(Type) { }; pub const ExpandedAcpi = extern struct { - type: DevicePath.Type = .acpi, + type: Type = .acpi, subtype: Subtype = .expanded, length: u16 align(1), @@ -264,9 +270,9 @@ pub const DevicePath = union(Type) { /// the _HID field is used. If the length of this null-terminated string is greater than 0, then this field /// supersedes the _HID field. pub fn hidStr(self: *const ExpandedAcpi) [:0]const u8 { - const ptr = @as([*:0]const u8, @ptrCast(self)) + @sizeOf(ExpandedAcpi); + const ptr: [*:0]const u8 = @ptrCast(self); - return std.mem.span(ptr); + return std.mem.span(ptr + @sizeOf(ExpandedAcpi)); } /// Unique ID that is required by ACPI if two devices have the same _HID. This value must also match the @@ -308,7 +314,7 @@ pub const DevicePath = union(Type) { /// Protocol. The device path can contain multiple _ADR entries if multiple video output devices are displaying /// the same output. pub const Adr = extern struct { - type: DevicePath.Type = .acpi, + type: Type = .acpi, subtype: Subtype = .adr, length: u16 align(1), // 4 + 4*x @@ -332,7 +338,7 @@ pub const DevicePath = union(Type) { }; pub const Nvdimm = extern struct { - type: DevicePath.Type = .acpi, + type: Type = .acpi, subtype: Subtype = .nvdimm, length: u16 align(1) = 8, @@ -360,26 +366,55 @@ pub const DevicePath = union(Type) { infiniband: *const InfiniBand, uart: *const UartDevicePath, vendor: *const Vendor, + scsi_extended: *const ScsiExtended, + iscsi: *const Iscsi, + nvme_namespace: *const NvmeNamespace, + uri: *const Uri, + ufs: *const Ufs, + sd: *const Sd, + bluetooth: *const Bluetooth, + wifi: *const Wifi, + emmc: *const Emmc, + bluetooth_le: *const BluetoothLe, + dns: *const Dns, + nvdimm_namespace: *const NvdimmNamespace, + rest: *const Rest, + nvme_over_fabric: *const NvmeOverFabric, pub const Subtype = enum(u8) { atapi = 1, scsi = 2, fibre_channel = 3, - fibre_channel_ex = 21, @"1394" = 4, usb = 5, - sata = 18, - usb_wwid = 16, - lun = 17, - usb_class = 15, i2o = 6, + infiniband = 9, + vendor = 10, mac_address = 11, ipv4 = 12, ipv6 = 13, - vlan = 20, - infiniband = 9, uart = 14, - vendor = 10, + usb_class = 15, + usb_wwid = 16, + lun = 17, + sata = 18, + iscsi = 19, + vlan = 20, + fibre_channel_ex = 21, + scsi_extended = 22, + nvme = 23, + uri = 24, + ufs = 25, + sd = 26, + bluetooth = 27, + wifi = 28, + emmc = 29, + bluetooth_le = 30, + dns = 31, + nvdimm_namespace = 32, + rest = 33, + nvme_over_fabric = 34, + _, }; @@ -538,7 +573,7 @@ pub const DevicePath = union(Type) { }; pub const Sata = extern struct { - type: DevicePath.Type = .messaging, + type: Type = .messaging, subtype: Subtype = .sata, length: u16 align(1) = 10, @@ -800,27 +835,29 @@ pub const DevicePath = union(Type) { }; pub const Vlan = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + type: Type = .messaging, + subtype: Subtype = .vlan, + length: u16 align(1) = 6, + + /// VLAN Identifier vlan_id: u16 align(1), - }; - comptime { - assert(6 == @sizeOf(Vlan)); - assert(1 == @alignOf(Vlan)); + comptime { + assert(6 == @sizeOf(Vlan)); + assert(1 == @alignOf(Vlan)); - assert(0 == @offsetOf(Vlan, "type")); - assert(1 == @offsetOf(Vlan, "subtype")); - assert(2 == @offsetOf(Vlan, "length")); - assert(4 == @offsetOf(Vlan, "vlan_id")); - } + assert(0 == @offsetOf(Vlan, "type")); + assert(1 == @offsetOf(Vlan, "subtype")); + assert(2 == @offsetOf(Vlan, "length")); + assert(4 == @offsetOf(Vlan, "vlan_id")); + } + }; pub const InfiniBand = extern struct { pub const ResourceFlags = packed struct(u32) { pub const ControllerType = enum(u1) { - Ioc = 0, - Service = 1, + ioc = 0, + service = 1, }; ioc_or_service: ControllerType, @@ -833,13 +870,23 @@ pub const DevicePath = union(Type) { reserved: u27, }; - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + type: Type = .messaging, + subtype: Subtype = .infiniband, + length: u16 align(1) = 48, + + /// Flags to help identify/manage InfiniBand device path elements. resource_flags: ResourceFlags align(1), + + /// 128-bit global identifier for remote fabric port. port_gid: [16]u8, + + /// 64-bit unique identifier to remote IOC or server process. service_id: u64 align(1), + + /// 64-bit persistent ID of remote IOC port. target_port_id: u64 align(1), + + /// 64-bit persistent ID of remote device. device_id: u64 align(1), comptime { @@ -859,351 +906,798 @@ pub const DevicePath = union(Type) { pub const UartDevicePath = extern struct { pub const Parity = enum(u8) { - Default = 0, - None = 1, - Even = 2, - Odd = 3, - Mark = 4, - Space = 5, + default = 0, + none = 1, + even = 2, + odd = 3, + mark = 4, + space = 5, _, }; pub const StopBits = enum(u8) { - Default = 0, - One = 1, - OneAndAHalf = 2, - Two = 3, + default = 0, + one = 1, + one_and_half = 2, + two = 3, _, }; - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - reserved: u32 align(1), + type: Type = .messaging, + subtype: Subtype = .uart, + length: u16 align(1) = 19, + reserved: u32 align(1) = 0, + + /// The baud rate for the UART device. A value of 0 means default. baud_rate: u64 align(1), + + /// The number of data bits for the UART device. A value of 0 means default. data_bits: u8, + + /// The parity setting for the UART device. parity: Parity, - stop_bits: StopBits, - }; - comptime { - assert(19 == @sizeOf(UartDevicePath)); - assert(1 == @alignOf(UartDevicePath)); + /// The number of stop bits for the UART device. + stop_bits: StopBits, - assert(0 == @offsetOf(UartDevicePath, "type")); - assert(1 == @offsetOf(UartDevicePath, "subtype")); - assert(2 == @offsetOf(UartDevicePath, "length")); - assert(4 == @offsetOf(UartDevicePath, "reserved")); - assert(8 == @offsetOf(UartDevicePath, "baud_rate")); - assert(16 == @offsetOf(UartDevicePath, "data_bits")); - assert(17 == @offsetOf(UartDevicePath, "parity")); - assert(18 == @offsetOf(UartDevicePath, "stop_bits")); - } + comptime { + assert(19 == @sizeOf(UartDevicePath)); + assert(1 == @alignOf(UartDevicePath)); + + assert(0 == @offsetOf(UartDevicePath, "type")); + assert(1 == @offsetOf(UartDevicePath, "subtype")); + assert(2 == @offsetOf(UartDevicePath, "length")); + assert(4 == @offsetOf(UartDevicePath, "reserved")); + assert(8 == @offsetOf(UartDevicePath, "baud_rate")); + assert(16 == @offsetOf(UartDevicePath, "data_bits")); + assert(17 == @offsetOf(UartDevicePath, "parity")); + assert(18 == @offsetOf(UartDevicePath, "stop_bits")); + } + }; pub const Vendor = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + type: Type = .messaging, + subtype: Subtype = .vendor, + length: u16 align(1) = 20, + + /// Vendor-assigned GUID vendor_guid: Guid align(1), + + /// Vendor-specific data + pub fn data(self: *const Vendor) []const u8 { + const ptr: [*:0]const u8 = @ptrCast(self); + + return ptr[@sizeOf(Vendor)..self.length]; + } + + comptime { + assert(20 == @sizeOf(Vendor)); + assert(1 == @alignOf(Vendor)); + + assert(0 == @offsetOf(Vendor, "type")); + assert(1 == @offsetOf(Vendor, "subtype")); + assert(2 == @offsetOf(Vendor, "length")); + assert(4 == @offsetOf(Vendor, "vendor_guid")); + } + }; + + /// This section defines the extended device node for Serial Attached SCSI (SAS) devices. In this device + /// path the SAS Address and LUN are now defined as arrays to remove the need to endian swap the values. + pub const ScsiExtended = extern struct { + type: Type = .messaging, + subtype: Subtype = .scsi_extended, + length: u16 align(1) = 32, + + /// SAS Address for the SCSI Target port + address: [8]u8, + + /// SAS Logical unit number + logical_unit_number: [8]u8, + + /// Device and topology information + device_topology: u16 align(1), + + /// Relative Target Port + relative_target_port: u16 align(1), + + comptime { + assert(32 == @sizeOf(ScsiExtended)); + assert(1 == @alignOf(ScsiExtended)); + + assert(0 == @offsetOf(ScsiExtended, "type")); + assert(1 == @offsetOf(ScsiExtended, "subtype")); + assert(2 == @offsetOf(ScsiExtended, "length")); + assert(4 == @offsetOf(ScsiExtended, "address")); + assert(20 == @offsetOf(ScsiExtended, "logical_unit_number")); + assert(28 == @offsetOf(ScsiExtended, "device_topology")); + assert(30 == @offsetOf(ScsiExtended, "relative_target_port")); + } + }; + + pub const Iscsi = extern struct { + type: Type = .messaging, + subtype: Subtype = .iscsi, + length: u16 align(1), // 18 + x + + /// Network protocol. 0=TCP, 1+ is reserved. + network_protocol: u16 align(1), + + /// iSCSI login options + login_options: u16 align(1), + + /// iSCSI LUN + logical_unit_number: [8]u8, + + /// iSCSI target portal group tag the initiator intends to establish a session with. + target_portal_group: u16 align(1), + + /// iSCSI node target name + pub fn target_name(self: *const Iscsi) []const u8 { + const ptr: [*:0]const u8 = @ptrCast(self); + + return ptr[18..self.length]; + } + + comptime { + assert(18 == @sizeOf(Iscsi)); + assert(1 == @alignOf(Iscsi)); + + assert(0 == @offsetOf(Iscsi, "type")); + assert(1 == @offsetOf(Iscsi, "subtype")); + assert(2 == @offsetOf(Iscsi, "length")); + assert(4 == @offsetOf(Iscsi, "network_protocol")); + assert(6 == @offsetOf(Iscsi, "login_options")); + assert(8 == @offsetOf(Iscsi, "logical_unit_number")); + assert(16 == @offsetOf(Iscsi, "target_portal_group")); + } + }; + + pub const NvmeNamespace = extern struct { + type: Type = .messaging, + subtype: Subtype = .nvme, + length: u16 align(1) = 16, + + /// Namespace ID, 0 and 0xFFFFFFFF are invalid. + namespace_id: u32 align(1), + + /// the IEEE EUI-64 for the namespace + namespace_eid: [8]u8 align(1), + + comptime { + assert(16 == @sizeOf(NvmeNamespace)); + assert(1 == @alignOf(NvmeNamespace)); + + assert(0 == @offsetOf(NvmeNamespace, "type")); + assert(1 == @offsetOf(NvmeNamespace, "subtype")); + assert(2 == @offsetOf(NvmeNamespace, "length")); + assert(4 == @offsetOf(NvmeNamespace, "namespace_id")); + assert(8 == @offsetOf(NvmeNamespace, "namespace_eid")); + } + }; + + pub const Uri = extern struct { + type: Type = .messaging, + subtype: Subtype = .uri, + length: u16 align(1), // 4 + x + + /// URI string + pub fn uri(self: *const Uri) []const u8 { + const ptr: [*:0]const u8 = @ptrCast(self); + + return ptr[4..self.length]; + } + + comptime { + assert(4 == @sizeOf(Uri)); + assert(1 == @alignOf(Uri)); + + assert(0 == @offsetOf(Uri, "type")); + assert(1 == @offsetOf(Uri, "subtype")); + assert(2 == @offsetOf(Uri, "length")); + } + }; + + pub const Ufs = extern struct { + type: Type = .messaging, + subtype: Subtype = .ufs, + length: u16 align(1) = 6, + + /// Target id on the UFS interface + pun: u8, + + /// Logical unit number + lun: u8, + + comptime { + assert(6 == @sizeOf(Ufs)); + assert(1 == @alignOf(Ufs)); + + assert(0 == @offsetOf(Ufs, "type")); + assert(1 == @offsetOf(Ufs, "subtype")); + assert(2 == @offsetOf(Ufs, "length")); + assert(4 == @offsetOf(Ufs, "pun")); + assert(5 == @offsetOf(Ufs, "lun")); + } + }; + + pub const Sd = extern struct { + type: Type = .messaging, + subtype: Subtype = .sd, + length: u16 align(1) = 5, + + /// Slot number + slot_number: u8, + + comptime { + assert(6 == @sizeOf(Sd)); + assert(1 == @alignOf(Sd)); + + assert(0 == @offsetOf(Sd, "type")); + assert(1 == @offsetOf(Sd, "subtype")); + assert(2 == @offsetOf(Sd, "length")); + assert(4 == @offsetOf(Sd, "slot_number")); + } + }; + + pub const Bluetooth = extern struct { + type: Type = .messaging, + subtype: Subtype = .bluetooth, + length: u16 align(1) = 10, + + /// Bluetooth device address + address: [6]u8 align(1), + + comptime { + assert(10 == @sizeOf(Bluetooth)); + assert(1 == @alignOf(Bluetooth)); + + assert(0 == @offsetOf(Bluetooth, "type")); + assert(1 == @offsetOf(Bluetooth, "subtype")); + assert(2 == @offsetOf(Bluetooth, "length")); + assert(4 == @offsetOf(Bluetooth, "address")); + } + }; + + pub const Wifi = extern struct { + type: Type = .messaging, + subtype: Subtype = .wifi, + length: u16 align(1) = 36, + + ssid: [32]u8, + + comptime { + assert(36 == @sizeOf(Wifi)); + assert(1 == @alignOf(Wifi)); + + assert(0 == @offsetOf(Wifi, "type")); + assert(1 == @offsetOf(Wifi, "subtype")); + assert(2 == @offsetOf(Wifi, "length")); + assert(4 == @offsetOf(Wifi, "ssid")); + } + }; + + pub const Emmc = extern struct { + type: Type = .messaging, + subtype: Subtype = .emmc, + length: u16 align(1) = 5, + + /// Slot number + slot_number: u8, + + comptime { + assert(5 == @sizeOf(Emmc)); + assert(1 == @alignOf(Emmc)); + + assert(0 == @offsetOf(Emmc, "type")); + assert(1 == @offsetOf(Emmc, "subtype")); + assert(2 == @offsetOf(Emmc, "length")); + assert(4 == @offsetOf(Emmc, "slot_number")); + } + }; + + pub const BluetoothLe = extern struct { + pub const AddressType = enum(u8) { + public = 0, + random = 1, + }; + + type: Type = .messaging, + subtype: Subtype = .bluetooth_le, + length: u16 align(1) = 11, + + /// Bluetooth device address + address: [6]u8 align(1), + + address_type: AddressType, + + comptime { + assert(11 == @sizeOf(BluetoothLe)); + assert(1 == @alignOf(BluetoothLe)); + + assert(0 == @offsetOf(BluetoothLe, "type")); + assert(1 == @offsetOf(BluetoothLe, "subtype")); + assert(2 == @offsetOf(BluetoothLe, "length")); + assert(4 == @offsetOf(BluetoothLe, "address")); + assert(10 == @offsetOf(BluetoothLe, "address_type")); + } + }; + + pub const Dns = extern struct { + type: Type = .messaging, + subtype: Subtype = .dns, + length: u16 align(1), // 5 + x + + /// If true, the addresses are IPv6, otherwise IPv4 + is_ipv6: bool, + + pub fn addresses(self: *const Dns) []align(1) const bits.IpAddress { + const ptr = @as([*:0]const u8, @ptrCast(self)) + 5; + const adrs: [*:0]align(1) const bits.IpAddress = @ptrCast(ptr); + + const entries = @divExact(self.length - 5, @sizeOf(bits.IpAddress)); + return adrs[0..entries]; + } + + comptime { + assert(5 == @sizeOf(Dns)); + assert(1 == @alignOf(Dns)); + + assert(0 == @offsetOf(Dns, "type")); + assert(1 == @offsetOf(Dns, "subtype")); + assert(2 == @offsetOf(Dns, "length")); + assert(4 == @offsetOf(Dns, "is_ipv6")); + } + }; + + pub const NvdimmNamespace = extern struct { + type: Type = .messaging, + subtype: Subtype = .nvdimm_namespace, + length: u16 align(1) = 20, + + /// Namespace unique label identifier UUID. + namespace_uuid: [16]u8 align(1), + + comptime { + assert(20 == @sizeOf(NvdimmNamespace)); + assert(1 == @alignOf(NvdimmNamespace)); + + assert(0 == @offsetOf(NvdimmNamespace, "type")); + assert(1 == @offsetOf(NvdimmNamespace, "subtype")); + assert(2 == @offsetOf(NvdimmNamespace, "length")); + assert(4 == @offsetOf(NvdimmNamespace, "namespace_uuid")); + } }; - comptime { - assert(20 == @sizeOf(Vendor)); - assert(1 == @alignOf(Vendor)); + pub const Rest = extern struct { + pub const Service = enum(u8) { + redfish = 1, + odata = 2, + vendor = 0xff, + _, + }; - assert(0 == @offsetOf(Vendor, "type")); - assert(1 == @offsetOf(Vendor, "subtype")); - assert(2 == @offsetOf(Vendor, "length")); - assert(4 == @offsetOf(Vendor, "vendor_guid")); - } + pub const AccessMode = enum(u8) { + in_band = 1, + out_of_band = 2, + _, + }; + + type: Type = .messaging, + subtype: Subtype = .rest, + length: u16 align(1) = 6, + + /// Service type + service: Service, + + /// Access mode + access_mode: AccessMode, + + comptime { + assert(6 == @sizeOf(Rest)); + assert(1 == @alignOf(Rest)); + + assert(0 == @offsetOf(Rest, "type")); + assert(1 == @offsetOf(Rest, "subtype")); + assert(2 == @offsetOf(Rest, "length")); + assert(4 == @offsetOf(Rest, "service")); + assert(5 == @offsetOf(Rest, "access_mode")); + } + }; + + pub const NvmeOverFabric = extern struct { + type: Type = .messaging, + subtype: Subtype = .nvme_over_fabric, + length: u16 align(1), // 20 + x + + /// Namespace identifier type + nidt: u8, + + /// Namespace id + nid: [16]u8, + + /// Unique identifier of an NVMe subsystem stored as a null-terminated UTF-8 string. + pub fn data(self: *const NvmeOverFabric) [:0]const u8 { + const ptr: [*:0]const u8 = @ptrCast(self); + + return ptr[20..self.length :0]; + } + + comptime { + assert(16 == @sizeOf(NvmeOverFabric)); + assert(1 == @alignOf(NvmeOverFabric)); + + assert(0 == @offsetOf(NvmeOverFabric, "type")); + assert(1 == @offsetOf(NvmeOverFabric, "subtype")); + assert(2 == @offsetOf(NvmeOverFabric, "length")); + assert(4 == @offsetOf(NvmeOverFabric, "namespace_uuid")); + } + }; }; pub const Media = union(Subtype) { - HardDrive: *const HardDriveDevicePath, - Cdrom: *const CdromDevicePath, - Vendor: *const VendorDevicePath, - FilePath: *const FilePathDevicePath, - MediaProtocol: *const MediaProtocolDevicePath, - PiwgFirmwareFile: *const PiwgFirmwareFileDevicePath, - PiwgFirmwareVolume: *const PiwgFirmwareVolumeDevicePath, - RelativeOffsetRange: *const RelativeOffsetRangeDevicePath, - RamDisk: *const RamDiskDevicePath, + hard_drive: *const HardDrive, + cdrom: *const Cdrom, + vendor: *const Vendor, + file_path: *const FilePath, + media_protocol: *const MediaProtocol, + piwg_firmware_file: *const PiwgFirmwareFile, + piwg_firmware_volume: *const PiwgFirmwareVolume, + relative_offset_range: *const RelativeOffsetRange, + ram_disk: *const RamDisk, pub const Subtype = enum(u8) { - HardDrive = 1, - Cdrom = 2, - Vendor = 3, - FilePath = 4, - MediaProtocol = 5, - PiwgFirmwareFile = 6, - PiwgFirmwareVolume = 7, - RelativeOffsetRange = 8, - RamDisk = 9, + hard_drive = 1, + cdrom = 2, + vendor = 3, + file_path = 4, + media_protocol = 5, + piwg_firmware_file = 6, + piwg_firmware_volume = 7, + relative_offset_range = 8, + ram_disk = 9, _, }; - pub const HardDriveDevicePath = extern struct { + /// The Hard Drive Media Device Path is used to represent a partition on a hard drive. Each partition has at + /// least Hard Drive Device Path node, each describing an entry in a partition table. EFI supports MBR and + /// GPT partitioning formats. Partitions are numbered according to their entry in their respective partition + /// table, starting with 1. Partitions are addressed in EFI starting at LBA zero. A partition number of zero + /// can be used to represent the raw hard drive or a raw extended partition. + /// + /// The partition format is stored in the Device Path to allow new partition formats to be supported in the + /// future. The Hard Drive Device Path also contains a Disk Signature and a Disk Signature Type. The disk + /// signature is maintained by the OS and only used by EFI to partition Device Path nodes. The disk + /// signature enables the OS to find disks even after they have been physically moved in a system. + pub const HardDrive = extern struct { pub const Format = enum(u8) { - LegacyMbr = 0x01, - GuidPartitionTable = 0x02, + mbr = 0x01, + gpt = 0x02, }; pub const SignatureType = enum(u8) { - NoSignature = 0x00, + none = 0x00, /// "32-bit signature from address 0x1b8 of the type 0x01 MBR" - MbrSignature = 0x01, - GuidSignature = 0x02, + mbr = 0x01, + guid = 0x02, }; - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + type: Type = .media, + subtype: Subtype = .hard_drive, + length: u16 align(1) = 42, + + /// Describes the partition number of the physical hard drive. partition_number: u32 align(1), + + /// The starting LBA of the partition partition_start: u64 align(1), + + /// Size of the partition in logical blocks partition_size: u64 align(1), + + /// Signature unique to this partition partition_signature: [16]u8, + + /// Partition format partition_format: Format, + + /// Signature type signature_type: SignatureType, + + comptime { + assert(42 == @sizeOf(HardDrive)); + assert(1 == @alignOf(HardDrive)); + + assert(0 == @offsetOf(HardDrive, "type")); + assert(1 == @offsetOf(HardDrive, "subtype")); + assert(2 == @offsetOf(HardDrive, "length")); + assert(4 == @offsetOf(HardDrive, "partition_number")); + assert(8 == @offsetOf(HardDrive, "partition_start")); + assert(16 == @offsetOf(HardDrive, "partition_size")); + assert(24 == @offsetOf(HardDrive, "partition_signature")); + assert(40 == @offsetOf(HardDrive, "partition_format")); + assert(41 == @offsetOf(HardDrive, "signature_type")); + } }; - comptime { - assert(42 == @sizeOf(HardDriveDevicePath)); - assert(1 == @alignOf(HardDriveDevicePath)); - - assert(0 == @offsetOf(HardDriveDevicePath, "type")); - assert(1 == @offsetOf(HardDriveDevicePath, "subtype")); - assert(2 == @offsetOf(HardDriveDevicePath, "length")); - assert(4 == @offsetOf(HardDriveDevicePath, "partition_number")); - assert(8 == @offsetOf(HardDriveDevicePath, "partition_start")); - assert(16 == @offsetOf(HardDriveDevicePath, "partition_size")); - assert(24 == @offsetOf(HardDriveDevicePath, "partition_signature")); - assert(40 == @offsetOf(HardDriveDevicePath, "partition_format")); - assert(41 == @offsetOf(HardDriveDevicePath, "signature_type")); - } - - pub const CdromDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + /// The CD-ROM Media Device Path is used to define a system partition that exists on a CD-ROM. The CD-ROM is + /// assumed to contain an ISO-9660 file system and follow the CD-ROM “El Torito” format. The Boot Entry + /// number from the Boot Catalog is how the “El Torito” specification defines the existence of bootable + /// entities on a CD-ROM. In EFI the bootable entity is an EFI System Partition that is pointed to by the + /// Boot Entry. + pub const Cdrom = extern struct { + type: Type = .media, + subtype: Subtype = .cdrom, + length: u16 align(1) = 24, + + ///Boot Entry Number from the Boot Catalog. The default entry is zero. boot_entry: u32 align(1), + + /// Starting RBA of the partition on the medium. CDROMs use Relative logical Block Addressing. partition_start: u64 align(1), + + /// Size of the partition in units of Blocks, also called Sectors. partition_size: u64 align(1), + + comptime { + assert(24 == @sizeOf(Cdrom)); + assert(1 == @alignOf(Cdrom)); + + assert(0 == @offsetOf(Cdrom, "type")); + assert(1 == @offsetOf(Cdrom, "subtype")); + assert(2 == @offsetOf(Cdrom, "length")); + assert(4 == @offsetOf(Cdrom, "boot_entry")); + assert(8 == @offsetOf(Cdrom, "partition_start")); + assert(16 == @offsetOf(Cdrom, "partition_size")); + } }; - comptime { - assert(24 == @sizeOf(CdromDevicePath)); - assert(1 == @alignOf(CdromDevicePath)); - - assert(0 == @offsetOf(CdromDevicePath, "type")); - assert(1 == @offsetOf(CdromDevicePath, "subtype")); - assert(2 == @offsetOf(CdromDevicePath, "length")); - assert(4 == @offsetOf(CdromDevicePath, "boot_entry")); - assert(8 == @offsetOf(CdromDevicePath, "partition_start")); - assert(16 == @offsetOf(CdromDevicePath, "partition_size")); - } - - pub const VendorDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + pub const Vendor = extern struct { + type: Type = .media, + subtype: Subtype = .vendor, + length: u16 align(1), // 20 + x + + /// Vendor GUID guid: Guid align(1), - }; - comptime { - assert(20 == @sizeOf(VendorDevicePath)); - assert(1 == @alignOf(VendorDevicePath)); + /// Vendor-specific data + pub fn data(self: *const Vendor) []const u8 { + const ptr: [*:0]const u8 = @ptrCast(self); - assert(0 == @offsetOf(VendorDevicePath, "type")); - assert(1 == @offsetOf(VendorDevicePath, "subtype")); - assert(2 == @offsetOf(VendorDevicePath, "length")); - assert(4 == @offsetOf(VendorDevicePath, "guid")); - } + return ptr[@sizeOf(Vendor)..self.length]; + } - pub const FilePathDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + comptime { + assert(20 == @sizeOf(Vendor)); + assert(1 == @alignOf(Vendor)); - pub fn getPath(self: *const FilePathDevicePath) [*:0]align(1) const u16 { - return @as([*:0]align(1) const u16, @ptrCast(@as([*]const u8, @ptrCast(self)) + @sizeOf(FilePathDevicePath))); + assert(0 == @offsetOf(Vendor, "type")); + assert(1 == @offsetOf(Vendor, "subtype")); + assert(2 == @offsetOf(Vendor, "length")); + assert(4 == @offsetOf(Vendor, "guid")); } }; - comptime { - assert(4 == @sizeOf(FilePathDevicePath)); - assert(1 == @alignOf(FilePathDevicePath)); + pub const FilePath = extern struct { + type: Type = .media, + subtype: Subtype = .file_path, + length: u16 align(1), // 4 + x + + ///A NULL-terminated Path string including directory and file names. The length of this string n can be + /// determined by subtracting 4 from the Length entry. A device path may contain one or more of these + /// nodes. Each node can optionally add a “" separator to the beginning and/or the end of the Path Name + /// string. The complete path to a file can be found by logically concatenating all the Path Name + /// strings in the File Path Media Device Path nodes. + pub fn path(self: *const FilePath) [:0]align(1) const u16 { + const ptr: [*:0]align(1) const u16 = @ptrCast(self); + + const entries = @divExact(self.length, 2); + return ptr[2..entries]; + } - assert(0 == @offsetOf(FilePathDevicePath, "type")); - assert(1 == @offsetOf(FilePathDevicePath, "subtype")); - assert(2 == @offsetOf(FilePathDevicePath, "length")); - } + comptime { + assert(4 == @sizeOf(FilePath)); + assert(1 == @alignOf(FilePath)); - pub const MediaProtocolDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - guid: Guid align(1), + assert(0 == @offsetOf(FilePath, "type")); + assert(1 == @offsetOf(FilePath, "subtype")); + assert(2 == @offsetOf(FilePath, "length")); + } }; - comptime { - assert(20 == @sizeOf(MediaProtocolDevicePath)); - assert(1 == @alignOf(MediaProtocolDevicePath)); + pub const MediaProtocol = extern struct { + type: Type = .media, + subtype: Subtype = .media_protocol, + length: u16 align(1) = 20, + + /// The ID of the protocol. + guid: Guid align(1), - assert(0 == @offsetOf(MediaProtocolDevicePath, "type")); - assert(1 == @offsetOf(MediaProtocolDevicePath, "subtype")); - assert(2 == @offsetOf(MediaProtocolDevicePath, "length")); - assert(4 == @offsetOf(MediaProtocolDevicePath, "guid")); - } + comptime { + assert(20 == @sizeOf(MediaProtocol)); + assert(1 == @alignOf(MediaProtocol)); - pub const PiwgFirmwareFileDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - fv_filename: Guid align(1), + assert(0 == @offsetOf(MediaProtocol, "type")); + assert(1 == @offsetOf(MediaProtocol, "subtype")); + assert(2 == @offsetOf(MediaProtocol, "length")); + assert(4 == @offsetOf(MediaProtocol, "guid")); + } }; - comptime { - assert(20 == @sizeOf(PiwgFirmwareFileDevicePath)); - assert(1 == @alignOf(PiwgFirmwareFileDevicePath)); + /// This type is used by systems implementing the PI architecture specifications to describe a firmware + /// file in a firmware volume. + pub const PiwgFirmwareFile = extern struct { + type: Type = .media, + subtype: Subtype = .piwg_firmware_file, + length: u16 align(1) = 20, - assert(0 == @offsetOf(PiwgFirmwareFileDevicePath, "type")); - assert(1 == @offsetOf(PiwgFirmwareFileDevicePath, "subtype")); - assert(2 == @offsetOf(PiwgFirmwareFileDevicePath, "length")); - assert(4 == @offsetOf(PiwgFirmwareFileDevicePath, "fv_filename")); - } + /// Firmware file name + fv_filename: Guid align(1), - pub const PiwgFirmwareVolumeDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - fv_name: Guid align(1), + comptime { + assert(20 == @sizeOf(PiwgFirmwareFile)); + assert(1 == @alignOf(PiwgFirmwareFile)); + + assert(0 == @offsetOf(PiwgFirmwareFile, "type")); + assert(1 == @offsetOf(PiwgFirmwareFile, "subtype")); + assert(2 == @offsetOf(PiwgFirmwareFile, "length")); + assert(4 == @offsetOf(PiwgFirmwareFile, "fv_filename")); + } }; - comptime { - assert(20 == @sizeOf(PiwgFirmwareVolumeDevicePath)); - assert(1 == @alignOf(PiwgFirmwareVolumeDevicePath)); + /// This type is used by systems implementing the PI architecture specifications to describe a firmware volume. + pub const PiwgFirmwareVolume = extern struct { + type: Type = .media, + subtype: Subtype = .piwg_firmware_volume, + length: u16 align(1) = 20, - assert(0 == @offsetOf(PiwgFirmwareVolumeDevicePath, "type")); - assert(1 == @offsetOf(PiwgFirmwareVolumeDevicePath, "subtype")); - assert(2 == @offsetOf(PiwgFirmwareVolumeDevicePath, "length")); - assert(4 == @offsetOf(PiwgFirmwareVolumeDevicePath, "fv_name")); - } + /// Firmware volume name + fv_name: Guid align(1), - pub const RelativeOffsetRangeDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + comptime { + assert(20 == @sizeOf(PiwgFirmwareVolume)); + assert(1 == @alignOf(PiwgFirmwareVolume)); + + assert(0 == @offsetOf(PiwgFirmwareVolume, "type")); + assert(1 == @offsetOf(PiwgFirmwareVolume, "subtype")); + assert(2 == @offsetOf(PiwgFirmwareVolume, "length")); + assert(4 == @offsetOf(PiwgFirmwareVolume, "fv_name")); + } + }; + + /// This device path node specifies a range of offsets relative to the first byte available on the device. + /// The starting offset is the first byte of the range and the ending offset is the last byte of the range + /// (not the last byte + 1). + pub const RelativeOffsetRange = extern struct { + type: Type = .media, + subtype: Subtype = .relative_offset_range, + length: u16 align(1) = 24, reserved: u32 align(1), + + /// Offset of the first byte, relative to the parent device node. start: u64 align(1), + + /// Offset of the last byte, relative to the parent device node. end: u64 align(1), + + comptime { + assert(24 == @sizeOf(RelativeOffsetRange)); + assert(1 == @alignOf(RelativeOffsetRange)); + + assert(0 == @offsetOf(RelativeOffsetRange, "type")); + assert(1 == @offsetOf(RelativeOffsetRange, "subtype")); + assert(2 == @offsetOf(RelativeOffsetRange, "length")); + assert(4 == @offsetOf(RelativeOffsetRange, "reserved")); + assert(8 == @offsetOf(RelativeOffsetRange, "start")); + assert(16 == @offsetOf(RelativeOffsetRange, "end")); + } }; - comptime { - assert(24 == @sizeOf(RelativeOffsetRangeDevicePath)); - assert(1 == @alignOf(RelativeOffsetRangeDevicePath)); - - assert(0 == @offsetOf(RelativeOffsetRangeDevicePath, "type")); - assert(1 == @offsetOf(RelativeOffsetRangeDevicePath, "subtype")); - assert(2 == @offsetOf(RelativeOffsetRangeDevicePath, "length")); - assert(4 == @offsetOf(RelativeOffsetRangeDevicePath, "reserved")); - assert(8 == @offsetOf(RelativeOffsetRangeDevicePath, "start")); - assert(16 == @offsetOf(RelativeOffsetRangeDevicePath, "end")); - } - - pub const RamDiskDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + pub const RamDisk = extern struct { + type: Type = .media, + subtype: Subtype = .ram_disk, + length: u16 align(1) = 38, + + /// Starting memory address start: u64 align(1), + + /// Ending memory address end: u64 align(1), + + /// GUID that defines the type of the RAM Disk. disk_type: Guid align(1), + + /// RAM Disk instance number, if supported. The default value is zero. instance: u16 align(1), - }; - comptime { - assert(38 == @sizeOf(RamDiskDevicePath)); - assert(1 == @alignOf(RamDiskDevicePath)); - - assert(0 == @offsetOf(RamDiskDevicePath, "type")); - assert(1 == @offsetOf(RamDiskDevicePath, "subtype")); - assert(2 == @offsetOf(RamDiskDevicePath, "length")); - assert(4 == @offsetOf(RamDiskDevicePath, "start")); - assert(12 == @offsetOf(RamDiskDevicePath, "end")); - assert(20 == @offsetOf(RamDiskDevicePath, "disk_type")); - assert(36 == @offsetOf(RamDiskDevicePath, "instance")); - } + comptime { + assert(38 == @sizeOf(RamDisk)); + assert(1 == @alignOf(RamDisk)); + + assert(0 == @offsetOf(RamDisk, "type")); + assert(1 == @offsetOf(RamDisk, "subtype")); + assert(2 == @offsetOf(RamDisk, "length")); + assert(4 == @offsetOf(RamDisk, "start")); + assert(12 == @offsetOf(RamDisk, "end")); + assert(20 == @offsetOf(RamDisk, "disk_type")); + assert(36 == @offsetOf(RamDisk, "instance")); + } + }; }; pub const BiosBootSpecification = union(Subtype) { - BBS101: *const BBS101DevicePath, + bbs101: *const BBS101, pub const Subtype = enum(u8) { - BBS101 = 1, + bbs101 = 1, _, }; - pub const BBS101DevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + pub const BBS101 = extern struct { + type: Type = .bios_boot_specification, + subtype: Subtype = .bbs101, + length: u16 align(1), // 8 + x + + /// Device Type as defined by the BIOS Boot Specification. device_type: u16 align(1), + + /// Status Flags as defined by the BIOS Boot Specification status_flag: u16 align(1), - pub fn getDescription(self: *const BBS101DevicePath) [*:0]const u8 { - return @as([*:0]const u8, @ptrCast(self)) + @sizeOf(BBS101DevicePath); + pub fn description(self: *const BBS101) [*:0]const u8 { + return @as([*:0]const u8, @ptrCast(self)) + @sizeOf(BBS101); } - }; - comptime { - assert(8 == @sizeOf(BBS101DevicePath)); - assert(1 == @alignOf(BBS101DevicePath)); - - assert(0 == @offsetOf(BBS101DevicePath, "type")); - assert(1 == @offsetOf(BBS101DevicePath, "subtype")); - assert(2 == @offsetOf(BBS101DevicePath, "length")); - assert(4 == @offsetOf(BBS101DevicePath, "device_type")); - assert(6 == @offsetOf(BBS101DevicePath, "status_flag")); - } + comptime { + assert(8 == @sizeOf(BBS101)); + assert(1 == @alignOf(BBS101)); + + assert(0 == @offsetOf(BBS101, "type")); + assert(1 == @offsetOf(BBS101, "subtype")); + assert(2 == @offsetOf(BBS101, "length")); + assert(4 == @offsetOf(BBS101, "device_type")); + assert(6 == @offsetOf(BBS101, "status_flag")); + } + }; }; pub const End = union(Subtype) { - EndEntire: *const EndEntireDevicePath, - EndThisInstance: *const EndThisInstanceDevicePath, + entire: *const EndEntire, + this_instance: *const EndThisInstance, pub const Subtype = enum(u8) { - EndEntire = 0xff, - EndThisInstance = 0x01, + entire = 0xff, + this_instance = 0x01, _, }; - pub const EndEntireDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), - }; - - comptime { - assert(4 == @sizeOf(EndEntireDevicePath)); - assert(1 == @alignOf(EndEntireDevicePath)); + pub const EndEntire = extern struct { + type: Type = .end, + subtype: Subtype = .entire, + length: u16 align(1) = 4, - assert(0 == @offsetOf(EndEntireDevicePath, "type")); - assert(1 == @offsetOf(EndEntireDevicePath, "subtype")); - assert(2 == @offsetOf(EndEntireDevicePath, "length")); - } + comptime { + assert(4 == @sizeOf(EndEntire)); + assert(1 == @alignOf(EndEntire)); - pub const EndThisInstanceDevicePath = extern struct { - type: DevicePath.Type, - subtype: Subtype, - length: u16 align(1), + assert(0 == @offsetOf(EndEntire, "type")); + assert(1 == @offsetOf(EndEntire, "subtype")); + assert(2 == @offsetOf(EndEntire, "length")); + } }; - comptime { - assert(4 == @sizeOf(EndEntireDevicePath)); - assert(1 == @alignOf(EndEntireDevicePath)); + pub const EndThisInstance = extern struct { + type: Type = .end, + subtype: Subtype = .this_instance, + length: u16 align(1) = 4, - assert(0 == @offsetOf(EndEntireDevicePath, "type")); - assert(1 == @offsetOf(EndEntireDevicePath, "subtype")); - assert(2 == @offsetOf(EndEntireDevicePath, "length")); - } + comptime { + assert(4 == @sizeOf(EndEntire)); + assert(1 == @alignOf(EndEntire)); + + assert(0 == @offsetOf(EndEntire, "type")); + assert(1 == @offsetOf(EndEntire, "subtype")); + assert(2 == @offsetOf(EndEntire, "length")); + } + }; }; }; diff --git a/lib/std/os/uefi/protocol/device_path.zig b/lib/std/os/uefi/protocol/device_path.zig index a08eee193cb6..15b21ecf87a9 100644 --- a/lib/std/os/uefi/protocol/device_path.zig +++ b/lib/std/os/uefi/protocol/device_path.zig @@ -53,24 +53,24 @@ pub const DevicePath = extern struct { // Pointer to the copy of the end node of the current chain, which is - 4 from the buffer // as the end node itself is 4 bytes (type: u8 + subtype: u8 + length: u16). - var new = @as(*uefi.DevicePath.Media.FilePathDevicePath, @ptrCast(buf.ptr + path_size - 4)); + var new = @as(*uefi.DevicePath.Media.FilePath, @ptrCast(buf.ptr + path_size - 4)); new.type = .Media; new.subtype = .FilePath; - new.length = @sizeOf(uefi.DevicePath.Media.FilePathDevicePath) + 2 * (@as(u16, @intCast(path.len)) + 1); + new.length = @sizeOf(uefi.DevicePath.Media.FilePath) + 2 * (@as(u16, @intCast(path.len)) + 1); // The same as new.getPath(), but not const as we're filling it in. - var ptr = @as([*:0]align(1) u16, @ptrCast(@as([*]u8, @ptrCast(new)) + @sizeOf(uefi.DevicePath.Media.FilePathDevicePath))); + var ptr = @as([*:0]align(1) u16, @ptrCast(@as([*]u8, @ptrCast(new)) + @sizeOf(uefi.DevicePath.Media.FilePath))); for (path, 0..) |s, i| ptr[i] = s; ptr[path.len] = 0; - var end = @as(*uefi.DevicePath.End.EndEntireDevicePath, @ptrCast(@as(*DevicePath, @ptrCast(new)).next().?)); + var end = @as(*uefi.DevicePath.End.EndEntire, @ptrCast(@as(*DevicePath, @ptrCast(new)).next().?)); end.type = .End; end.subtype = .EndEntire; - end.length = @sizeOf(uefi.DevicePath.End.EndEntireDevicePath); + end.length = @sizeOf(uefi.DevicePath.End.EndEntire); return @as(*DevicePath, @ptrCast(buf.ptr)); } From ca6eaef67c0c26fa3055f81bd1d6e2015e286f3e Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 28 Dec 2023 10:30:20 -0600 Subject: [PATCH 05/23] std.os.uefi: bind and fix console protocols --- lib/std/os/uefi/bits.zig | 23 +- lib/std/os/uefi/device_path.zig | 22 +- lib/std/os/uefi/protocol/absolute_pointer.zig | 84 ++++-- lib/std/os/uefi/protocol/graphics_output.zig | 247 ++++++++++++++---- lib/std/os/uefi/protocol/serial_io.zig | 170 ++++++++++++ lib/std/os/uefi/protocol/simple_pointer.zig | 85 ++++-- .../os/uefi/protocol/simple_text_input.zig | 31 ++- .../os/uefi/protocol/simple_text_input_ex.zig | 69 +++-- .../os/uefi/protocol/simple_text_output.zig | 160 ++++++++---- 9 files changed, 676 insertions(+), 215 deletions(-) create mode 100644 lib/std/os/uefi/protocol/serial_io.zig diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig index 89b6953052be..26ef748da4e3 100644 --- a/lib/std/os/uefi/bits.zig +++ b/lib/std/os/uefi/bits.zig @@ -15,13 +15,13 @@ pub const PhysicalAddress = u64; pub const VirtualAddress = u64; /// An EFI Handle represents a collection of related interfaces. -pub const Handle = *opaque {}; +pub const Handle = *const opaque {}; /// A handle to an event structure. -pub const Event = *opaque {}; +pub const Event = *const opaque {}; /// File Handle as specified in the EFI Shell Spec -pub const FileHandle = *opaque {}; +pub const FileHandle = *const opaque {}; pub const Crc32 = std.hash.crc.Crc32IsoHdlc; @@ -345,6 +345,23 @@ pub const Guid = extern struct { } }; +pub const Parity = enum(u8) { + default = 0, + none = 1, + even = 2, + odd = 3, + mark = 4, + space = 5, +}; + +pub const StopBits = enum(u8) { + default = 0, + one = 1, + one_and_half = 2, + two = 3, + _, +}; + pub const FileInfo = extern struct { size: u64, file_size: u64, diff --git a/lib/std/os/uefi/device_path.zig b/lib/std/os/uefi/device_path.zig index 01e5312700c5..c2277929d337 100644 --- a/lib/std/os/uefi/device_path.zig +++ b/lib/std/os/uefi/device_path.zig @@ -905,24 +905,6 @@ pub const DevicePath = union(Type) { }; pub const UartDevicePath = extern struct { - pub const Parity = enum(u8) { - default = 0, - none = 1, - even = 2, - odd = 3, - mark = 4, - space = 5, - _, - }; - - pub const StopBits = enum(u8) { - default = 0, - one = 1, - one_and_half = 2, - two = 3, - _, - }; - type: Type = .messaging, subtype: Subtype = .uart, length: u16 align(1) = 19, @@ -935,10 +917,10 @@ pub const DevicePath = union(Type) { data_bits: u8, /// The parity setting for the UART device. - parity: Parity, + parity: bits.Parity, /// The number of stop bits for the UART device. - stop_bits: StopBits, + stop_bits: bits.StopBits, comptime { assert(19 == @sizeOf(UartDevicePath)); diff --git a/lib/std/os/uefi/protocol/absolute_pointer.zig b/lib/std/os/uefi/protocol/absolute_pointer.zig index e71f7b3cc821..178163833a6c 100644 --- a/lib/std/os/uefi/protocol/absolute_pointer.zig +++ b/lib/std/os/uefi/protocol/absolute_pointer.zig @@ -1,62 +1,92 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Event = uefi.Event; -const Guid = uefi.Guid; -const Status = uefi.Status; -const cc = uefi.cc; - -/// Protocol for touchscreens. +const bits = @import("../bits.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; +const Event = bits.Event; + +/// Provides services that allow information about an absolute pointer device to be retrieved. pub const AbsolutePointer = extern struct { _reset: *const fn (*const AbsolutePointer, bool) callconv(cc) Status, _get_state: *const fn (*const AbsolutePointer, *State) callconv(cc) Status, - wait_for_input: Event, - mode: *Mode, - /// Resets the pointer device hardware. - pub fn reset(self: *const AbsolutePointer, verify: bool) Status { - return self._reset(self, verify); - } - - /// Retrieves the current state of a pointer device. - pub fn getState(self: *const AbsolutePointer, state: *State) Status { - return self._get_state(self, state); - } + /// Event to use with `waitForEvent()` to wait for input from the pointer device. + wait_for_input: Event, - pub const guid align(8) = Guid{ - .time_low = 0x8d59d32b, - .time_mid = 0xc655, - .time_high_and_version = 0x4ae9, - .clock_seq_high_and_reserved = 0x9b, - .clock_seq_low = 0x15, - .node = [_]u8{ 0xf2, 0x59, 0x04, 0x99, 0x2a, 0x43 }, - }; + /// The current mode of the pointer device. + mode: *Mode, pub const Mode = extern struct { absolute_min_x: u64, absolute_min_y: u64, absolute_min_z: u64, + + /// If zero, the device does not support a x-axis. absolute_max_x: u64, + + /// If zero, the device does not support a y-axis. absolute_max_y: u64, + + /// If zero, the device does not support a z-axis. absolute_max_z: u64, + attributes: Attributes, pub const Attributes = packed struct(u32) { + /// Supports an alternate button input. supports_alt_active: bool, + + /// Returns pressure data as the z-axis. supports_pressure_as_z: bool, + _pad: u30 = 0, }; }; + /// Resets the pointer device hardware. + pub fn reset( + self: *const AbsolutePointer, + /// Indicates that the driver may perform a more exhaustive verification operation of the device during reset. + verify: bool, + ) !void { + try self._reset(self, verify).err(); + } + pub const State = extern struct { + /// Must be ignored when both `absolute_min_x` and `absolute_max_x` are zero. current_x: u64, + + /// Must be ignored when both `absolute_min_y` and `absolute_max_y` are zero. current_y: u64, + + /// Must be ignored when both `absolute_min_z` and `absolute_max_z` are zero. current_z: u64, active_buttons: ActiveButtons, pub const ActiveButtons = packed struct(u32) { + /// The touch sensor is active. touch_active: bool, + + /// The alt sensor is active, such as a pen side button. alt_active: bool, _pad: u30 = 0, }; }; + + /// Retrieves the current state of a pointer device. + pub fn getState(self: *const AbsolutePointer) !State { + var state: State = undefined; + try self._get_state(self, &state).err(); + return state; + } + + pub const guid align(8) = Guid{ + .time_low = 0x8d59d32b, + .time_mid = 0xc655, + .time_high_and_version = 0x4ae9, + .clock_seq_high_and_reserved = 0x9b, + .clock_seq_low = 0x15, + .node = [_]u8{ 0xf2, 0x59, 0x04, 0x99, 0x2a, 0x43 }, + }; }; diff --git a/lib/std/os/uefi/protocol/graphics_output.zig b/lib/std/os/uefi/protocol/graphics_output.zig index 155428c107b8..eafdb8a128b7 100644 --- a/lib/std/os/uefi/protocol/graphics_output.zig +++ b/lib/std/os/uefi/protocol/graphics_output.zig @@ -1,83 +1,232 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Guid = uefi.Guid; -const Status = uefi.Status; -const cc = uefi.cc; +const bits = @import("../bits.zig"); +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; + +/// Provides a basic abstraction to set video modes and copy pixels to and from the graphics controller’s frame +/// buffer. The linear address of the hardware frame buffer is also exposed so software can write directly to the +/// video hardware. pub const GraphicsOutput = extern struct { - _query_mode: *const fn (*const GraphicsOutput, u32, *usize, **Mode.Info) callconv(cc) Status, + _query_mode: *const fn (*const GraphicsOutput, u32, *usize, **const Mode.Info) callconv(cc) Status, _set_mode: *const fn (*const GraphicsOutput, u32) callconv(cc) Status, _blt: *const fn (*const GraphicsOutput, ?[*]BltPixel, BltOperation, usize, usize, usize, usize, usize, usize, usize) callconv(cc) Status, - mode: *Mode, - - /// Returns information for an available graphics mode that the graphics device and the set of active video output devices supports. - pub fn queryMode(self: *const GraphicsOutput, mode: u32, size_of_info: *usize, info: **Mode.Info) Status { - return self._query_mode(self, mode, size_of_info, info); - } - - /// Set the video device into the specified mode and clears the visible portions of the output display to black. - pub fn setMode(self: *const GraphicsOutput, mode: u32) Status { - return self._set_mode(self, mode); - } - - /// Blt a rectangle of pixels on the graphics screen. Blt stands for BLock Transfer. - pub fn blt(self: *const GraphicsOutput, blt_buffer: ?[*]BltPixel, blt_operation: BltOperation, source_x: usize, source_y: usize, destination_x: usize, destination_y: usize, width: usize, height: usize, delta: usize) Status { - return self._blt(self, blt_buffer, blt_operation, source_x, source_y, destination_x, destination_y, width, height, delta); - } - - pub const guid align(8) = Guid{ - .time_low = 0x9042a9de, - .time_mid = 0x23dc, - .time_high_and_version = 0x4a38, - .clock_seq_high_and_reserved = 0x96, - .clock_seq_low = 0xfb, - .node = [_]u8{ 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a }, - }; + mode: *const Mode, pub const Mode = extern struct { + /// The number of valid modes supported by `setMode()` and `queryMode()`. + /// + /// This will be one higher than the highest mode number supported. max_mode: u32, + + /// Current mode of the graphics device. mode: u32, - info: *Info, + + /// Pointer to this mode's mode information structure. + info: *const Info, + + /// Size of the info structure in bytes. This size may be increased in future revisions. size_of_info: usize, - frame_buffer_base: u64, + + /// Base address of graphics linear frame buffer. This field is only valid if the pixel mode is not `.blt_only`. + frame_buffer_base: bits.PhysicalAddress, + + /// The size in bytes of the graphics linear frame buffer. This is defined as + /// `pixels_per_scan_line` * vertical_resolution * pixelElementSize()`. frame_buffer_size: usize, pub const Info = extern struct { - version: u32, + /// The version of this data structure. A value of zero represents the this structure. Future version of + /// this specification may extend this data structure in a backwards compatible way and increase the + /// value of Version. + version: u32 = 0, + + /// The size of video screen in pixels in the X dimension. horizontal_resolution: u32, + + /// The size of video screen in pixels in the Y dimension. vertical_resolution: u32, + + /// The physical format of the pixel. pixel_format: PixelFormat, - pixel_information: PixelBitmask, + + /// When format is `.bitmask`, this field defines the mask of each color channel. + pixel_bitmask: PixelBitmask, + + /// The number of pixel elements per video memory line. This value must be used instead of + /// `horizontal_resolution` to calculate the offset to the next line. pixels_per_scan_line: u32, + + /// The size of a pixel element in bytes. + pub fn pixelElementSize(self: *const Info) usize { + switch (self.pixel_format) { + .rgb_8bit => return 4, + .bgr_8bit => return 4, + .bitmask => return @divExact(self.pixel_bitmask.bitSizeOf(), 8), + .blt_only => return 0, + } + } }; }; pub const PixelFormat = enum(u32) { - RedGreenBlueReserved8BitPerColor, - BlueGreenRedReserved8BitPerColor, - BitMask, - BltOnly, + /// A pixel is 32 bits, byte zero represents red, byte one represents green, byte two represents blue, and + /// byte three is reserved. The values range from the minimum intensity of 0 to maximum intensity of 255. + rgb_8bit, + + /// A pixel is 32 bits, byte zero represents blue, byte one represents green, byte two represents red, and + /// byte three is reserved. The values range from the minimum intensity of 0 to maximum intensity of 255. + bgr_8bit, + + /// The pixel format is defined by the `PixelBitmask` structure. + bitmask, + + /// This mode does not support a physical framebuffer. + blt_only, }; pub const PixelBitmask = extern struct { - red_mask: u32, - green_mask: u32, - blue_mask: u32, - reserved_mask: u32, + red: u32, + green: u32, + blue: u32, + reserved: u32, + + pub const PixelField = enum { + red, + green, + blue, + reserved, + }; + + /// Finds the size in bits of a single pixel. + pub fn bitSizeOf(self: *const PixelBitmask) usize { + const highest_red_bit = 32 - @clz(self.red_mask); + const highest_green_bit = 32 - @clz(self.green_mask); + const highest_blue_bit = 32 - @clz(self.blue_mask); + const highest_reserved_bit = 32 - @clz(self.reserved_mask); + + return @max(@max(highest_red_bit, highest_green_bit), @max(highest_blue_bit, highest_reserved_bit)); + } + + /// Finds the size in bits of a pixel field. + pub fn bitSizeOfField(self: *const PixelBitmask, field: PixelField) usize { + switch (field) { + .red => return @popCount(self.red_mask), + .green => return @popCount(self.green_mask), + .blue => return @popCount(self.blue_mask), + .reserved => return @popCount(self.reserved_mask), + } + } + + /// Finds the offset from zero (ie. a shift) in bits of a pixel field. + pub fn bitOffsetOfField(self: *const PixelBitmask, field: PixelField) usize { + switch (field) { + .red => return @ctz(self.red_mask), + .green => return @ctz(self.green_mask), + .blue => return @ctz(self.blue_mask), + .reserved => return @ctz(self.reserved_mask), + } + } + + /// Pulls the value of a pixel field out of a pixel. + pub fn getValue(self: *const PixelBitmask, field: PixelField, pixel_ptr: [*]const u8) u32 { + const pixel: *align(1) const u32 = @ptrCast(pixel_ptr); + + switch (field) { + .red => return (pixel.* & self.red_mask) >> @ctz(self.red_mask), + .green => return (pixel.* & self.green_mask) >> @ctz(self.green_mask), + .blue => return (pixel.* & self.blue_mask) >> @ctz(self.blue_mask), + .reserved => return (pixel.* & self.reserved_mask) >> @ctz(self.reserved_mask), + } + } }; + /// Returns information for an available graphics mode that the graphics device and the set of active video + /// output devices supports. + pub fn queryMode(self: *const GraphicsOutput, mode: u32) !*const Mode.Info { + var size: usize = undefined; + var info: *const Mode.Info = undefined; + try self._query_mode(self, mode, &size, &info).err(); + + if (size < @sizeOf(Mode.Info)) return error.Unsupported; + return info; + } + + /// Set the video device into the specified mode and clears the visible portions of the output display to black. + pub fn setMode(self: *const GraphicsOutput, mode: u32) !void { + try self._set_mode(self, mode).err(); + } + pub const BltPixel = extern struct { blue: u8, green: u8, red: u8, - reserved: u8 = undefined, + reserved: u8 = 0, }; pub const BltOperation = enum(u32) { - BltVideoFill, - BltVideoToBltBuffer, - BltBufferToVideo, - BltVideoToVideo, - GraphicsOutputBltOperationMax, + /// Write data from the `blt_buffer` pixel (0, 0) directly to every pixel of the video display rectangle + /// (`destination_x`, `destination_y`) (`destination_x + width`, `destination_y + height`). Only one pixel + /// will be used from the `blt_buffer`. `delta` is NOT used. + video_fill, + + /// Read data from the video display rectangle (`source_x`, `source_y`) (`source_x + width`, `source_y + height`) + /// and place it in the `blt_buffer` rectangle (`destination_x`, `destination_y`) (`destination_x + width`, + /// `destination_y + height`). If `destination_x` or `destination_y` is not zero then `delta` must be set to + /// the length in bytes of a row in the `blt_buffer`. + video_to_blt_buffer, + + /// Write data from the `blt_buffer` rectangle (`source_x`, `source_x`) (`source_x + width`, `source_y + height`) + /// directly to the video display rectangle (`destination_x`, `destination_y`) (`destination_x + width`, + /// `destination_y + height`). If `source_x` or `source_x` is not zero then `delta` must be set to the length + /// in bytes of a row in the `blt_buffer`. + blt_buffer_to_video, + + /// Copy from the video display rectangle (`source_x`, `source_y`) (`source_x + width`, `source_y + height`) + /// to the video display rectangle (`destination_x`, `destination_y`) (`destination_x + width`, + /// `destination_y + height`. The `blt_buffer` and `delta` are not used in this mode. There is no limitation + /// on the overlapping of the source and destination rectangles + video_to_video, + }; + + /// Blt a rectangle of pixels on the graphics screen. Blt stands for BLock Transfer. + pub fn blt( + self: *const GraphicsOutput, + /// The data to transfer to the graphics screen. Must be at least `width * height`. + blt_buffer: ?[*]BltPixel, + /// The operation to perform. + blt_operation: BltOperation, + /// The X coordinate of the source for the `blt_operation`. The origin of the screen is 0, 0 and that is the + /// upper left-hand corner of the screen + source_x: usize, + /// The Y coordinate of the source for the `blt_operation`. The origin of the screen is 0, 0 and that is the + /// upper left-hand corner of the screen. + source_y: usize, + /// The X coordinate of the destination for the `blt_operation`. The origin of the screen is 0, 0 and that is + /// the upper left-hand corner of the screen. + destination_x: usize, + /// The Y coordinate of the destination for the `blt_operation`. The origin of the screen is 0, 0 and that is + /// the upper left-hand corner of the screen. + destination_y: usize, + /// The width of a rectangle in the blt rectangle in pixels. + width: usize, + /// The height of a rectangle in the blt rectangle in pixels. + height: usize, + /// Not used for `.video_fill` or the `.video_to_video` operation. If a `delta` of zero is used, the entire + /// `blt_buffer` is being operated on. If a subrectangle of the `blt_buffer` is being used then `delta` + /// represents the number of bytes in a row of the `blt_buffer`. + delta: usize, + ) !void { + try self._blt(self, blt_buffer, blt_operation, source_x, source_y, destination_x, destination_y, width, height, delta).err(); + } + + pub const guid align(8) = Guid{ + .time_low = 0x9042a9de, + .time_mid = 0x23dc, + .time_high_and_version = 0x4a38, + .clock_seq_high_and_reserved = 0x96, + .clock_seq_low = 0xfb, + .node = [_]u8{ 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a }, }; }; diff --git a/lib/std/os/uefi/protocol/serial_io.zig b/lib/std/os/uefi/protocol/serial_io.zig new file mode 100644 index 000000000000..b2ab074e6547 --- /dev/null +++ b/lib/std/os/uefi/protocol/serial_io.zig @@ -0,0 +1,170 @@ +const bits = @import("../bits.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; + +/// This protocol is used to communicate with any type of character-based I/O device. +pub const SerialIo = extern struct { + revision: u32, + + _reset: *const fn (*const SerialIo) callconv(cc) Status, + _set_attributes: *const fn (*const SerialIo, u64, u32, u32, u32, u32, u32) callconv(cc) Status, + _set_control: *const fn (*const SerialIo, Control) callconv(cc) Status, + _get_control: *const fn (*const SerialIo, *Control) callconv(cc) Status, + _write: *const fn (*const SerialIo, *usize, [*]const u8) callconv(cc) Status, + _read: *const fn (*const SerialIo, *usize, [*]u8) callconv(cc) Status, + + /// The current mode of the pointer device. + mode: *Mode, + + /// The type of device, only present when `revision` is `0x00010001` or greater. + device_type: *const Guid, + + pub const Control = packed struct(u32) { + /// Can be set with `setControl()`. + data_terminal_ready: bool, + + /// Can be set with `setControl()`. + request_to_send: bool, + + _pad1: u2 = 0, + + clear_to_send: bool, + data_set_ready: bool, + ring_indicate: bool, + carrier_detect: bool, + input_buffer_empty: bool, + output_buffer_empty: bool, + + _pad2: u2 = 0, + + /// Can be set with `setControl()`. + hardware_loopback_enable: bool, + + /// Can be set with `setControl()`. + software_loopback_enable: bool, + + /// Can be set with `setControl()`. + hardware_flow_control_enable: bool, + + _pad3: u17 = 0, + }; + + pub const Mode = extern struct { + /// A value of true here means the field is supported. + control_mask: Control, + + /// If applicable, the number of microseconds to wait before timing out a Read or Write operation. + timeout: u32, + + /// If applicable, the current baud rate setting of the device; otherwise, baud rate has the value of zero to + /// indicate that device runs at the device’s designed speed. + baud_rate: u64, + + /// The number of characters the device will buffer on input. + receive_fifo_depth: u32, + + /// The number of data bits in each character. + data_bits: u32, + + /// If applicable, this is the parity that is computed or checked as each character is transmitted or + /// received. If the device does not support parity the value is the default parity value. + parity: bits.Parity, + + /// If applicable, the number of stop bits per character. If the device does not support stop bits the value + /// is the default stop bit value. + stop_bits: bits.StopBits, + }; + + /// Resets the pointer device hardware. + pub fn reset( + self: *const SerialIo, + ) !void { + try self._reset(self).err(); + } + + /// Sets the baud rate, receive FIFO depth, transmit/receive time out, parity, data bits, and stop bits on a + /// serial device. + pub fn setAttributes( + self: *const SerialIo, + /// The baud rate to use on the device. + baud_rate: u64, + /// The number of characters the device will buffer on input. + receive_fifo_depth: u32, + /// The timeout for a read or write operation in microseconds. + timeout: u32, + /// The parity setting to use on this device. + parity: bits.Parity, + /// The number of data bits to use on this device. + data_bits: u32, + /// The number of stop bits to use on this device. + stop_bits: bits.StopBits, + ) !void { + try self._set_attributes( + self, + baud_rate, + receive_fifo_depth, + timeout, + @intFromEnum(parity), + data_bits, + @intFromEnum(stop_bits), + ).err(); + } + + /// Sets the status of the control bits on a serial device. + pub fn setControl( + self: *const SerialIo, + control: Control, + ) !void { + try self._set_control(self, control).err(); + } + + /// Retrieves the status of the control bits on a serial device. + pub fn getControl( + self: *const SerialIo, + ) !Control { + var control: Control = undefined; + try self._get_control(self, &control).err(); + return control; + } + + /// Writes data to a serial device. + pub fn write( + self: *const SerialIo, + buffer: []const u8, + ) !usize { + var size: usize = buffer.len; + try self._write(self, &size, buffer.ptr).err(); + return size; + } + + /// Reads data from a serial device. + pub fn read( + self: *const SerialIo, + buffer: []u8, + ) !usize { + var size: usize = buffer.len; + try self._read(self, &size, buffer.ptr).err(); + return size; + } + + pub const guid align(8) = Guid{ + .time_low = 0xbb25cf6f, + .time_mid = 0xf1d4, + .time_high_and_version = 0x11d2, + .clock_seq_high_and_reserved = 0x9a, + .clock_seq_low = 0x0c, + .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0xfd }, + }; + + pub const terminal_device_guid align(8) = Guid{ + .time_low = 0x6ad9a60f, + .time_mid = 0x5815, + .time_high_and_version = 0x4c7c, + .clock_seq_high_and_reserved = 0x8a, + .clock_seq_low = 0x10, + .node = [_]u8{ 0x50, 0x53, 0xd2, 0xbf, 0x7a, 0x1b }, + }; +}; diff --git a/lib/std/os/uefi/protocol/simple_pointer.zig b/lib/std/os/uefi/protocol/simple_pointer.zig index ab7d1abc5849..2d5fec01f0e3 100644 --- a/lib/std/os/uefi/protocol/simple_pointer.zig +++ b/lib/std/os/uefi/protocol/simple_pointer.zig @@ -1,25 +1,70 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Event = uefi.Event; -const Guid = uefi.Guid; -const Status = uefi.Status; -const cc = uefi.cc; - -/// Protocol for mice. +const bits = @import("../bits.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; +const Event = bits.Event; + +/// Provides services that allow information about a pointer device to be retrieved. pub const SimplePointer = struct { _reset: *const fn (*const SimplePointer, bool) callconv(cc) Status, _get_state: *const fn (*const SimplePointer, *State) callconv(cc) Status, + + /// Event to use with `waitForEvent()` to wait for input from the pointer device. wait_for_input: Event, + + /// The current mode of the pointer device. mode: *Mode, + pub const Mode = extern struct { + /// The resolution of the pointer device in counts/mm. If zero, this device does not support an x-axis. + resolution_x: u64, + + /// The resolution of the pointer device in counts/mm. If zero, this device does not support a y-axis. + resolution_y: u64, + + /// The resolution of the pointer device in counts/mm. If zero, this device does not support a z-axis. + resolution_z: u64, + + /// When true, a left button is present on the pointer device. + left_button: bool, + + /// When true, a right button is present on the pointer device. + right_button: bool, + }; + /// Resets the pointer device hardware. - pub fn reset(self: *const SimplePointer, verify: bool) Status { - return self._reset(self, verify); + pub fn reset( + self: *const SimplePointer, + /// Indicates that the driver may perform a more exhaustive verification operation of the device during reset. + verify: bool, + ) !void { + try self._reset(self, verify).err(); } + pub const State = extern struct { + /// Relative distance moved in counts. Must be ignored when `mode.resolution_x` is zero. + relative_movement_x: i32, + + /// Relative distance moved in counts. Must be ignored when `mode.resolution_y` is zero. + relative_movement_y: i32, + + /// Relative distance moved in counts. Must be ignored when `mode.resolution_z` is zero. + relative_movement_z: i32, + + /// When true, the left button is pressed. Must be ignored when `mode.left_button` is false. + left_button: bool, + + /// When true, the right button is pressed. Must be ignored when `mode.right_button` is false. + right_button: bool, + }; + /// Retrieves the current state of a pointer device. - pub fn getState(self: *const SimplePointer, state: *State) Status { - return self._get_state(self, state); + pub fn getState(self: *const SimplePointer) !State { + var state: State = undefined; + try self._get_state(self, &state).err(); + return state; } pub const guid align(8) = Guid{ @@ -30,20 +75,4 @@ pub const SimplePointer = struct { .clock_seq_low = 0x4f, .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, }; - - pub const Mode = struct { - resolution_x: u64, - resolution_y: u64, - resolution_z: u64, - left_button: bool, - right_button: bool, - }; - - pub const State = struct { - relative_movement_x: i32, - relative_movement_y: i32, - relative_movement_z: i32, - left_button: bool, - right_button: bool, - }; }; diff --git a/lib/std/os/uefi/protocol/simple_text_input.zig b/lib/std/os/uefi/protocol/simple_text_input.zig index e6091b93b762..45583f2c31b7 100644 --- a/lib/std/os/uefi/protocol/simple_text_input.zig +++ b/lib/std/os/uefi/protocol/simple_text_input.zig @@ -1,9 +1,11 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Event = uefi.Event; -const Guid = uefi.Guid; -const Status = uefi.Status; -const cc = uefi.cc; +const bits = @import("../bits.zig"); +const protocol = @import("../protocol.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; +const Event = bits.Event; /// Character input devices, e.g. Keyboard pub const SimpleTextInput = extern struct { @@ -12,13 +14,20 @@ pub const SimpleTextInput = extern struct { wait_for_key: Event, /// Resets the input device hardware. - pub fn reset(self: *const SimpleTextInput, verify: bool) Status { - return self._reset(self, verify); + pub fn reset( + self: *const SimpleTextInput, + /// Indicates that the driver may perform a more exhaustive verification operation of + /// the device during reset. + verify: bool, + ) !void { + try self._reset(self, verify).err(); } /// Reads the next keystroke from the input device. - pub fn readKeyStroke(self: *const SimpleTextInput, input_key: *Key.Input) Status { - return self._read_key_stroke(self, input_key); + pub fn readKeyStroke(self: *const SimpleTextInput) !Key.Input { + var input_key: Key.Input = undefined; + try self._read_key_stroke(self, &input_key).err(); + return input_key; } pub const guid align(8) = Guid{ @@ -30,5 +39,5 @@ pub const SimpleTextInput = extern struct { .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, }; - pub const Key = uefi.protocol.SimpleTextInputEx.Key; + pub const Key = protocol.SimpleTextInputEx.Key; }; diff --git a/lib/std/os/uefi/protocol/simple_text_input_ex.zig b/lib/std/os/uefi/protocol/simple_text_input_ex.zig index 593321e130f1..3c11cf78419f 100644 --- a/lib/std/os/uefi/protocol/simple_text_input_ex.zig +++ b/lib/std/os/uefi/protocol/simple_text_input_ex.zig @@ -1,41 +1,64 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Event = uefi.Event; -const Guid = uefi.Guid; -const Status = uefi.Status; -const cc = uefi.cc; - -/// Character input devices, e.g. Keyboard +const bits = @import("../bits.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; +const Event = bits.Event; + +/// The Simple Text Input Ex protocol defines an extension to the Simple Text Input protocol +/// which enables various new capabilities. pub const SimpleTextInputEx = extern struct { _reset: *const fn (*const SimpleTextInputEx, bool) callconv(cc) Status, _read_key_stroke_ex: *const fn (*const SimpleTextInputEx, *Key) callconv(cc) Status, wait_for_key_ex: Event, - _set_state: *const fn (*const SimpleTextInputEx, *const u8) callconv(cc) Status, - _register_key_notify: *const fn (*const SimpleTextInputEx, *const Key, *const fn (*const Key) callconv(cc) usize, **anyopaque) callconv(cc) Status, - _unregister_key_notify: *const fn (*const SimpleTextInputEx, *const anyopaque) callconv(cc) Status, + _set_state: *const fn (*const SimpleTextInputEx, *const Key.State.Toggle) callconv(cc) Status, + _register_key_notify: *const fn (*const SimpleTextInputEx, *Key, *const fn (*const Key) callconv(cc) usize, *NotifyHandle) callconv(cc) Status, + _unregister_key_notify: *const fn (*const SimpleTextInputEx, NotifyHandle) callconv(cc) Status, /// Resets the input device hardware. - pub fn reset(self: *const SimpleTextInputEx, verify: bool) Status { - return self._reset(self, verify); + pub fn reset( + self: *const SimpleTextInputEx, + /// Indicates that the driver may perform a more exhaustive verification operation of the device during reset. + verify: bool, + ) !void { + try self._reset(self, verify).err(); } /// Reads the next keystroke from the input device. - pub fn readKeyStrokeEx(self: *const SimpleTextInputEx, key_data: *Key) Status { - return self._read_key_stroke_ex(self, key_data); + pub fn readKeyStrokeEx(self: *const SimpleTextInputEx) !Key { + var key_data: Key = undefined; + try self._read_key_stroke_ex(self, &key_data).err(); + return key_data; } /// Set certain state for the input device. - pub fn setState(self: *const SimpleTextInputEx, state: *const u8) Status { - return self._set_state(self, state); + pub fn setState(self: *const SimpleTextInputEx, state: Key.State.Toggle) !void { + try self._set_state(self, &state).err(); } + pub const NotifyFn = *const fn (*const Key) callconv(cc) Status; + pub const NotifyHandle = *const opaque {}; + /// Register a notification function for a particular keystroke for the input device. - pub fn registerKeyNotify(self: *const SimpleTextInputEx, key_data: *const Key, notify: *const fn (*const Key) callconv(cc) usize, handle: **anyopaque) Status { - return self._register_key_notify(self, key_data, notify, handle); + pub fn registerKeyNotify( + self: *const SimpleTextInputEx, + /// Buffer that is filled with keystroke information + key_data: *Key, + /// Pointer to function to be called for a key press. + notify: NotifyFn, + ) !NotifyHandle { + var handle: NotifyHandle = undefined; + try self._register_key_notify(self, key_data, notify, &handle).err(); + return handle; } /// Remove the notification that was previously registered. - pub fn unregisterKeyNotify(self: *const SimpleTextInputEx, handle: *const anyopaque) Status { + pub fn unregisterKeyNotify( + self: *const SimpleTextInputEx, + /// The handle of the notification function being unregistered. + handle: NotifyHandle, + ) Status { return self._unregister_key_notify(self, handle); } @@ -68,6 +91,8 @@ pub const SimpleTextInputEx = extern struct { menu_key_pressed: bool, sys_req_pressed: bool, _pad: u21 = 0, + + /// This bitfield is only valid when this is true. shift_state_valid: bool, }; @@ -76,7 +101,11 @@ pub const SimpleTextInputEx = extern struct { num_lock_active: bool, caps_lock_active: bool, _pad: u3 = 0, + + /// When true, this instance of the protocol supports partial keystrokes. key_state_exposed: bool, + + /// This bitfield is only valid when this is true. toggle_state_valid: bool, }; }; diff --git a/lib/std/os/uefi/protocol/simple_text_output.zig b/lib/std/os/uefi/protocol/simple_text_output.zig index 3c46be3d6a4c..6341c3a27268 100644 --- a/lib/std/os/uefi/protocol/simple_text_output.zig +++ b/lib/std/os/uefi/protocol/simple_text_output.zig @@ -1,8 +1,11 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Guid = uefi.Guid; -const Status = uefi.Status; -const cc = uefi.cc; +const std = @import("../../../std.zig"); +const bits = @import("../bits.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; +const Event = bits.Event; /// Character output devices pub const SimpleTextOutput = extern struct { @@ -15,51 +18,128 @@ pub const SimpleTextOutput = extern struct { _clear_screen: *const fn (*const SimpleTextOutput) callconv(cc) Status, _set_cursor_position: *const fn (*const SimpleTextOutput, usize, usize) callconv(cc) Status, _enable_cursor: *const fn (*const SimpleTextOutput, bool) callconv(cc) Status, + + ///The mode information for this instance of the protocol. mode: *Mode, + // the numbers here are specified as "int32", which implies signed, but signed values make no sense here. + pub const Mode = extern struct { + /// The number of modes supported by `queryMode()` and `setMode()`. + max_mode: u32, + + /// The current mode. + mode: u32, + attribute: Attribute, + cursor_column: u32, + cursor_row: u32, + cursor_visible: bool, + }; + /// Resets the text output device hardware. - pub fn reset(self: *const SimpleTextOutput, verify: bool) Status { - return self._reset(self, verify); + pub fn reset( + self: *const SimpleTextOutput, + /// Indicates that the driver may perform a more exhaustive verification operation of + /// the device during reset. + verify: bool, + ) !void { + try self._reset(self, verify).err(); } /// Writes a string to the output device. - pub fn outputString(self: *const SimpleTextOutput, msg: [*:0]const u16) Status { - return self._output_string(self, msg); + pub fn outputString( + self: *const SimpleTextOutput, + /// The Null-terminated string to be displayed on the output device(s). + msg: [:0]const u16, + ) !void { + try self._output_string(self, msg.ptr).err(); } /// Verifies that all characters in a string can be output to the target device. - pub fn testString(self: *const SimpleTextOutput, msg: [*:0]const u16) Status { - return self._test_string(self, msg); + pub fn testString( + self: *const SimpleTextOutput, + msg: [:0]const u16, + ) bool { + return self._test_string(self, msg.ptr) == .success; } + /// The geometry of a output mode. + pub const ModeGeometry = struct { + rows: usize, + columns: usize, + }; + /// Returns information for an available text mode that the output device(s) supports. - pub fn queryMode(self: *const SimpleTextOutput, mode_number: usize, columns: *usize, rows: *usize) Status { - return self._query_mode(self, mode_number, columns, rows); + pub fn queryMode( + self: *const SimpleTextOutput, + /// The mode number to return information on. + mode_number: usize, + ) !ModeGeometry { + var info: ModeGeometry = undefined; + try self._query_mode(self, mode_number, &info.columns, &info.rows).err(); + return info; } /// Sets the output device(s) to a specified mode. - pub fn setMode(self: *const SimpleTextOutput, mode_number: usize) Status { - return self._set_mode(self, mode_number); + pub fn setMode( + self: *const SimpleTextOutput, + /// The text mode to set. + mode_number: usize, + ) !void { + try self._set_mode(self, mode_number).err(); } + pub const Attribute = packed struct(usize) { + pub const Color = enum(u3) { + black, + blue, + green, + cyan, + red, + magenta, + brown, + lightgray, + }; + + foreground: Color, + + /// If true, `foreground` will use the bright variant of the color + foreground_bright: bool, + background: Color, + + // this is a usize-sized bitfield, so we have to fill in the padding programmatically + reserved: std.meta.Int(.unsigned, @bitSizeOf(usize) - 7), + }; + /// Sets the background and foreground colors for the outputString() and clearScreen() functions. - pub fn setAttribute(self: *const SimpleTextOutput, attribute: usize) Status { - return self._set_attribute(self, attribute); + pub fn setAttribute( + self: *const SimpleTextOutput, + attribute: Attribute, + ) !void { + try self._set_attribute(self, attribute).err(); } /// Clears the output device(s) display to the currently selected background color. - pub fn clearScreen(self: *const SimpleTextOutput) Status { - return self._clear_screen(self); + pub fn clearScreen(self: *const SimpleTextOutput) !void { + try self._clear_screen(self).err(); } /// Sets the current coordinates of the cursor position. - pub fn setCursorPosition(self: *const SimpleTextOutput, column: usize, row: usize) Status { - return self._set_cursor_position(self, column, row); + pub fn setCursorPosition( + self: *const SimpleTextOutput, + /// The column to move to. Must be less than the columns in the geometry of the mode. + column: usize, + /// The row to move to. Must be less than the rows in the geometry of the mode. + row: usize, + ) !void { + try self._set_cursor_position(self, column, row).err(); } /// Makes the cursor visible or invisible. - pub fn enableCursor(self: *const SimpleTextOutput, visible: bool) Status { - return self._enable_cursor(self, visible); + pub fn enableCursor( + self: *const SimpleTextOutput, + visible: bool, + ) !void { + try self._enable_cursor(self, visible).err(); } pub const guid align(8) = Guid{ @@ -118,38 +198,4 @@ pub const SimpleTextOutput = extern struct { pub const geometricshape_left_triangle: u16 = 0x25c4; pub const arrow_up: u16 = 0x2591; pub const arrow_down: u16 = 0x2593; - pub const black: u8 = 0x00; - pub const blue: u8 = 0x01; - pub const green: u8 = 0x02; - pub const cyan: u8 = 0x03; - pub const red: u8 = 0x04; - pub const magenta: u8 = 0x05; - pub const brown: u8 = 0x06; - pub const lightgray: u8 = 0x07; - pub const bright: u8 = 0x08; - pub const darkgray: u8 = 0x08; - pub const lightblue: u8 = 0x09; - pub const lightgreen: u8 = 0x0a; - pub const lightcyan: u8 = 0x0b; - pub const lightred: u8 = 0x0c; - pub const lightmagenta: u8 = 0x0d; - pub const yellow: u8 = 0x0e; - pub const white: u8 = 0x0f; - pub const background_black: u8 = 0x00; - pub const background_blue: u8 = 0x10; - pub const background_green: u8 = 0x20; - pub const background_cyan: u8 = 0x30; - pub const background_red: u8 = 0x40; - pub const background_magenta: u8 = 0x50; - pub const background_brown: u8 = 0x60; - pub const background_lightgray: u8 = 0x70; - - pub const Mode = extern struct { - max_mode: u32, // specified as signed - mode: u32, // specified as signed - attribute: i32, - cursor_column: i32, - cursor_row: i32, - cursor_visible: bool, - }; }; From 9fd071fde5eeec21172ef86f6b18da91c154a14f Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 28 Dec 2023 16:24:15 -0600 Subject: [PATCH 06/23] std.os.uefi: file protocols and improve std bindings --- lib/std/builtin.zig | 48 +---- lib/std/fs/File.zig | 4 + lib/std/io.zig | 12 ++ lib/std/os/uefi.zig | 92 +++++++- lib/std/os/uefi/bits.zig | 81 +++++-- lib/std/os/uefi/device_path.zig | 38 ++-- lib/std/os/uefi/protocol.zig | 203 ++++++++++++++++-- lib/std/os/uefi/protocol/block_io.zig | 81 ------- .../{ => console}/absolute_pointer.zig | 8 +- .../os/uefi/protocol/{ => console}/edid.zig | 38 ++-- .../{ => console}/graphics_output.zig | 87 ++++++-- .../uefi/protocol/{ => console}/serial_io.zig | 14 +- .../protocol/{ => console}/simple_pointer.zig | 8 +- .../{ => console}/simple_text_input.zig | 19 +- .../{ => console}/simple_text_input_ex.zig | 14 +- .../{ => console}/simple_text_output.zig | 30 +-- lib/std/os/uefi/protocol/device_path.zig | 126 +++++------ lib/std/os/uefi/protocol/file.zig | 144 ------------- lib/std/os/uefi/protocol/loaded_image.zig | 39 ++-- lib/std/os/uefi/protocol/media/block_io.zig | 113 ++++++++++ lib/std/os/uefi/protocol/media/file.zig | 188 ++++++++++++++++ lib/std/os/uefi/protocol/media/load_file.zig | 53 +++++ .../protocol/media/simple_file_system.zig | 29 +++ lib/std/os/uefi/protocol/shell_parameters.zig | 15 +- .../os/uefi/protocol/simple_file_system.zig | 24 --- lib/std/os/uefi/table.zig | 8 +- lib/std/os/uefi/table/boot_services.zig | 29 ++- lib/std/os/uefi/table/configuration.zig | 12 +- lib/std/os/uefi/table/system.zig | 2 +- lib/std/posix.zig | 2 +- 30 files changed, 1038 insertions(+), 523 deletions(-) delete mode 100644 lib/std/os/uefi/protocol/block_io.zig rename lib/std/os/uefi/protocol/{ => console}/absolute_pointer.zig (90%) rename lib/std/os/uefi/protocol/{ => console}/edid.zig (67%) rename lib/std/os/uefi/protocol/{ => console}/graphics_output.zig (74%) rename lib/std/os/uefi/protocol/{ => console}/serial_io.zig (89%) rename lib/std/os/uefi/protocol/{ => console}/simple_pointer.zig (90%) rename lib/std/os/uefi/protocol/{ => console}/simple_text_input.zig (62%) rename lib/std/os/uefi/protocol/{ => console}/simple_text_input_ex.zig (86%) rename lib/std/os/uefi/protocol/{ => console}/simple_text_output.zig (87%) delete mode 100644 lib/std/os/uefi/protocol/file.zig create mode 100644 lib/std/os/uefi/protocol/media/block_io.zig create mode 100644 lib/std/os/uefi/protocol/media/file.zig create mode 100644 lib/std/os/uefi/protocol/media/load_file.zig create mode 100644 lib/std/os/uefi/protocol/media/simple_file_system.zig delete mode 100644 lib/std/os/uefi/protocol/simple_file_system.zig diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 6f6e0c97fc0f..208a621eefe3 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -787,46 +787,16 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr .uefi => { const uefi = std.os.uefi; - const ExitData = struct { - pub fn create_exit_data(exit_msg: []const u8, exit_size: *usize) ![*:0]u16 { - // Need boot services for pool allocation - if (uefi.system_table.boot_services == null) { - return error.BootServicesUnavailable; - } - - // ExitData buffer must be allocated using boot_services.allocatePool - var utf16: []u16 = try uefi.raw_pool_allocator.alloc(u16, 256); - errdefer uefi.raw_pool_allocator.free(utf16); - - if (exit_msg.len > 255) { - return error.MessageTooLong; - } - - var fmt: [256]u8 = undefined; - const slice = try std.fmt.bufPrint(&fmt, "\r\nerr: {s}\r\n", .{exit_msg}); - const len = try std.unicode.utf8ToUtf16Le(utf16, slice); - - utf16[len] = 0; - - exit_size.* = 256; - - return @as([*:0]u16, @ptrCast(utf16.ptr)); - } - }; - - var exit_size: usize = 0; - const exit_data = ExitData.create_exit_data(msg, &exit_size) catch null; - - if (exit_data) |data| { - if (uefi.system_table.std_err) |out| { - _ = out.setAttribute(uefi.protocol.SimpleTextOutput.red); - _ = out.outputString(data); - _ = out.setAttribute(uefi.protocol.SimpleTextOutput.white); - } - } - if (uefi.system_table.boot_services) |bs| { - _ = bs.exit(uefi.handle, .Aborted, exit_size, exit_data); + uefi.system_table.std_err.?.setAttribute(.{ .foreground = .red }) catch {}; + std.debug.print("{s}", .{msg}); + uefi.system_table.std_err.?.setAttribute(.{}) catch {}; + + if (std.unicode.utf8ToUtf16LeWithNull(uefi.raw_pool_allocator, msg)) |data| { + _ = bs.exit(uefi.handle, .aborted, data[0 .. data.len + 1]); + } else |_| { + _ = bs.exit(uefi.handle, .aborted, null); + } } // Didn't have boot_services, just fallback to whatever. diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 0b5058c5fe3c..daac1b632b85 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -255,6 +255,10 @@ pub fn supportsAnsiEscapeCodes(self: File) bool { return self.isCygwinPty(); } + if (builtin.os.tag == .uefi) { + // The UEFI console only supports a tiny subset of ANSI escape codes, primarily for color. + return false; + } if (builtin.os.tag == .wasi) { // WASI sanitizes stdout when fd is a tty so ANSI escape codes // will not be interpreted as actual cursor commands, and diff --git a/lib/std/io.zig b/lib/std/io.zig index 9f0f444a8365..f60703b871d2 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -27,6 +27,10 @@ fn getStdOutHandle() posix.fd_t { return root.os.io.getStdOutHandle(); } + if (builtin.os.tag == .uefi) { + return .{ .simple_output = os.uefi.system_table.con_out.? }; + } + return posix.STDOUT_FILENO; } @@ -47,6 +51,10 @@ fn getStdErrHandle() posix.fd_t { return root.os.io.getStdErrHandle(); } + if (builtin.os.tag == .uefi) { + return .{ .simple_output = os.uefi.system_table.std_err.? }; + } + return posix.STDERR_FILENO; } @@ -67,6 +75,10 @@ fn getStdInHandle() posix.fd_t { return root.os.io.getStdInHandle(); } + if (builtin.os.tag == .uefi) { + return .{ .simple_input = os.uefi.system_table.con_in.? }; + } + return posix.STDIN_FILENO; } diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 5eeba1cdbd0d..249d56be5c13 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -1,19 +1,19 @@ +const builtin = @import("builtin"); const std = @import("../std.zig"); /// A protocol is an interface identified by a GUID. pub const protocol = @import("uefi/protocol.zig"); -pub const DevicePath = @import("uefi/device_path.zig").DevicePath; pub const hii = @import("uefi/hii.zig"); pub const bits = @import("uefi/bits.zig"); /// Status codes returned by EFI interfaces pub const Status = @import("uefi/status.zig").Status; -pub const tables = @import("uefi/tables.zig"); +pub const table = @import("uefi/table.zig"); /// The memory type to allocate when using the pool /// Defaults to .LoaderData, the default data allocation type /// used by UEFI applications to allocate pool memory. -pub var efi_pool_memory_type: tables.MemoryType = .LoaderData; +pub var efi_pool_memory_type: bits.MemoryDescriptor.Type = .LoaderData; pub const pool_allocator = @import("uefi/pool_allocator.zig").pool_allocator; pub const raw_pool_allocator = @import("uefi/pool_allocator.zig").raw_pool_allocator; @@ -21,9 +21,91 @@ pub const raw_pool_allocator = @import("uefi/pool_allocator.zig").raw_pool_alloc pub var handle: bits.Handle = undefined; /// A pointer to the EFI System Table that is passed to the EFI image's entry point. -pub var system_table: *tables.SystemTable = undefined; +pub var system_table: *table.System = undefined; + +pub const fd_t = union(enum) { + file: *const protocol.File, + simple_output: *const protocol.SimpleTextOutput, + simple_input: *const protocol.SimpleTextInput, +}; + +pub fn close(fd: fd_t) void { + switch (fd) { + .file => |p| p.close(fd.file), + .simple_output => |p| p.reset(true) catch {}, + .simple_input => |p| p.reset(true) catch {}, + } +} + +pub const ReadError = Status.EfiError; + +pub fn read(fd: fd_t, buf: []u8) ReadError!usize { + switch (fd) { + .file => |p| p.read(fd.file, buf), + .simple_input => |p| { + var index: usize = 0; + while (index == 0) { + while (try p.readKeyStroke()) |key| { + if (key.unicodeChar != 0) { + index += try std.unicode.utf16leToUtf8(buf, &.{key.unicode_char}); + } + } + } + return index; + }, + else => return error.EndOfFile, + } +} + +pub const WriteError = Status.EfiError || error{InvalidUtf8}; + +pub fn write(fd: fd_t, buf: []const u8) WriteError!usize { + switch (fd) { + .file => |p| p.write(fd.file, buf), + .simple_output => |p| { + const view = try std.unicode.Utf8View.init(buf); + var iter = view.iterator(); + + // rudimentary utf16 writer + var index: usize = 0; + var utf16: [256]u16 = undefined; + while (iter.nextCodepoint()) |rune| { + if (index + 1 >= utf16.len) { + utf16[index] = 0; + try p.outputString(utf16[0..index :0]); + index = 0; + } + + if (rune < 0x10000) { + utf16[index] = rune; + index += 1; + } else { + const high = @as(u16, @intCast((rune - 0x10000) >> 10)) + 0xD800; + const low = @as(u16, @intCast(rune & 0x3FF)) + 0xDC00; + switch (builtin.cpu.arch.endian()) { + .little => { + utf16[index] = high; + utf16[index] = low; + }, + .big => { + utf16[index] = low; + utf16[index] = high; + }, + } + index += 2; + } + } + + if (index != 0) { + utf16[index] = 0; + try p.outputString(utf16[0..index :0]); + } + }, + else => return error.EndOfFile, + } +} test { - _ = tables; + _ = table; _ = protocol; } diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig index 26ef748da4e3..0a7fd626989a 100644 --- a/lib/std/os/uefi/bits.zig +++ b/lib/std/os/uefi/bits.zig @@ -14,6 +14,9 @@ pub const PhysicalAddress = u64; /// A pointer in virtual address space. pub const VirtualAddress = u64; +/// A logical block address +pub const LogicalBlockAddress = u64; + /// An EFI Handle represents a collection of related interfaces. pub const Handle = *const opaque {}; @@ -362,26 +365,47 @@ pub const StopBits = enum(u8) { _, }; +/// Provides generic file information. pub const FileInfo = extern struct { size: u64, + + /// The size of the file in bytes. file_size: u64, + + /// The amount of physical space the file consumes of the file system volume. physical_size: u64, + + /// The time the file was created. create_time: Time, + + /// The time the file was last accessed. last_access_time: Time, + + /// The time the file's contents were last modified. modification_time: Time, - attribute: u64, - pub fn getFileName(self: *const FileInfo) [*:0]const u16 { - return @ptrCast(@alignCast(@as([*]const u8, @ptrCast(self)) + @sizeOf(FileInfo))); + /// The attributes of the file. + attribute: Attributes, + + /// The null-terminated name of the file. For the root directory, this is an empty string. + pub fn getFileName(self: *const FileInfo) [:0]const u16 { + const ptr: [*:0]const u16 = @ptrCast(self); + const file_name = ptr + @divExact(@sizeOf(FileInfo), 2); + + return std.mem.span(file_name); } - pub const efi_file_read_only: u64 = 0x0000000000000001; - pub const efi_file_hidden: u64 = 0x0000000000000002; - pub const efi_file_system: u64 = 0x0000000000000004; - pub const efi_file_reserved: u64 = 0x0000000000000008; - pub const efi_file_directory: u64 = 0x0000000000000010; - pub const efi_file_archive: u64 = 0x0000000000000020; - pub const efi_file_valid_attr: u64 = 0x0000000000000037; + /// The attributes of a file. + pub const Attributes = packed struct(u64) { + read_only: bool = true, + hidden: bool = false, + system: bool = false, + reserved: bool = false, + directory: bool = false, + archive: bool = false, + + _pad: u58 = 0, + }; pub const guid align(8) = Guid{ .time_low = 0x09576e92, @@ -393,16 +417,28 @@ pub const FileInfo = extern struct { }; }; +/// Provides information about the system volume and volume label. pub const FileSystemInfo = extern struct { size: u64, + + /// If true, the file system is read-only. read_only: bool, + + /// The number of bytes managed by the file system. volume_size: u64, + + /// The number of available bytes for use by the file system. free_space: u64, + + /// The nominal block size by which files are typically grown. block_size: u32, - _volume_label: u16, - pub fn getVolumeLabel(self: *const FileSystemInfo) [*:0]const u16 { - return @as([*:0]const u16, @ptrCast(&self._volume_label)); + /// The null-terminated string that is the volume's label. + pub fn getVolumeLabel(self: *const FileSystemInfo) [:0]const u16 { + const ptr: [*:0]const u16 = @ptrCast(self); + const file_name = ptr + @divExact(@sizeOf(FileSystemInfo), 2); + + return std.mem.span(file_name); } pub const guid align(8) = Guid{ @@ -414,3 +450,22 @@ pub const FileSystemInfo = extern struct { .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, }; }; + +/// Provides information about the volume label. Can safely be cast to and from `[*:0]const u16`. +pub const FileSystemVolumeLabel = extern struct { + /// The null-terminated string that is the volume's label. + pub fn getVolumeLabel(self: *const FileSystemVolumeLabel) [:0]const u16 { + const ptr: [*:0]const u16 = @ptrCast(self); + + return std.mem.span(ptr); + } + + pub const guid align(8) = Guid{ + .time_low = 0xdb47d7d3, + .time_mid = 0xfe81, + .time_high_and_version = 0x11d3, + .clock_seq_high_and_reserved = 0x9a, + .clock_seq_low = 0x35, + .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, + }; +}; diff --git a/lib/std/os/uefi/device_path.zig b/lib/std/os/uefi/device_path.zig index c2277929d337..5c616b55d50a 100644 --- a/lib/std/os/uefi/device_path.zig +++ b/lib/std/os/uefi/device_path.zig @@ -5,7 +5,7 @@ const Guid = bits.Guid; const assert = std.debug.assert; -pub const DevicePath = union(Type) { +pub const DevicePathNode = union(Type) { hardware: Hardware, acpi: Acpi, messaging: Messaging, @@ -23,7 +23,15 @@ pub const DevicePath = union(Type) { _, }; - pub const Any = extern struct { + pub fn toGeneric(self: *const DevicePathNode) *const Generic { + switch (self) { + inline else => |typ| switch (typ) { + inline else => |subtype| return @ptrCast(subtype), + }, + } + } + + pub const Generic = extern struct { type: Type, subtype: u8, length: u16 align(1), @@ -364,7 +372,7 @@ pub const DevicePath = union(Type) { ipv6: *const Ipv6, vlan: *const Vlan, infiniband: *const InfiniBand, - uart: *const UartDevicePath, + uart: *const Uart, vendor: *const Vendor, scsi_extended: *const ScsiExtended, iscsi: *const Iscsi, @@ -904,7 +912,7 @@ pub const DevicePath = union(Type) { } }; - pub const UartDevicePath = extern struct { + pub const Uart = extern struct { type: Type = .messaging, subtype: Subtype = .uart, length: u16 align(1) = 19, @@ -923,17 +931,17 @@ pub const DevicePath = union(Type) { stop_bits: bits.StopBits, comptime { - assert(19 == @sizeOf(UartDevicePath)); - assert(1 == @alignOf(UartDevicePath)); - - assert(0 == @offsetOf(UartDevicePath, "type")); - assert(1 == @offsetOf(UartDevicePath, "subtype")); - assert(2 == @offsetOf(UartDevicePath, "length")); - assert(4 == @offsetOf(UartDevicePath, "reserved")); - assert(8 == @offsetOf(UartDevicePath, "baud_rate")); - assert(16 == @offsetOf(UartDevicePath, "data_bits")); - assert(17 == @offsetOf(UartDevicePath, "parity")); - assert(18 == @offsetOf(UartDevicePath, "stop_bits")); + assert(19 == @sizeOf(Uart)); + assert(1 == @alignOf(Uart)); + + assert(0 == @offsetOf(Uart, "type")); + assert(1 == @offsetOf(Uart, "subtype")); + assert(2 == @offsetOf(Uart, "length")); + assert(4 == @offsetOf(Uart, "reserved")); + assert(8 == @offsetOf(Uart, "baud_rate")); + assert(16 == @offsetOf(Uart, "data_bits")); + assert(17 == @offsetOf(Uart, "parity")); + assert(18 == @offsetOf(Uart, "stop_bits")); } }; diff --git a/lib/std/os/uefi/protocol.zig b/lib/std/os/uefi/protocol.zig index 4f63cb814a95..5e2c43de9092 100644 --- a/lib/std/os/uefi/protocol.zig +++ b/lib/std/os/uefi/protocol.zig @@ -1,36 +1,215 @@ + +// ** UEFI Specification Version 2.10, August 29, 2022 + +// 3.2 BootManagerPolicy + +// 9. Loaded Image pub const LoadedImage = @import("protocol/loaded_image.zig").LoadedImage; + +// 10. Device Path pub const DevicePath = @import("protocol/device_path.zig").DevicePath; -pub const Rng = @import("protocol/rng.zig").Rng; -pub const ShellParameters = @import("protocol/shell_parameters.zig").ShellParameters; -pub const SimpleFileSystem = @import("protocol/simple_file_system.zig").SimpleFileSystem; -pub const File = @import("protocol/file.zig").File; -pub const BlockIo = @import("protocol/block_io.zig").BlockIo; +// 12. Console Support +pub const SimpleTextInputEx = @import("protocol/console/simple_text_input_ex.zig").SimpleTextInputEx; +pub const SimpleTextInput = @import("protocol/console/simple_text_input.zig").SimpleTextInput; +pub const SimpleTextOutput = @import("protocol/console/simple_text_output.zig").SimpleTextOutput; +pub const SimplePointer = @import("protocol/console/simple_pointer.zig").SimplePointer; +pub const AbsolutePointer = @import("protocol/console/absolute_pointer.zig").AbsolutePointer; +pub const SerialIo = @import("protocol/console/serial_io.zig").SerialIo; +pub const GraphicsOutput = @import("protocol/console/graphics_output.zig").GraphicsOutput; +pub const edid = @import("protocol/console/edid.zig"); + +/// 13. Media Access +pub const LoadFile = @import("protocol/media/load_file.zig").LoadFile; +pub const SimpleFileSystem = @import("protocol/media/simple_file_system.zig").SimpleFileSystem; +pub const File = @import("protocol/media/file.zig").File; +// TapeIo +// DiskIo +// DiskIo2 +pub const BlockIo = @import("protocol/media/block_io.zig").BlockIo; +// BlockIo2 +// BlockIoCrypto +// EraseBlock +// AtaPassThrough +// StorageSecurityCommand +// NvmExpressPassThrough +// SdmmcPassThrough +// RamDisk +// PartitionInfo +// NvdimmLabel +// UfsDeviceConfig -pub const SimpleTextInput = @import("protocol/simple_text_input.zig").SimpleTextInput; -pub const SimpleTextInputEx = @import("protocol/simple_text_input_ex.zig").SimpleTextInputEx; -pub const SimpleTextOutput = @import("protocol/simple_text_output.zig").SimpleTextOutput; +// 14. PCI +// PciRootBridgeIo +// PciIo -pub const SimplePointer = @import("protocol/simple_pointer.zig").SimplePointer; -pub const AbsolutePointer = @import("protocol/absolute_pointer.zig").AbsolutePointer; +// 15. SCSI +// ScsiIo +// ExtendedScsiPassThrough -pub const GraphicsOutput = @import("protocol/graphics_output.zig").GraphicsOutput; +// 16. iSCSI +// iScsiInitiatorName -pub const edid = @import("protocol/edid.zig"); +// 17. USB +// Usb2HostController +// UsbIo +// UsbFunctionIo +// 18. Debugging +// DebugSupport +// DebugPort + +// 19. Compression +// Decompress + +// 20. ACPI +// AcpiTable + +// 21. String Services +// UnicodeCollation +// RegularExpression + +// 22. EFI Byte Code Machine +// EfiByteCode + +// 23. Firmware Update and Reporting +// FirmwareManagement + +// 24. SNP, PXE, BIS, HTTP pub const SimpleNetwork = @import("protocol/simple_network.zig").SimpleNetwork; +// NetworkInterfaceIdentifier +// PxeBaseCode +// PxeBaseCodeCallback +// BootIntegrityServices +// HttpBootCallback + +// 25. Managed Network +// ManagedNetworkServiceBinding pub const ManagedNetwork = @import("protocol/managed_network.zig").ManagedNetwork; +// 26. Bluetooth +// BluetoothHostController +// BluetoothIoServiceBinding +// BluetoothIo +// BluetoothConfig +// BluetoothAttribute +// BluetoothAttributeServiceBinding +// BluetoothLeConfig + +// 27. VLAN, EAP, WiFi, and Supplicant +// VlanConfig +// Eap +// EapManagement +// EapManagement2 +// EapConfiguration +// WirelessMacConnection +// WirelessMacConnection2 +// SupplicantServiceBinding +// Supplicant + +// 28. TCP, IP, IPSec, FTP, TLS +// Tcp4ServiceBinding +// Tcp4 +// Tcp6ServiceBinding +// Tcp6 +// Ip4ServiceBinding +// Ip4 +// Ip4Config +// Ip4Config2 pub const Ip6ServiceBinding = @import("protocol/ip6_service_binding.zig").Ip6ServiceBinding; pub const Ip6 = @import("protocol/ip6.zig").Ip6; pub const Ip6Config = @import("protocol/ip6_config.zig").Ip6Config; +// IpsecConfig +// Ipsec +// Ipsec2 +// Ftp4ServiceBinding +// Ftp4 +// TlsServiceBinding +// Tls +// TlsConfig +// 29. ARP, DHCP, DNS, HTTP, REST +// ArpServiceBinding +// Arp +// Dhcp4ServiceBinding +// Dhcp4 +// Dhcp6ServiceBinding +// Dhcp6 +// Dns4ServiceBinding +// Dns4 +// Dns6ServiceBinding +// Dns6 +// HttpServiceBinding +// Http +// HttpUtilities +// Rest +// RestExServiceBinding +// RestEx +// RestJsonStructure + +// 30. UDP, MTFTP +// Udp4ServiceBinding +// Udp4 pub const Udp6ServiceBinding = @import("protocol/udp6_service_binding.zig").Udp6ServiceBinding; pub const Udp6 = @import("protocol/udp6.zig").Udp6; +// Mtftp4ServiceBinding +// Mtftp4 +// Mtftp6ServiceBinding +// Mtftp6 + +// 31. Redfish +// RedfishDiscover +// 32. Secure Boot +// AuthenticationInfo + +// 34. HII +// HiiFont +// HiiFontEx +// HiiString +// HiiImage +// HiiImageEx +// HiiImageDecoder +// HiiFontGlyphGenerator pub const HiiDatabase = @import("protocol/hii_database.zig").HiiDatabase; + +// 35. HII Configuration +// ConfigKeywordHandler +// HiiConfigRouting +// HiiConfigAccess +// FormBrowser2 pub const HiiPopup = @import("protocol/hii_popup.zig").HiiPopup; +// 36. User Identification +// UserManager +// UserCredential2 +// DeferredImageLoad + +// 37. Secure Technologies +// HashServiceBinding +// Hash +// Hash2ServiceBinding +// Hash2 +// KeyManagementService +// Pkcs7Verify +pub const Rng = @import("protocol/rng.zig").Rng; +// SmartCardReader +// SmartCardEdge +// MemoryAttribute + +// 38. Confidential Computing +// ConfidentialComputingMeasurement + +// 39. Miscellaneous +// Timestamp +// ResetNotification + +// ** EFI Shell Specification Version 2.2, January 26, 2016 + +// Shell +pub const ShellParameters = @import("protocol/shell_parameters.zig").ShellParameters; +// ShellDynamicCommand + test { @setEvalBranchQuota(2000); @import("std").testing.refAllDeclsRecursive(@This()); diff --git a/lib/std/os/uefi/protocol/block_io.zig b/lib/std/os/uefi/protocol/block_io.zig deleted file mode 100644 index 8e749ac949f9..000000000000 --- a/lib/std/os/uefi/protocol/block_io.zig +++ /dev/null @@ -1,81 +0,0 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Status = uefi.Status; -const cc = uefi.cc; - -pub const BlockIo = extern struct { - const Self = @This(); - - revision: u64, - media: *EfiBlockMedia, - - _reset: *const fn (*BlockIo, extended_verification: bool) callconv(cc) Status, - _read_blocks: *const fn (*BlockIo, media_id: u32, lba: u64, buffer_size: usize, buf: [*]u8) callconv(cc) Status, - _write_blocks: *const fn (*BlockIo, media_id: u32, lba: u64, buffer_size: usize, buf: [*]u8) callconv(cc) Status, - _flush_blocks: *const fn (*BlockIo) callconv(cc) Status, - - /// Resets the block device hardware. - pub fn reset(self: *Self, extended_verification: bool) Status { - return self._reset(self, extended_verification); - } - - /// Reads the number of requested blocks from the device. - pub fn readBlocks(self: *Self, media_id: u32, lba: u64, buffer_size: usize, buf: [*]u8) Status { - return self._read_blocks(self, media_id, lba, buffer_size, buf); - } - - /// Writes a specified number of blocks to the device. - pub fn writeBlocks(self: *Self, media_id: u32, lba: u64, buffer_size: usize, buf: [*]u8) Status { - return self._write_blocks(self, media_id, lba, buffer_size, buf); - } - - /// Flushes all modified data to a physical block device. - pub fn flushBlocks(self: *Self) Status { - return self._flush_blocks(self); - } - - pub const guid align(8) = uefi.Guid{ - .time_low = 0x964e5b21, - .time_mid = 0x6459, - .time_high_and_version = 0x11d2, - .clock_seq_high_and_reserved = 0x8e, - .clock_seq_low = 0x39, - .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, - }; - - pub const EfiBlockMedia = extern struct { - /// The current media ID. If the media changes, this value is changed. - media_id: u32, - - /// `true` if the media is removable; otherwise, `false`. - removable_media: bool, - /// `true` if there is a media currently present in the device - media_present: bool, - /// `true` if the `BlockIo` was produced to abstract - /// partition structures on the disk. `false` if the `BlockIo` was - /// produced to abstract the logical blocks on a hardware device. - logical_partition: bool, - /// `true` if the media is marked read-only otherwise, `false`. This field - /// shows the read-only status as of the most recent `WriteBlocks()` - read_only: bool, - /// `true` if the WriteBlocks() function caches write data. - write_caching: bool, - - /// The intrinsic block size of the device. If the media changes, then this - // field is updated. Returns the number of bytes per logical block. - block_size: u32, - /// Supplies the alignment requirement for any buffer used in a data - /// transfer. IoAlign values of 0 and 1 mean that the buffer can be - /// placed anywhere in memory. Otherwise, IoAlign must be a power of - /// 2, and the requirement is that the start address of a buffer must be - /// evenly divisible by IoAlign with no remainder. - io_align: u32, - /// The last LBA on the device. If the media changes, then this field is updated. - last_block: u64, - - // Revision 2 - lowest_aligned_lba: u64, - logical_blocks_per_physical_block: u32, - optimal_transfer_length_granularity: u32, - }; -}; diff --git a/lib/std/os/uefi/protocol/absolute_pointer.zig b/lib/std/os/uefi/protocol/console/absolute_pointer.zig similarity index 90% rename from lib/std/os/uefi/protocol/absolute_pointer.zig rename to lib/std/os/uefi/protocol/console/absolute_pointer.zig index 178163833a6c..db7d8782378e 100644 --- a/lib/std/os/uefi/protocol/absolute_pointer.zig +++ b/lib/std/os/uefi/protocol/console/absolute_pointer.zig @@ -1,15 +1,15 @@ -const bits = @import("../bits.zig"); +const bits = @import("../../bits.zig"); const cc = bits.cc; -const Status = @import("../status.zig").Status; +const Status = @import("../../status.zig").Status; const Guid = bits.Guid; const Event = bits.Event; /// Provides services that allow information about an absolute pointer device to be retrieved. pub const AbsolutePointer = extern struct { - _reset: *const fn (*const AbsolutePointer, bool) callconv(cc) Status, - _get_state: *const fn (*const AbsolutePointer, *State) callconv(cc) Status, + _reset: *const fn (*const AbsolutePointer, verify: bool) callconv(cc) Status, + _get_state: *const fn (*const AbsolutePointer, state: *State) callconv(cc) Status, /// Event to use with `waitForEvent()` to wait for input from the pointer device. wait_for_input: Event, diff --git a/lib/std/os/uefi/protocol/edid.zig b/lib/std/os/uefi/protocol/console/edid.zig similarity index 67% rename from lib/std/os/uefi/protocol/edid.zig rename to lib/std/os/uefi/protocol/console/edid.zig index 8392dc7370e8..b3b93d81c238 100644 --- a/lib/std/os/uefi/protocol/edid.zig +++ b/lib/std/os/uefi/protocol/console/edid.zig @@ -1,9 +1,10 @@ -const std = @import("../../../std.zig"); -const uefi = std.os.uefi; -const Guid = uefi.Guid; -const Handle = uefi.Handle; -const Status = uefi.Status; -const cc = uefi.cc; +const bits = @import("../../bits.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; + +const Handle = bits.Handle; +const Guid = bits.Guid; /// EDID information for an active video output device pub const Active = extern struct { @@ -37,17 +38,28 @@ pub const Discovered = extern struct { /// Override EDID information pub const Override = extern struct { - _get_edid: *const fn (*const Override, Handle, *Attributes, *usize, *?[*]u8) callconv(cc) Status, + _get_edid: *const fn (*const Override, *const Handle, *Attributes, *usize, *?[*]u8) callconv(cc) Status, + + pub const Attributes = packed struct(u32) { + dont_override: bool, + enable_hot_plug: bool, + _pad: u30 = 0, + }; /// Returns policy information and potentially a replacement EDID for the specified video output device. pub fn getEdid( self: *const Override, - handle: Handle, + /// A pointer to a child handle that represents a possible video output device. + handle: *const Handle, + /// A pointer to the attributes associated with ChildHandle video output device. attributes: *Attributes, + /// A pointer to the size, in bytes, of the Edid buffer. edid_size: *usize, - edid: *?[*]u8, + //// A pointer to the callee allocated buffer that contains the EDID information associated with ChildHandle. + /// If EdidSize is 0, then a pointer to NULL is returned. + edid_buffer: *?[*]u8, ) Status { - return self._get_edid(self, handle, attributes, edid_size, edid); + return self._get_edid(self, handle, attributes, edid_size, edid_buffer); } pub const guid align(8) = Guid{ @@ -58,10 +70,4 @@ pub const Override = extern struct { .clock_seq_low = 0x22, .node = [_]u8{ 0xf4, 0x58, 0xfe, 0x04, 0x0b, 0xd5 }, }; - - pub const Attributes = packed struct(u32) { - dont_override: bool, - enable_hot_plug: bool, - _pad: u30 = 0, - }; }; diff --git a/lib/std/os/uefi/protocol/graphics_output.zig b/lib/std/os/uefi/protocol/console/graphics_output.zig similarity index 74% rename from lib/std/os/uefi/protocol/graphics_output.zig rename to lib/std/os/uefi/protocol/console/graphics_output.zig index eafdb8a128b7..a7b467323b63 100644 --- a/lib/std/os/uefi/protocol/graphics_output.zig +++ b/lib/std/os/uefi/protocol/console/graphics_output.zig @@ -1,7 +1,8 @@ -const bits = @import("../bits.zig"); +const std = @import("../../../../std.zig"); +const bits = @import("../../bits.zig"); const cc = bits.cc; -const Status = @import("../status.zig").Status; +const Status = @import("../../status.zig").Status; const Guid = bits.Guid; @@ -9,9 +10,9 @@ const Guid = bits.Guid; /// buffer. The linear address of the hardware frame buffer is also exposed so software can write directly to the /// video hardware. pub const GraphicsOutput = extern struct { - _query_mode: *const fn (*const GraphicsOutput, u32, *usize, **const Mode.Info) callconv(cc) Status, - _set_mode: *const fn (*const GraphicsOutput, u32) callconv(cc) Status, - _blt: *const fn (*const GraphicsOutput, ?[*]BltPixel, BltOperation, usize, usize, usize, usize, usize, usize, usize) callconv(cc) Status, + _query_mode: *const fn (*const GraphicsOutput, mode: u32, *usize, info: **const Mode.Info) callconv(cc) Status, + _set_mode: *const fn (*const GraphicsOutput, mode: u32) callconv(cc) Status, + _blt: *const fn (*const GraphicsOutput, buffer: ?[*]BltPixel, op: BltOperation, sx: usize, sy: usize, dx: usize, dy: usize, w: usize, h: usize, delta: usize) callconv(cc) Status, mode: *const Mode, pub const Mode = extern struct { @@ -68,6 +69,48 @@ pub const GraphicsOutput = extern struct { } } }; + + /// Writes a single pixel into the linear framebuffer. + pub fn setPixel( + self: *const Mode, + /// The X coordinate of the pixel. + x: usize, + /// The Y coordinate of the pixel. + y: usize, + /// The red channel of the pixel. + red: u8, + /// The green channel of the pixel. + green: u8, + /// The blue channel of the pixel. + blue: u8, + ) !void { + const addr = self.frame_buffer_base + (y * self.pixels_per_scan_line + x) * self.info.pixelElementSize(); + + const pixel: *[4]u8 = @ptrFromInt(addr); + + switch (self.info.pixel_format) { + .rgb_8bit => { + pixel[0] = red; + pixel[1] = green; + pixel[2] = blue; + }, + .bgr_8bit => { + pixel[0] = blue; + pixel[1] = green; + pixel[2] = red; + }, + .bitmask => { + const pixel_u32: *u32 = @ptrCast(pixel); + + const red_value = self.info.pixel_bitmask.toValue(.red, red); + const green_value = self.info.pixel_bitmask.toValue(.green, green); + const blue_value = self.info.pixel_bitmask.toValue(.blue, blue); + + pixel_u32.* = red_value | green_value | blue_value; + }, + .blt_only => unreachable, + } + } }; pub const PixelFormat = enum(u32) { @@ -100,7 +143,7 @@ pub const GraphicsOutput = extern struct { }; /// Finds the size in bits of a single pixel. - pub fn bitSizeOf(self: *const PixelBitmask) usize { + pub fn bitSizeOf(self: *const PixelBitmask) u5 { const highest_red_bit = 32 - @clz(self.red_mask); const highest_green_bit = 32 - @clz(self.green_mask); const highest_blue_bit = 32 - @clz(self.blue_mask); @@ -110,7 +153,7 @@ pub const GraphicsOutput = extern struct { } /// Finds the size in bits of a pixel field. - pub fn bitSizeOfField(self: *const PixelBitmask, field: PixelField) usize { + pub inline fn bitSizeOfField(self: *const PixelBitmask, comptime field: PixelField) u5 { switch (field) { .red => return @popCount(self.red_mask), .green => return @popCount(self.green_mask), @@ -120,7 +163,7 @@ pub const GraphicsOutput = extern struct { } /// Finds the offset from zero (ie. a shift) in bits of a pixel field. - pub fn bitOffsetOfField(self: *const PixelBitmask, field: PixelField) usize { + pub inline fn bitOffsetOfField(self: *const PixelBitmask, comptime field: PixelField) u5 { switch (field) { .red => return @ctz(self.red_mask), .green => return @ctz(self.green_mask), @@ -129,16 +172,30 @@ pub const GraphicsOutput = extern struct { } } + /// Returns the bit mask of a pixel field. + pub inline fn bitMaskOfField(self: *const PixelBitmask, comptime field: PixelField) u32 { + switch (field) { + .red => return self.red_mask, + .green => return self.green_mask, + .blue => return self.blue_mask, + .reserved => return self.reserved_mask, + } + } + /// Pulls the value of a pixel field out of a pixel. - pub fn getValue(self: *const PixelBitmask, field: PixelField, pixel_ptr: [*]const u8) u32 { + pub fn getValue(self: *const PixelBitmask, comptime field: PixelField, pixel_ptr: [*]const u8) u32 { const pixel: *align(1) const u32 = @ptrCast(pixel_ptr); - switch (field) { - .red => return (pixel.* & self.red_mask) >> @ctz(self.red_mask), - .green => return (pixel.* & self.green_mask) >> @ctz(self.green_mask), - .blue => return (pixel.* & self.blue_mask) >> @ctz(self.blue_mask), - .reserved => return (pixel.* & self.reserved_mask) >> @ctz(self.reserved_mask), - } + return (pixel.* & self.bitMaskOfField(field)) >> self.bitOffsetOfField(field); + } + + /// Returns the value of a pixel field shifted and saturated to the correct position for the pixel. + pub fn toValue(self: *const PixelBitmask, comptime field: PixelField, value: u8) u32 { + const max_field_value: u32 = 1 << self.bitSizeOfField(field); + + const value_saturated: u32 = @min(value, max_field_value); + const value_shifted = value_saturated << self.bitOffsetOfField(field); + return value_shifted & self.bitMaskOfField(field); } }; diff --git a/lib/std/os/uefi/protocol/serial_io.zig b/lib/std/os/uefi/protocol/console/serial_io.zig similarity index 89% rename from lib/std/os/uefi/protocol/serial_io.zig rename to lib/std/os/uefi/protocol/console/serial_io.zig index b2ab074e6547..9cb9fb6f5792 100644 --- a/lib/std/os/uefi/protocol/serial_io.zig +++ b/lib/std/os/uefi/protocol/console/serial_io.zig @@ -1,7 +1,7 @@ -const bits = @import("../bits.zig"); +const bits = @import("../../bits.zig"); const cc = bits.cc; -const Status = @import("../status.zig").Status; +const Status = @import("../../status.zig").Status; const Guid = bits.Guid; @@ -10,11 +10,11 @@ pub const SerialIo = extern struct { revision: u32, _reset: *const fn (*const SerialIo) callconv(cc) Status, - _set_attributes: *const fn (*const SerialIo, u64, u32, u32, u32, u32, u32) callconv(cc) Status, - _set_control: *const fn (*const SerialIo, Control) callconv(cc) Status, - _get_control: *const fn (*const SerialIo, *Control) callconv(cc) Status, - _write: *const fn (*const SerialIo, *usize, [*]const u8) callconv(cc) Status, - _read: *const fn (*const SerialIo, *usize, [*]u8) callconv(cc) Status, + _set_attributes: *const fn (*const SerialIo, baud: u64, fifo_depth: u32, timeout: u32, parity: u32, data_bits: u32, stop_bits: u32) callconv(cc) Status, + _set_control: *const fn (*const SerialIo, ctrl: Control) callconv(cc) Status, + _get_control: *const fn (*const SerialIo, ctrl: *Control) callconv(cc) Status, + _write: *const fn (*const SerialIo, buf_size: *usize, buf: [*]const u8) callconv(cc) Status, + _read: *const fn (*const SerialIo, buf_size: *usize, buf: [*]u8) callconv(cc) Status, /// The current mode of the pointer device. mode: *Mode, diff --git a/lib/std/os/uefi/protocol/simple_pointer.zig b/lib/std/os/uefi/protocol/console/simple_pointer.zig similarity index 90% rename from lib/std/os/uefi/protocol/simple_pointer.zig rename to lib/std/os/uefi/protocol/console/simple_pointer.zig index 2d5fec01f0e3..b2430ea1c345 100644 --- a/lib/std/os/uefi/protocol/simple_pointer.zig +++ b/lib/std/os/uefi/protocol/console/simple_pointer.zig @@ -1,15 +1,15 @@ -const bits = @import("../bits.zig"); +const bits = @import("../../bits.zig"); const cc = bits.cc; -const Status = @import("../status.zig").Status; +const Status = @import("../../status.zig").Status; const Guid = bits.Guid; const Event = bits.Event; /// Provides services that allow information about a pointer device to be retrieved. pub const SimplePointer = struct { - _reset: *const fn (*const SimplePointer, bool) callconv(cc) Status, - _get_state: *const fn (*const SimplePointer, *State) callconv(cc) Status, + _reset: *const fn (*const SimplePointer, verify: bool) callconv(cc) Status, + _get_state: *const fn (*const SimplePointer, state: *State) callconv(cc) Status, /// Event to use with `waitForEvent()` to wait for input from the pointer device. wait_for_input: Event, diff --git a/lib/std/os/uefi/protocol/simple_text_input.zig b/lib/std/os/uefi/protocol/console/simple_text_input.zig similarity index 62% rename from lib/std/os/uefi/protocol/simple_text_input.zig rename to lib/std/os/uefi/protocol/console/simple_text_input.zig index 45583f2c31b7..4cfdcb9b0b65 100644 --- a/lib/std/os/uefi/protocol/simple_text_input.zig +++ b/lib/std/os/uefi/protocol/console/simple_text_input.zig @@ -1,16 +1,16 @@ -const bits = @import("../bits.zig"); -const protocol = @import("../protocol.zig"); +const bits = @import("../../bits.zig"); +const protocol = @import("../../protocol.zig"); const cc = bits.cc; -const Status = @import("../status.zig").Status; +const Status = @import("../../status.zig").Status; const Guid = bits.Guid; const Event = bits.Event; /// Character input devices, e.g. Keyboard pub const SimpleTextInput = extern struct { - _reset: *const fn (*const SimpleTextInput, bool) callconv(cc) Status, - _read_key_stroke: *const fn (*const SimpleTextInput, *Key.Input) callconv(cc) Status, + _reset: *const fn (*const SimpleTextInput, verify: bool) callconv(cc) Status, + _read_key_stroke: *const fn (*const SimpleTextInput, key: *Key.Input) callconv(cc) Status, wait_for_key: Event, /// Resets the input device hardware. @@ -24,10 +24,13 @@ pub const SimpleTextInput = extern struct { } /// Reads the next keystroke from the input device. - pub fn readKeyStroke(self: *const SimpleTextInput) !Key.Input { + pub fn readKeyStroke(self: *const SimpleTextInput) !?Key.Input { var input_key: Key.Input = undefined; - try self._read_key_stroke(self, &input_key).err(); - return input_key; + switch (self._read_key_stroke(self, &input_key)) { + .success => return input_key, + .not_ready => return null, + else => |s| return s.err(), + } } pub const guid align(8) = Guid{ diff --git a/lib/std/os/uefi/protocol/simple_text_input_ex.zig b/lib/std/os/uefi/protocol/console/simple_text_input_ex.zig similarity index 86% rename from lib/std/os/uefi/protocol/simple_text_input_ex.zig rename to lib/std/os/uefi/protocol/console/simple_text_input_ex.zig index 3c11cf78419f..9fdef1cbcb28 100644 --- a/lib/std/os/uefi/protocol/simple_text_input_ex.zig +++ b/lib/std/os/uefi/protocol/console/simple_text_input_ex.zig @@ -1,7 +1,7 @@ -const bits = @import("../bits.zig"); +const bits = @import("../../bits.zig"); const cc = bits.cc; -const Status = @import("../status.zig").Status; +const Status = @import("../../status.zig").Status; const Guid = bits.Guid; const Event = bits.Event; @@ -9,12 +9,12 @@ const Event = bits.Event; /// The Simple Text Input Ex protocol defines an extension to the Simple Text Input protocol /// which enables various new capabilities. pub const SimpleTextInputEx = extern struct { - _reset: *const fn (*const SimpleTextInputEx, bool) callconv(cc) Status, - _read_key_stroke_ex: *const fn (*const SimpleTextInputEx, *Key) callconv(cc) Status, + _reset: *const fn (*const SimpleTextInputEx, verify: bool) callconv(cc) Status, + _read_key_stroke_ex: *const fn (*const SimpleTextInputEx, key: *Key) callconv(cc) Status, wait_for_key_ex: Event, - _set_state: *const fn (*const SimpleTextInputEx, *const Key.State.Toggle) callconv(cc) Status, - _register_key_notify: *const fn (*const SimpleTextInputEx, *Key, *const fn (*const Key) callconv(cc) usize, *NotifyHandle) callconv(cc) Status, - _unregister_key_notify: *const fn (*const SimpleTextInputEx, NotifyHandle) callconv(cc) Status, + _set_state: *const fn (*const SimpleTextInputEx, state: *const Key.State.Toggle) callconv(cc) Status, + _register_key_notify: *const fn (*const SimpleTextInputEx, key: *Key, func: *const fn (*const Key) callconv(cc) usize, handle: *NotifyHandle) callconv(cc) Status, + _unregister_key_notify: *const fn (*const SimpleTextInputEx, handle: NotifyHandle) callconv(cc) Status, /// Resets the input device hardware. pub fn reset( diff --git a/lib/std/os/uefi/protocol/simple_text_output.zig b/lib/std/os/uefi/protocol/console/simple_text_output.zig similarity index 87% rename from lib/std/os/uefi/protocol/simple_text_output.zig rename to lib/std/os/uefi/protocol/console/simple_text_output.zig index 6341c3a27268..9fceb95bcd6d 100644 --- a/lib/std/os/uefi/protocol/simple_text_output.zig +++ b/lib/std/os/uefi/protocol/console/simple_text_output.zig @@ -1,23 +1,23 @@ -const std = @import("../../../std.zig"); -const bits = @import("../bits.zig"); +const std = @import("../../../../std.zig"); +const bits = @import("../../bits.zig"); const cc = bits.cc; -const Status = @import("../status.zig").Status; +const Status = @import("../../status.zig").Status; const Guid = bits.Guid; const Event = bits.Event; /// Character output devices pub const SimpleTextOutput = extern struct { - _reset: *const fn (*const SimpleTextOutput, bool) callconv(cc) Status, - _output_string: *const fn (*const SimpleTextOutput, [*:0]const u16) callconv(cc) Status, - _test_string: *const fn (*const SimpleTextOutput, [*:0]const u16) callconv(cc) Status, - _query_mode: *const fn (*const SimpleTextOutput, usize, *usize, *usize) callconv(cc) Status, - _set_mode: *const fn (*const SimpleTextOutput, usize) callconv(cc) Status, - _set_attribute: *const fn (*const SimpleTextOutput, usize) callconv(cc) Status, + _reset: *const fn (*const SimpleTextOutput, verify: bool) callconv(cc) Status, + _output_string: *const fn (*const SimpleTextOutput, str: [*:0]const u16) callconv(cc) Status, + _test_string: *const fn (*const SimpleTextOutput, str: [*:0]const u16) callconv(cc) Status, + _query_mode: *const fn (*const SimpleTextOutput, mode: usize, cols: *usize, rows: *usize) callconv(cc) Status, + _set_mode: *const fn (*const SimpleTextOutput, mode: usize) callconv(cc) Status, + _set_attribute: *const fn (*const SimpleTextOutput, Attribute) callconv(cc) Status, _clear_screen: *const fn (*const SimpleTextOutput) callconv(cc) Status, - _set_cursor_position: *const fn (*const SimpleTextOutput, usize, usize) callconv(cc) Status, - _enable_cursor: *const fn (*const SimpleTextOutput, bool) callconv(cc) Status, + _set_cursor_position: *const fn (*const SimpleTextOutput, col: usize, row: usize) callconv(cc) Status, + _enable_cursor: *const fn (*const SimpleTextOutput, enabled: bool) callconv(cc) Status, ///The mode information for this instance of the protocol. mode: *Mode, @@ -100,14 +100,14 @@ pub const SimpleTextOutput = extern struct { lightgray, }; - foreground: Color, + foreground: Color = .lightgray, /// If true, `foreground` will use the bright variant of the color - foreground_bright: bool, - background: Color, + foreground_bright: bool = false, + background: Color = .black, // this is a usize-sized bitfield, so we have to fill in the padding programmatically - reserved: std.meta.Int(.unsigned, @bitSizeOf(usize) - 7), + reserved: std.meta.Int(.unsigned, @bitSizeOf(usize) - 7) = 0, }; /// Sets the background and foreground colors for the outputString() and clearScreen() functions. diff --git a/lib/std/os/uefi/protocol/device_path.zig b/lib/std/os/uefi/protocol/device_path.zig index 15b21ecf87a9..50e7ec1b9690 100644 --- a/lib/std/os/uefi/protocol/device_path.zig +++ b/lib/std/os/uefi/protocol/device_path.zig @@ -1,15 +1,21 @@ const std = @import("../../../std.zig"); +const bits = @import("../bits.zig"); + const mem = std.mem; -const uefi = std.os.uefi; +const cc = bits.cc; +const DevicePathNode = @import("../device_path.zig").DevicePathNode; + +const Handle = bits.Handle; +const Guid = bits.Guid; const Allocator = mem.Allocator; -const Guid = uefi.Guid; + const assert = std.debug.assert; // All Device Path Nodes are byte-packed and may appear on any byte boundary. // All code references to device path nodes must assume all fields are unaligned. pub const DevicePath = extern struct { - type: uefi.DevicePath.Type, + type: DevicePathNode.Type, subtype: u8, length: u16 align(1), @@ -22,88 +28,82 @@ pub const DevicePath = extern struct { .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, }; + pub const loaded_image_guid align(8) = Guid{ + .time_low = 0xbc62157e, + .time_mid = 0x3e33, + .time_high_and_version = 0x4fec, + .clock_seq_high_and_reserved = 0x99, + .clock_seq_low = 0x20, + .node = [_]u8{ 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf }, + }; + /// Returns the next DevicePath node in the sequence, if any. - pub fn next(self: *DevicePath) ?*DevicePath { - if (self.type == .End and @as(uefi.DevicePath.End.Subtype, @enumFromInt(self.subtype)) == .EndEntire) + fn next(self: *const DevicePath) ?*const DevicePath { + const subtype: DevicePathNode.End.Subtype = @enumFromInt(self.subtype); + if (self.type != .end or subtype != .entire) return null; - return @as(*DevicePath, @ptrCast(@as([*]u8, @ptrCast(self)) + self.length)); + const next_addr = @intFromPtr(self) + self.length; + return @ptrFromInt(next_addr); } /// Calculates the total length of the device path structure in bytes, including the end of device path node. - pub fn size(self: *DevicePath) usize { - var node = self; + pub fn size(self: *const DevicePath) usize { + var cur_node = self; - while (node.next()) |next_node| { - node = next_node; + while (cur_node.next()) |next_node| { + cur_node = next_node; } - return (@intFromPtr(node) + node.length) - @intFromPtr(self); + return (@intFromPtr(cur_node) + cur_node.length) - @intFromPtr(self); } - /// Creates a file device path from the existing device path and a file path. - pub fn create_file_device_path(self: *DevicePath, allocator: Allocator, path: [:0]align(1) const u16) !*DevicePath { - const path_size = self.size(); - - // 2 * (path.len + 1) for the path and its null terminator, which are u16s - // DevicePath for the extra node before the end - var buf = try allocator.alloc(u8, path_size + 2 * (path.len + 1) + @sizeOf(DevicePath)); - - @memcpy(buf[0..path_size], @as([*]const u8, @ptrCast(self))[0..path_size]); - - // Pointer to the copy of the end node of the current chain, which is - 4 from the buffer - // as the end node itself is 4 bytes (type: u8 + subtype: u8 + length: u16). - var new = @as(*uefi.DevicePath.Media.FilePath, @ptrCast(buf.ptr + path_size - 4)); + /// Creates a new device path with only the end entire node. The device path will be owned by the caller. + pub fn create(allocator: Allocator) !*DevicePath { + const bytes = try allocator.alloc(u8, 4); - new.type = .Media; - new.subtype = .FilePath; - new.length = @sizeOf(uefi.DevicePath.Media.FilePath) + 2 * (@as(u16, @intCast(path.len)) + 1); + const device_path: *DevicePath = @ptrCast(bytes.ptr); + device_path.type = .end; + device_path.subtype = @intFromEnum(DevicePathNode.End.Subtype.entire); + device_path.length = 4; - // The same as new.getPath(), but not const as we're filling it in. - var ptr = @as([*:0]align(1) u16, @ptrCast(@as([*]u8, @ptrCast(new)) + @sizeOf(uefi.DevicePath.Media.FilePath))); + return device_path; + } - for (path, 0..) |s, i| - ptr[i] = s; + /// Appends a device path node to the end of an existing device path. `allocator` must own the memory of the + /// existing device path. The existing device path must be the start of the entire device path chain. + /// + /// This will reallocate the existing device path. The pointer returned here must be used instead of any dangling + /// references to the previous device path. + pub fn append(self: *DevicePath, allocator: Allocator, node_to_append: DevicePathNode) !*DevicePath { + const original_size = self.size(); + const new_size = original_size + node_to_append.toGeneric().length; - ptr[path.len] = 0; + const original_bytes: [*]u8 = @ptrCast(self); + const new_bytes = try allocator.realloc(original_bytes[0..original_size], new_size); - var end = @as(*uefi.DevicePath.End.EndEntire, @ptrCast(@as(*DevicePath, @ptrCast(new)).next().?)); - end.type = .End; - end.subtype = .EndEntire; - end.length = @sizeOf(uefi.DevicePath.End.EndEntire); + // copy end entire node to the end of the new buffer. It is always 4 bytes. + @memcpy(new_bytes[new_size - 4 ..], new_bytes[original_size - 4 .. original_size]); - return @as(*DevicePath, @ptrCast(buf.ptr)); - } + const node_bytes: [*]const u8 = @ptrCast(node_to_append.toGeneric()); - pub fn getDevicePath(self: *const DevicePath) ?uefi.DevicePath { - inline for (@typeInfo(uefi.DevicePath).Union.fields) |ufield| { - const enum_value = std.meta.stringToEnum(uefi.DevicePath.Type, ufield.name); - - // Got the associated union type for self.type, now - // we need to initialize it and its subtype - if (self.type == enum_value) { - const subtype = self.initSubtype(ufield.type); - if (subtype) |sb| { - // e.g. return .{ .Hardware = .{ .Pci = @ptrCast(...) } } - return @unionInit(uefi.DevicePath, ufield.name, sb); - } - } - } + // Copy new node on top of the previous end entire node. + @memcpy(new_bytes[original_size - 4 .. new_size - 4], node_bytes[0..node_to_append.toGeneric().length]); - return null; + return @ptrCast(new_bytes); } - pub fn initSubtype(self: *const DevicePath, comptime TUnion: type) ?TUnion { - const type_info = @typeInfo(TUnion).Union; - const TTag = type_info.tag_type.?; + /// Returns the DevicePathNode union for this device path protocol + pub fn node(self: *const DevicePath) ?DevicePathNode { + inline for (@typeInfo(DevicePathNode).Union.fields) |type_field| { + if (self.type == @field(DevicePathNode.Type, type_field.name)) { + const subtype: type_field.type.Subtype = @enumFromInt(self.subtype); - inline for (type_info.fields) |subtype| { - // The tag names match the union names, so just grab that off the enum - const tag_val: u8 = @intFromEnum(@field(TTag, subtype.name)); - - if (self.subtype == tag_val) { - // e.g. expr = .{ .Pci = @ptrCast(...) } - return @unionInit(TUnion, subtype.name, @as(subtype.type, @ptrCast(self))); + inline for (@typeInfo(type_field.type).Union.fields) |subtype_field| { + if (subtype == @field(type_field.type.Subtype, subtype_field.name)) { + return @unionInit(DevicePathNode, type_field.name, @unionInit(type_field.type, subtype_field.name, @ptrCast(self))); + } + } } } diff --git a/lib/std/os/uefi/protocol/file.zig b/lib/std/os/uefi/protocol/file.zig deleted file mode 100644 index 9c801b2f4c28..000000000000 --- a/lib/std/os/uefi/protocol/file.zig +++ /dev/null @@ -1,144 +0,0 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const io = std.io; -const Guid = uefi.Guid; -const Time = uefi.Time; -const Status = uefi.Status; -const cc = uefi.cc; - -pub const File = extern struct { - revision: u64, - _open: *const fn (*const File, **const File, [*:0]const u16, u64, u64) callconv(cc) Status, - _close: *const fn (*const File) callconv(cc) Status, - _delete: *const fn (*const File) callconv(cc) Status, - _read: *const fn (*const File, *usize, [*]u8) callconv(cc) Status, - _write: *const fn (*const File, *usize, [*]const u8) callconv(cc) Status, - _get_position: *const fn (*const File, *u64) callconv(cc) Status, - _set_position: *const fn (*const File, u64) callconv(cc) Status, - _get_info: *const fn (*const File, *align(8) const Guid, *const usize, [*]u8) callconv(cc) Status, - _set_info: *const fn (*const File, *align(8) const Guid, usize, [*]const u8) callconv(cc) Status, - _flush: *const fn (*const File) callconv(cc) Status, - - pub const SeekError = error{SeekError}; - pub const GetSeekPosError = error{GetSeekPosError}; - pub const ReadError = error{ReadError}; - pub const WriteError = error{WriteError}; - - pub const SeekableStream = io.SeekableStream(*const File, SeekError, GetSeekPosError, seekTo, seekBy, getPos, getEndPos); - pub const Reader = io.Reader(*const File, ReadError, readFn); - pub const Writer = io.Writer(*const File, WriteError, writeFn); - - pub fn seekableStream(self: *File) SeekableStream { - return .{ .context = self }; - } - - pub fn reader(self: *File) Reader { - return .{ .context = self }; - } - - pub fn writer(self: *File) Writer { - return .{ .context = self }; - } - - pub fn open(self: *const File, new_handle: **const File, file_name: [*:0]const u16, open_mode: u64, attributes: u64) Status { - return self._open(self, new_handle, file_name, open_mode, attributes); - } - - pub fn close(self: *const File) Status { - return self._close(self); - } - - pub fn delete(self: *const File) Status { - return self._delete(self); - } - - pub fn read(self: *const File, buffer_size: *usize, buffer: [*]u8) Status { - return self._read(self, buffer_size, buffer); - } - - fn readFn(self: *const File, buffer: []u8) ReadError!usize { - var size: usize = buffer.len; - if (.Success != self.read(&size, buffer.ptr)) return ReadError.ReadError; - return size; - } - - pub fn write(self: *const File, buffer_size: *usize, buffer: [*]const u8) Status { - return self._write(self, buffer_size, buffer); - } - - fn writeFn(self: *const File, bytes: []const u8) WriteError!usize { - var size: usize = bytes.len; - if (.Success != self.write(&size, bytes.ptr)) return WriteError.WriteError; - return size; - } - - pub fn getPosition(self: *const File, position: *u64) Status { - return self._get_position(self, position); - } - - fn getPos(self: *const File) GetSeekPosError!u64 { - var pos: u64 = undefined; - if (.Success != self.getPosition(&pos)) return GetSeekPosError.GetSeekPosError; - return pos; - } - - fn getEndPos(self: *const File) GetSeekPosError!u64 { - // preserve the old file position - var pos: u64 = undefined; - if (.Success != self.getPosition(&pos)) return GetSeekPosError.GetSeekPosError; - // seek to end of file to get position = file size - if (.Success != self.setPosition(efi_file_position_end_of_file)) return GetSeekPosError.GetSeekPosError; - // restore the old position - if (.Success != self.setPosition(pos)) return GetSeekPosError.GetSeekPosError; - // return the file size = position - return pos; - } - - pub fn setPosition(self: *const File, position: u64) Status { - return self._set_position(self, position); - } - - fn seekTo(self: *const File, pos: u64) SeekError!void { - if (.Success != self.setPosition(pos)) return SeekError.SeekError; - } - - fn seekBy(self: *const File, offset: i64) SeekError!void { - // save the old position and calculate the delta - var pos: u64 = undefined; - if (.Success != self.getPosition(&pos)) return SeekError.SeekError; - const seek_back = offset < 0; - const amt = @abs(offset); - if (seek_back) { - pos += amt; - } else { - pos -= amt; - } - if (.Success != self.setPosition(pos)) return SeekError.SeekError; - } - - pub fn getInfo(self: *const File, information_type: *align(8) const Guid, buffer_size: *usize, buffer: [*]u8) Status { - return self._get_info(self, information_type, buffer_size, buffer); - } - - pub fn setInfo(self: *const File, information_type: *align(8) const Guid, buffer_size: usize, buffer: [*]const u8) Status { - return self._set_info(self, information_type, buffer_size, buffer); - } - - pub fn flush(self: *const File) Status { - return self._flush(self); - } - - pub const efi_file_mode_read: u64 = 0x0000000000000001; - pub const efi_file_mode_write: u64 = 0x0000000000000002; - pub const efi_file_mode_create: u64 = 0x8000000000000000; - - pub const efi_file_read_only: u64 = 0x0000000000000001; - pub const efi_file_hidden: u64 = 0x0000000000000002; - pub const efi_file_system: u64 = 0x0000000000000004; - pub const efi_file_reserved: u64 = 0x0000000000000008; - pub const efi_file_directory: u64 = 0x0000000000000010; - pub const efi_file_archive: u64 = 0x0000000000000020; - pub const efi_file_valid_attr: u64 = 0x0000000000000037; - - pub const efi_file_position_end_of_file: u64 = 0xffffffffffffffff; -}; diff --git a/lib/std/os/uefi/protocol/loaded_image.zig b/lib/std/os/uefi/protocol/loaded_image.zig index 319efd90f942..f525110c5313 100644 --- a/lib/std/os/uefi/protocol/loaded_image.zig +++ b/lib/std/os/uefi/protocol/loaded_image.zig @@ -1,31 +1,31 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Guid = uefi.Guid; -const Handle = uefi.Handle; -const Status = uefi.Status; -const SystemTable = uefi.tables.SystemTable; -const MemoryType = uefi.tables.MemoryType; -const DevicePath = uefi.protocol.DevicePath; -const cc = uefi.cc; +const bits = @import("../bits.zig"); +const table = @import("../table.zig"); +const protocol = @import("../protocol.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; +const Handle = bits.Handle; pub const LoadedImage = extern struct { revision: u32, parent_handle: Handle, - system_table: *SystemTable, + system_table: *table.System, device_handle: ?Handle, - file_path: *DevicePath, + file_path: *protocol.DevicePath, reserved: *anyopaque, load_options_size: u32, load_options: ?*anyopaque, image_base: [*]u8, image_size: u64, - image_code_type: MemoryType, - image_data_type: MemoryType, - _unload: *const fn (*const LoadedImage, Handle) callconv(cc) Status, + image_code_type: bits.MemoryDescriptor.Type, + image_data_type: bits.MemoryDescriptor.Type, + _unload: *const fn (Handle) callconv(cc) Status, /// Unloads an image from memory. pub fn unload(self: *const LoadedImage, handle: Handle) Status { - return self._unload(self, handle); + return self._unload(handle); } pub const guid align(8) = Guid{ @@ -36,13 +36,4 @@ pub const LoadedImage = extern struct { .clock_seq_low = 0x3f, .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, }; - - pub const device_path_guid align(8) = Guid{ - .time_low = 0xbc62157e, - .time_mid = 0x3e33, - .time_high_and_version = 0x4fec, - .clock_seq_high_and_reserved = 0x99, - .clock_seq_low = 0x20, - .node = [_]u8{ 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf }, - }; }; diff --git a/lib/std/os/uefi/protocol/media/block_io.zig b/lib/std/os/uefi/protocol/media/block_io.zig new file mode 100644 index 000000000000..747d3304855f --- /dev/null +++ b/lib/std/os/uefi/protocol/media/block_io.zig @@ -0,0 +1,113 @@ +const std = @import("../../../../std.zig"); +const bits = @import("../../bits.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; + +const Guid = bits.Guid; + +const assert = std.debug.assert; + +pub const BlockIo = extern struct { + const Self = @This(); + + revision: u64, + media: *const Media, + + _reset: *const fn (*const BlockIo, verify: bool) callconv(cc) Status, + _read_blocks: *const fn (*const BlockIo, media_id: u32, lba: bits.LogicalBlockAddress, buffer_size: usize, buf: [*]u8) callconv(cc) Status, + _write_blocks: *const fn (*const BlockIo, media_id: u32, lba: bits.LogicalBlockAddress, buffer_size: usize, buf: [*]const u8) callconv(cc) Status, + _flush_blocks: *const fn (*const BlockIo) callconv(cc) Status, + + /// Resets the block device hardware. + pub fn reset( + self: *Self, + /// Indicates that the driver may perform a more exhaustive verification operation of the device during reset. + verify: bool, + ) !void { + try self._reset(self, verify).err(); + } + + /// Reads the number of requested blocks from the device. + pub fn readBlocks( + self: *Self, + /// The media ID that the read request is for. + media_id: u32, + /// The starting logical block address to read from on the device. + lba: bits.LogicalBlockAddress, + /// The buffer into which the data is read. + buffer: []u8, + ) !void { + try self._read_blocks(self, media_id, lba, buffer.len, buffer.ptr).err(); + } + + /// Writes a specified number of blocks to the device. + pub fn writeBlocks( + self: *Self, + /// The media ID that the write request is for. + media_id: u32, + /// The starting logical block address to write from on the device. + lba: bits.LogicalBlockAddress, + /// The buffer from which the data is written. + buffer: []const u8, + ) !void { + try self._write_blocks(self, media_id, lba, buffer.len, buffer.ptr).err(); + } + + /// Flushes all modified data to a physical block device. + pub fn flushBlocks(self: *Self) !void { + try self._flush_blocks(self).err(); + } + + pub const guid align(8) = Guid{ + .time_low = 0x964e5b21, + .time_mid = 0x6459, + .time_high_and_version = 0x11d2, + .clock_seq_high_and_reserved = 0x8e, + .clock_seq_low = 0x39, + .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, + }; + + pub const Media = extern struct { + /// The current media ID. If the media changes, this value is changed. + media_id: u32, + + /// `true` if the media is removable; otherwise, `false`. + removable_media: bool, + /// `true` if there is a media currently present in the device + media_present: bool, + /// `true` if the `BlockIo` was produced to abstract partition structures on the disk. `false` if the `BlockIo` + /// was produced to abstract the logical blocks on a hardware device. + logical_partition: bool, + /// `true` if the media is marked read-only otherwise, `false`. This field shows the read-only status as of the + /// most recent `WriteBlocks()` + read_only: bool, + /// `true` if the WriteBlocks() function caches write data. + write_caching: bool, + + /// The intrinsic block size of the device. If the media changes, then this field is updated. Returns the number + /// of bytes per logical block. + block_size: u32, + /// Supplies the alignment requirement for any buffer used in a data transfer. IoAlign values of 0 and 1 mean + /// that the buffer can be placed anywhere in memory. Otherwise, IoAlign must be a power of 2, and the + /// requirement is that the start address of a buffer must be evenly divisible by `io_align` with no remainder. + io_align: u32, + /// The last LBA on the device. If the media changes, then this field is updated. + last_block: u64, + + // Revision 2 + + /// Returns the first LBA that is aligned to a physical block boundary. If `logical_partition` is true, then this + /// field will be zero. + lowest_aligned_lba: u64, + + /// Returns the number of logical blocks per physical block. A value of 0 means there is either one logical + /// block per physical block, or there are more than one physical block per logical block. If `logical_partition` + /// is true, then this field will be zero. + logical_blocks_per_physical_block: u32, + + /// Returns the optimal transfer length granularity as a number of logical blocks. A value of 0 means there is + /// no reported optimal transfer length granularity. If `logical_partition` is true, then this field will be zero. + optimal_transfer_length_granularity: u32, + }; +}; diff --git a/lib/std/os/uefi/protocol/media/file.zig b/lib/std/os/uefi/protocol/media/file.zig new file mode 100644 index 000000000000..6874aee860b2 --- /dev/null +++ b/lib/std/os/uefi/protocol/media/file.zig @@ -0,0 +1,188 @@ +const std = @import("../../../../std.zig"); +const io = std.io; + +const bits = @import("../../bits.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; + +const Guid = bits.Guid; + +pub const File = extern struct { + revision: u64, + _open: *const fn (*const File, fd: **const File, name: [*:0]const u16, mode: OpenMode, attrs: bits.FileInfo.Attributes) callconv(cc) Status, + _close: *const fn (*const File) callconv(cc) Status, + _delete: *const fn (*const File) callconv(cc) Status, + _read: *const fn (*const File, buf_size: *usize, buf: [*]u8) callconv(cc) Status, + _write: *const fn (*const File, buf_size: *usize, buf: [*]const u8) callconv(cc) Status, + _get_position: *const fn (*const File, pos: *u64) callconv(cc) Status, + _set_position: *const fn (*const File, pos: u64) callconv(cc) Status, + _get_info: *const fn (*const File, info: *align(8) const Guid, buf_size: *const usize, buf: ?[*]u8) callconv(cc) Status, + _set_info: *const fn (*const File, info: *align(8) const Guid, buf_size: usize, buf: [*]const u8) callconv(cc) Status, + _flush: *const fn (*const File) callconv(cc) Status, + + pub const SeekError = Status.EfiError; + pub const GetSeekPosError = Status.EfiError; + pub const ReadError = Status.EfiError; + pub const WriteError = Status.EfiError; + + pub const SeekableStream = io.SeekableStream(*const File, SeekError, GetSeekPosError, setPosition, movePosition, getPosition, getEndPosition); + pub const Reader = io.Reader(*const File, ReadError, read); + pub const Writer = io.Writer(*const File, WriteError, write); + + pub fn seekableStream(self: *File) SeekableStream { + return .{ .context = self }; + } + + pub fn reader(self: *File) Reader { + return .{ .context = self }; + } + + pub fn writer(self: *File) Writer { + return .{ .context = self }; + } + + pub const OpenMode = packed struct(u64) { + read: bool = true, + + /// May only be specified if `read` is true. + write: bool = false, + + _pad: u61 = 0, + + /// May only be specified if `write` is true. + create: bool = false, + }; + + /// Opens a new file relative to the source file's location. + pub fn open( + self: *const File, + /// The Null-terminated string of the name of the file to be opened. The file name may contain the following + /// path modifiers: "\", ".", and "..". + file_name: [:0]const u16, + /// The mode to open the file with. + open_mode: OpenMode, + /// The attributes for a newly created file. + attributes: bits.FileInfo.Attributes, + ) !*const File { + var new_handle: *const File = undefined; + try self._open(self, &new_handle, file_name.ptr, open_mode, attributes).err(); + return new_handle; + } + + /// Closes a specified file handle. + pub fn close(self: *const File) void { + _ = self._close(self); + } + + /// Closes and deletes a file. This can fail, but the descriptor will still be closed. + pub fn delete(self: *const File) void { + _ = self._delete(self); + } + + /// Reads data from a file. + pub fn read(self: *const File, buffer: []u8) ReadError!usize { + var size: usize = buffer.len; + try self._read(self, &size, buffer.ptr).err(); + return size; + } + + /// Writes data to a file. + pub fn write(self: *const File, buffer: []const u8) WriteError!usize { + var size: usize = buffer.len; + try self._write(self, &size, buffer.ptr).err(); + return size; + } + + /// Returns a file’s current position. + pub fn getPosition(self: *const File) GetSeekPosError!u64 { + var position: u64 = 0; + try self._get_position(self, &position).err(); + return position; + } + + /// Returns a file’s end position. + pub fn getEndPosition(self: *const File) GetSeekPosError!u64 { + // preserve the old file position + const position = try self.getPosition(); + + // seek to the end of the file + try self.setPosition(position_end_of_file); + const end_pos = try self.getPosition(); + + // restore the old position + try self.setPosition(position); + + return end_pos; + } + + /// Sets a file’s current position. This is allowed to move past the end of the file. A subsequent write will extend + /// the file. + pub fn setPosition(self: *const File, position: u64) !void { + try self._set_position(self, position).err(); + } + + /// Moves the file pointer by the specified offset. + pub fn movePosition(self: *const File, offset: i64) SeekError!void { + var position = try self.getPosition(); + + const seek_back = offset < 0; + const amt = @abs(offset); + if (seek_back) { + position += amt; + } else { + position -= amt; + } + + try self.setPosition(position); + } + + /// Returns the buffer size required to hold the information of the specified type. + /// + /// `Information` must be one of: `FileInfo` or `FileSystemInfo` or `FileSystemVolumeLabel`. + pub fn getInfoSize( + self: *const File, + comptime Information: type, + ) !usize { + var buffer_size: usize = 0; + try self._get_info(self, Information.guid, &buffer_size, null).err(); + return buffer_size; + } + + /// Returns information about a file. + /// + /// `Information` must be one of: `FileInfo` or `FileSystemInfo` or `FileSystemVolumeLabel`. + pub fn getInfo( + self: *const File, + comptime Information: type, + buffer: []align(@alignOf(Information)) u8, + ) !*const Information { + var size: usize = buffer.len; + try self._get_info(self, Information.guid, &size, buffer.ptr).err(); + return @ptrCast(buffer.ptr); + } + + /// Sets information about a file. + pub fn setInfo( + self: *const File, + comptime Information: type, + info: *const Information, + ) !void { + const size: usize = switch (Information) { + bits.FileInfo => @intCast(info.size), + bits.FileSystemInfo => @intCast(info.size), + bits.FileSystemVolumeLabel => 2 * info.getVolumeLabel().len + 2, + else => return error.InvalidParameter, + }; + + try self._set_info(self, Information.guid, size, @ptrCast(info)).err(); + } + + /// Flushes all modified data associated with a file to a device. + pub fn flush(self: *const File) !void { + try self._flush(self).err(); + } + + /// A special location that will move the file pointer to the end of the file. + pub const position_end_of_file: u64 = 0xffffffffffffffff; +}; diff --git a/lib/std/os/uefi/protocol/media/load_file.zig b/lib/std/os/uefi/protocol/media/load_file.zig new file mode 100644 index 000000000000..d73b43f8ddd6 --- /dev/null +++ b/lib/std/os/uefi/protocol/media/load_file.zig @@ -0,0 +1,53 @@ +const bits = @import("../../bits.zig"); +const protocol = @import("../../protocol.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; +const DevicePathProtocol = protocol.DevicePath; + +const Guid = bits.Guid; + +/// Used to obtain files, that are primarily boot options, from arbitrary devices. +pub const LoadFile = extern struct { + _load_file: *const fn (*const LoadFile, file_path: *const DevicePathProtocol, is_boot_policy: bool, buf_size: *usize, buf: ?[*]u8) callconv(cc) Status, + + /// Determines the size of buffer required to hold the specified file. + pub fn loadFileSize( + self: *const LoadFile, + /// The device specific path of the file to load. + file_path: *const DevicePathProtocol, + /// If true, the request originates from the boot manager, and that the boot manager is attempting to load the + /// file as a boot selection. If false, the file path must match an exact file to be loaded. + boot_policy: bool, + ) !usize { + var size: usize = 0; + switch (self._load_file(self, file_path, boot_policy, &size, null)) { + .buffer_too_small => return size, + else => |s| return s.err(), + } + } + + /// Causes the driver to load a specified file. + pub fn loadFile( + self: *const LoadFile, + /// The device specific path of the file to load. + file_path: *const DevicePathProtocol, + /// If true, the request originates from the boot manager, and that the boot manager is attempting to load the + /// file as a boot selection. If false, the file path must match an exact file to be loaded. + boot_policy: bool, + /// The memory buffer to transfer the file to. + buffer: [*]u8, + ) !void { + var size: usize = buffer.len; + try self._load_file(self, file_path, boot_policy, &size, buffer).err(); + } + + pub const guid align(8) = Guid{ + .time_low = 0x56ec3091, + .time_mid = 0x954c, + .time_high_and_version = 0x11d2, + .clock_seq_high_and_reserved = 0x8e, + .clock_seq_low = 0x3f, + .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, + }; +}; diff --git a/lib/std/os/uefi/protocol/media/simple_file_system.zig b/lib/std/os/uefi/protocol/media/simple_file_system.zig new file mode 100644 index 000000000000..121a7fc9255a --- /dev/null +++ b/lib/std/os/uefi/protocol/media/simple_file_system.zig @@ -0,0 +1,29 @@ +const bits = @import("../../bits.zig"); +const protocol = @import("../../protocol.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; +const FileProtocol = protocol.File; + +const Guid = bits.Guid; + +pub const SimpleFileSystem = extern struct { + revision: u64, + _open_volume: *const fn (*const SimpleFileSystem, fd: **const FileProtocol) callconv(cc) Status, + + /// Opens the root directory on a volume. + pub fn openVolume(self: *const SimpleFileSystem) !*const FileProtocol { + var root: *const FileProtocol = undefined; + try self._open_volume(self, &root).err(); + return root; + } + + pub const guid align(8) = Guid{ + .time_low = 0x0964e5b22, + .time_mid = 0x6459, + .time_high_and_version = 0x11d2, + .clock_seq_high_and_reserved = 0x8e, + .clock_seq_low = 0x39, + .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, + }; +}; diff --git a/lib/std/os/uefi/protocol/shell_parameters.zig b/lib/std/os/uefi/protocol/shell_parameters.zig index d457375ff170..89029d0da131 100644 --- a/lib/std/os/uefi/protocol/shell_parameters.zig +++ b/lib/std/os/uefi/protocol/shell_parameters.zig @@ -1,13 +1,16 @@ -const uefi = @import("std").os.uefi; -const Guid = uefi.Guid; -const FileHandle = uefi.FileHandle; +const bits = @import("../bits.zig"); +const Guid = bits.Guid; + +/// An instance of this protocol is installed on each shell application’s image handle prior to calling StartImage(). +/// It describes all of the command-line parameters passed on the command line, as well as the standard file handles +/// for standard input, output and error output. pub const ShellParameters = extern struct { argv: [*][*:0]const u16, argc: usize, - stdin: FileHandle, - stdout: FileHandle, - stderr: FileHandle, + stdin: bits.FileHandle, + stdout: bits.FileHandle, + stderr: bits.FileHandle, pub const guid align(8) = Guid{ .time_low = 0x752f3136, diff --git a/lib/std/os/uefi/protocol/simple_file_system.zig b/lib/std/os/uefi/protocol/simple_file_system.zig deleted file mode 100644 index bdd0d1d909b0..000000000000 --- a/lib/std/os/uefi/protocol/simple_file_system.zig +++ /dev/null @@ -1,24 +0,0 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Guid = uefi.Guid; -const FileProtocol = uefi.protocol.File; -const Status = uefi.Status; -const cc = uefi.cc; - -pub const SimpleFileSystem = extern struct { - revision: u64, - _open_volume: *const fn (*const SimpleFileSystem, **const FileProtocol) callconv(cc) Status, - - pub fn openVolume(self: *const SimpleFileSystem, root: **const FileProtocol) Status { - return self._open_volume(self, root); - } - - pub const guid align(8) = Guid{ - .time_low = 0x0964e5b22, - .time_mid = 0x6459, - .time_high_and_version = 0x11d2, - .clock_seq_high_and_reserved = 0x8e, - .clock_seq_low = 0x39, - .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, - }; -}; diff --git a/lib/std/os/uefi/table.zig b/lib/std/os/uefi/table.zig index f1f4d56d2deb..63e7b33d5efc 100644 --- a/lib/std/os/uefi/table.zig +++ b/lib/std/os/uefi/table.zig @@ -1,12 +1,12 @@ pub const Header = @import("table/header.zig").Header; pub const BootServices = @import("table/boot_services.zig").BootServices; pub const RuntimeServices = @import("table/runtime_services.zig").RuntimeServices; -pub const SystemTable = @import("table/system.zig").SystemTable; +pub const System = @import("table/system.zig").System; const configuration_table = @import("table/configuration.zig"); -pub const ConfigurationTable = configuration_table.ConfigurationTable; -pub const RtPropertiesTable = configuration_table.RtPropertiesTable; -pub const MemoryAttributesTable = configuration_table.MemoryAttributesTable; +pub const Configuration = configuration_table.Configuration; +pub const RtProperties = configuration_table.RtProperties; +pub const MemoryAttributes = configuration_table.MemoryAttributes; test { @import("std").testing.refAllDeclsRecursive(@This()); diff --git a/lib/std/os/uefi/table/boot_services.zig b/lib/std/os/uefi/table/boot_services.zig index 94a8d98758d0..69f138972736 100644 --- a/lib/std/os/uefi/table/boot_services.zig +++ b/lib/std/os/uefi/table/boot_services.zig @@ -61,8 +61,8 @@ pub const BootServices = extern struct { _installConfigurationTable: *const fn (guid: *align(8) const Guid, table: ?*const anyopaque) callconv(cc) Status, _loadImage: *const fn (boot_policy: bool, parent_image_handle: Handle, device_path: ?*const DevicePathProtocol, source_buffer: ?[*]const u8, source_size: usize, image_handle: *?Handle) callconv(cc) Status, - _startImage: *const fn (image_handle: Handle, exit_data_size: ?*usize, exit_data: ?*[*]align(2) const u8) callconv(cc) Status, - _exit: *const fn (image_handle: Handle, exit_status: Status, exit_data_size: usize, exit_data: ?[*]align(2) const u8) callconv(cc) Status, + _startImage: *const fn (image_handle: Handle, exit_data_size: ?*usize, exit_data: ?*[*]const u16) callconv(cc) Status, + _exit: *const fn (image_handle: Handle, exit_status: Status, exit_data_size: usize, exit_data: ?[*]const u16) callconv(cc) Status, _unloadImage: *const fn (image_handle: Handle) callconv(cc) Status, _exitBootServices: *const fn (image_handle: Handle, map_key: usize) callconv(cc) Status, @@ -487,6 +487,17 @@ pub const BootServices = extern struct { } }; + /// Creates a memory map from a buffer. + pub fn initFromBuffer(buffer: []u8) MemoryMap { + return .{ + .map = @ptrCast(buffer.ptr), + .size = buffer.len, + .key = 0, + .descriptor_size = 0, + .descriptor_version = 0, + }; + } + /// Creates a memory map from a list of memory descriptors. pub fn initFromList(list: []bits.MemoryDescriptor) MemoryMap { return .{ @@ -521,7 +532,7 @@ pub const BootServices = extern struct { } }; - /// Returns the size of the current memory map. + /// Returns the size of the current memory map. Or null if the buffer size is large enough. /// /// It is recommended to call this in a loop until it returns `null`. pub fn getMemoryMapSize( @@ -986,7 +997,7 @@ pub const BootServices = extern struct { return image_handle; } - pub const ImageReturn = struct { Status, []align(2) const u8 }; + pub const ImageReturn = struct { Status, []const u16 }; /// Transfers control to a loaded image's entry point. /// @@ -998,10 +1009,10 @@ pub const BootServices = extern struct { image_handle: Handle, ) ImageReturn { var exit_data_size: usize = 0; - var exit_data: *[*]align(2) u8 = null; + var exit_data: *[*]u16 = null; const status = self._startImage(image_handle, &exit_data_size, &exit_data); - return .{ status, exit_data[0..exit_data_size] }; + return .{ status, exit_data[0 .. exit_data_size / 2] }; } /// Unloads an image. @@ -1022,11 +1033,11 @@ pub const BootServices = extern struct { image_handle: Handle, /// The image's exit status. exit_status: Status, - /// The exit data. This must begin with a null terminated UCS2 string. - exit_data: ?[]align(2) const u8, + /// The exit data. This must begin with a null terminated utf16 string. + exit_data: ?[]const u16, ) !void { if (exit_data) |data| { - try self._exit(image_handle, exit_status, data.len, data.ptr).err(); + try self._exit(image_handle, exit_status, data.len * 2, data.ptr).err(); } else { try self._exit(image_handle, exit_status, 0, null).err(); } diff --git a/lib/std/os/uefi/table/configuration.zig b/lib/std/os/uefi/table/configuration.zig index fd76d4243558..da5f7f155357 100644 --- a/lib/std/os/uefi/table/configuration.zig +++ b/lib/std/os/uefi/table/configuration.zig @@ -2,7 +2,7 @@ const bits = @import("../bits.zig"); const Guid = bits.Guid; -pub const ConfigurationTable = extern struct { +pub const Configuration = extern struct { vendor_guid: Guid, vendor_table: *const anyopaque, @@ -132,7 +132,7 @@ pub const RtPropertiesTable = extern struct { /// When published by the firmware, this table provides additional information about regions within the run-time memory /// blocks defined in `MemoryDescriptor` entries. -pub const MemoryAttributesTable = extern struct { +pub const MemoryAttributes = extern struct { pub const Flags = packed struct(u32) { /// Implies that runtime code includes the forward control flow guard instruction. rt_forward_cfg: bool, @@ -153,7 +153,7 @@ pub const MemoryAttributesTable = extern struct { /// An iterator over the memory descriptors. pub const Iterator = struct { - table: *const MemoryAttributesTable, + table: *const MemoryAttributes, /// The current index of the iterator. index: usize = 0, @@ -165,7 +165,7 @@ pub const MemoryAttributesTable = extern struct { const offset = iter.index * iter.table.descriptor_size; - const addr = @intFromPtr(iter.table) + @sizeOf(MemoryAttributesTable) + offset; + const addr = @intFromPtr(iter.table) + @sizeOf(MemoryAttributes) + offset; iter.index += 1; return @ptrFromInt(addr); @@ -173,12 +173,12 @@ pub const MemoryAttributesTable = extern struct { }; /// Returns an iterator over the memory map. - pub fn iterator(self: *const MemoryAttributesTable) Iterator { + pub fn iterator(self: *const MemoryAttributes) Iterator { return Iterator{ .table = self }; } /// Returns a pointer to the memory descriptor at the given index. - pub fn at(self: *const MemoryAttributesTable, index: usize) ?*bits.MemoryDescriptor { + pub fn at(self: *const MemoryAttributes, index: usize) ?*bits.MemoryDescriptor { if (index >= self.entries) return null; diff --git a/lib/std/os/uefi/table/system.zig b/lib/std/os/uefi/table/system.zig index 10cbdc1d5cd6..de044ff61f65 100644 --- a/lib/std/os/uefi/table/system.zig +++ b/lib/std/os/uefi/table/system.zig @@ -5,7 +5,7 @@ const protocol = @import("../protocol.zig"); /// The EFI System Table contains pointers to the runtime and boot services tables. /// /// As the system_table may grow with new UEFI versions, it is important to check hdr.header_size. -pub const SystemTable = extern struct { +pub const System = extern struct { hdr: table.Header, /// A null-terminated string that identifies the vendor that produces the system firmware of the platform. diff --git a/lib/std/posix.zig b/lib/std/posix.zig index e80e64b45e27..df1994c6f80e 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -754,7 +754,7 @@ pub fn exit(status: u8) noreturn { _ = bs.exit(uefi.handle, @enumFromInt(status), 0, null); } // If we can't exit, reboot the system instead. - uefi.system_table.runtime_services.resetSystem(.ResetCold, @enumFromInt(status), 0, null); + uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), 0, null); } system.exit(status); } From bbbd6ab5443da0236dc9375bb76a3672091d06e6 Mon Sep 17 00:00:00 2001 From: Nameless Date: Mon, 15 Jan 2024 16:50:38 -0600 Subject: [PATCH 07/23] std.os.uefi: clean up allocator, add very basic std.os bindings for read,write,close --- lib/std/os/uefi.zig | 69 ++++++--- lib/std/os/uefi/allocator.zig | 234 +++++++++++++++++++++++++++++ lib/std/os/uefi/pool_allocator.zig | 139 ----------------- lib/std/os/uefi/status.zig | 4 +- lib/std/posix.zig | 9 +- 5 files changed, 291 insertions(+), 164 deletions(-) create mode 100644 lib/std/os/uefi/allocator.zig delete mode 100644 lib/std/os/uefi/pool_allocator.zig diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 249d56be5c13..aa34ce3a9f3e 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -10,12 +10,10 @@ pub const bits = @import("uefi/bits.zig"); pub const Status = @import("uefi/status.zig").Status; pub const table = @import("uefi/table.zig"); -/// The memory type to allocate when using the pool -/// Defaults to .LoaderData, the default data allocation type -/// used by UEFI applications to allocate pool memory. -pub var efi_pool_memory_type: bits.MemoryDescriptor.Type = .LoaderData; -pub const pool_allocator = @import("uefi/pool_allocator.zig").pool_allocator; -pub const raw_pool_allocator = @import("uefi/pool_allocator.zig").raw_pool_allocator; +const allocator = @import("uefi/allocator.zig"); +pub const PageAllocator = allocator.PageAllocator; +pub const PoolAllocator = allocator.PoolAllocator; +pub const RawPoolAllocator = allocator.RawPoolAllocator; /// The EFI image's handle that is passed to its entry point. pub var handle: bits.Handle = undefined; @@ -37,33 +35,50 @@ pub fn close(fd: fd_t) void { } } -pub const ReadError = Status.EfiError; - -pub fn read(fd: fd_t, buf: []u8) ReadError!usize { +pub fn read(fd: fd_t, buf: []u8) std.os.ReadError!usize { switch (fd) { - .file => |p| p.read(fd.file, buf), + .file => |p| { + return p.read(fd.file, buf) catch |err| switch (err) { + error.NoMedia => return error.InputOutput, + error.DeviceError => return error.InputOutput, + error.VolumeCorrupted => return error.InputOutput, + else => return error.Unexpected, + }; + }, .simple_input => |p| { var index: usize = 0; while (index == 0) { - while (try p.readKeyStroke()) |key| { + while (p.readKeyStroke() catch |err| switch (err) { + error.DeviceError => return error.InputOutput, + else => return error.Unexpected, + }) |key| { if (key.unicodeChar != 0) { - index += try std.unicode.utf16leToUtf8(buf, &.{key.unicode_char}); + // this definitely isn't the right way to handle this, and it may fail on towards the limit of a single utf16 item. + index += std.unicode.utf16leToUtf8(buf, &.{key.unicode_char}) catch continue; } } } - return index; + return @intCast(index); }, - else => return error.EndOfFile, + else => return error.NotOpenForReading, // cannot read } } -pub const WriteError = Status.EfiError || error{InvalidUtf8}; - -pub fn write(fd: fd_t, buf: []const u8) WriteError!usize { +pub fn write(fd: fd_t, buf: []const u8) std.os.WriteError!usize { switch (fd) { - .file => |p| p.write(fd.file, buf), + .file => |p| { + return p.write(fd.file, buf) catch |err| switch (err) { + error.Unsupported => return error.NotOpenForWriting, + error.NoMedia => return error.InputOutput, + error.DeviceError => return error.InputOutput, + error.VolumeCorrupted => return error.InputOutput, + error.WriteProtected => return error.NotOpenForWriting, + error.AccessDenied => return error.AccessDenied, + else => return error.Unexpected, + }; + }, .simple_output => |p| { - const view = try std.unicode.Utf8View.init(buf); + const view = std.unicode.Utf8View.init(buf) catch unreachable; var iter = view.iterator(); // rudimentary utf16 writer @@ -72,7 +87,11 @@ pub fn write(fd: fd_t, buf: []const u8) WriteError!usize { while (iter.nextCodepoint()) |rune| { if (index + 1 >= utf16.len) { utf16[index] = 0; - try p.outputString(utf16[0..index :0]); + p.outputString(utf16[0..index :0]) catch |err| switch (err) { + error.DeviceError => return error.InputOutput, + error.Unsupported => return error.NotOpenForWriting, + else => return error.Unexpected, + }; index = 0; } @@ -98,10 +117,16 @@ pub fn write(fd: fd_t, buf: []const u8) WriteError!usize { if (index != 0) { utf16[index] = 0; - try p.outputString(utf16[0..index :0]); + p.outputString(utf16[0..index :0]) catch |err| switch (err) { + error.DeviceError => return error.InputOutput, + error.Unsupported => return error.NotOpenForWriting, + else => return error.Unexpected, + }; } + + return @intCast(buf.len); }, - else => return error.EndOfFile, + else => return error.NotOpenForWriting, // cannot write } } diff --git a/lib/std/os/uefi/allocator.zig b/lib/std/os/uefi/allocator.zig new file mode 100644 index 000000000000..f7c134dfc18e --- /dev/null +++ b/lib/std/os/uefi/allocator.zig @@ -0,0 +1,234 @@ +const std = @import("../../std.zig"); + +const mem = std.mem; +const uefi = std.os.uefi; + +const Allocator = mem.Allocator; + +const assert = std.debug.assert; + +/// Allocates memory in pages. +/// +/// This allocator is backed by `allocatePages` and is therefore only suitable for usage when Boot Services are available. +pub const PageAllocator = struct { + memory_type: uefi.bits.MemoryDescriptor.Type = .loader_data, + + pub fn allocator(self: *PageAllocator) Allocator { + return Allocator{ + .ptr = self, + .vtable = &vtable, + }; + } + + const vtable = Allocator.VTable{ + .alloc = alloc, + .resize = resize, + .free = free, + }; + + fn alloc( + ctx: *anyopaque, + len: usize, + log2_ptr_align: u8, + ret_addr: usize, + ) ?[*]u8 { + _ = ret_addr; + const self: *PageAllocator = @ptrCast(@alignCast(ctx)); + + assert(len > 0); + assert(log2_ptr_align <= 12); // 4KiB max alignment + const pages = mem.alignForward(usize, len, 4096) / 4096; + + const buf = uefi.system_table.boot_services.?.allocatePages(.any, self.memory_type, pages) catch return null; + return buf.ptr; + } + + fn resize( + ctx: *anyopaque, + buf: []u8, + log2_buf_align: u8, + new_len: usize, + ret_addr: usize, + ) bool { + _ = .{ log2_buf_align, ret_addr }; + const self: *PageAllocator = @ptrCast(@alignCast(ctx)); + + // If the buffer was originally larger than the new length, we can grow or shrink it in place. + const original_len = mem.alignForward(usize, buf.len, 4096); + const new_aligned_len = mem.alignForward(usize, new_len, 4096); + + if (original_len >= new_aligned_len) return true; + + const new_pages_required = (new_aligned_len - original_len) / 4096; + const start_of_new_pages = @intFromPtr(buf.ptr) + original_len; + + // Try to allocate the necessary pages at the end of the buffer. + const new_pages = uefi.system_table.boot_services.?.allocatePages(.{ .at_address = start_of_new_pages }, self.memory_type, new_pages_required) catch return false; + _ = new_pages; + + // If the above function succeeds, then the new pages were successfully allocated. + return true; + } + + fn free( + ctx: *anyopaque, + buf: []u8, + log2_buf_align: u8, + ret_addr: usize, + ) void { + _ = .{ ctx, log2_buf_align, ret_addr }; + + const aligned_len = mem.alignForward(usize, buf.len, 4096); + const ptr: [*]align(4096) u8 = @alignCast(buf.ptr); + + uefi.system_table.boot_services.?.freePages(ptr[0..aligned_len]); + } +}; + +/// Supports the full std.mem.Allocator interface, including up to page alignment. +/// +/// This allocator is backed by `allocatePool` and is therefore only suitable for usage when Boot Services are available. +pub const PoolAllocator = struct { + memory_type: uefi.bits.MemoryDescriptor.Type = .loader_data, + + pub fn allocator(self: *PoolAllocator) Allocator { + return Allocator{ + .ptr = self, + .vtable = &vtable, + }; + } + + const vtable = Allocator.VTable{ + .alloc = alloc, + .resize = resize, + .free = free, + }; + + const Header = struct { + ptr: [*]align(8) u8, + len: usize, + }; + + fn getHeader(ptr: [*]u8) *Header { + return @ptrCast(ptr - @sizeOf(Header)); + } + + fn alloc( + ctx: *anyopaque, + len: usize, + log2_ptr_align: u8, + ret_addr: usize, + ) ?[*]u8 { + _ = ret_addr; + const self: *PoolAllocator = @ptrCast(@alignCast(ctx)); + + assert(len > 0); + + const ptr_align = @as(usize, 1) << @as(Allocator.Log2Align, @intCast(log2_ptr_align)); + + // The maximum size of the metadata and any alignment padding. + const metadata_len = mem.alignForward(usize, @sizeOf(Header), ptr_align); + + const full_len = metadata_len + len; + + const buf = uefi.system_table.boot_services.?.allocatePool(self.memory_type, full_len) catch return null; + const unaligned_ptr = buf.ptr; + + const unaligned_addr = @intFromPtr(unaligned_ptr); + const aligned_addr = mem.alignForward(usize, unaligned_addr + @sizeOf(Header), ptr_align); + + const aligned_ptr: [*]u8 = @ptrFromInt(aligned_addr); + getHeader(aligned_ptr).ptr = unaligned_ptr; + getHeader(aligned_ptr).len = unaligned_addr + full_len - aligned_addr; + + return aligned_ptr; + } + + fn resize( + ctx: *anyopaque, + buf: []u8, + log2_buf_align: u8, + new_len: usize, + ret_addr: usize, + ) bool { + _ = .{ ctx, log2_buf_align, ret_addr }; + + // If the buffer was originally larger than the new length, we can grow or shrink it in place. + if (getHeader(buf.ptr).len >= new_len) return true; + + // Otherwise, we cannot grow the buffer. + return false; + } + + fn free( + ctx: *anyopaque, + buf: []u8, + log2_buf_align: u8, + ret_addr: usize, + ) void { + _ = .{ ctx, log2_buf_align, ret_addr }; + uefi.system_table.boot_services.?.freePool(getHeader(buf.ptr).ptr); + } +}; + +/// Asserts all allocations are at most 8 byte aligned. This is the highest alignment UEFI will give us directly. +/// +/// This allocator is backed by `allocatePool` and is therefore only suitable for usage when Boot Services are available. +pub const RawPoolAllocator = struct { + memory_type: uefi.bits.MemoryDescriptor.Type = .loader_data, + + pub fn allocator(self: *RawPoolAllocator) Allocator { + return Allocator{ + .ptr = self, + .vtable = &vtable, + }; + } + + pub const vtable = Allocator.VTable{ + .alloc = alloc, + .resize = resize, + .free = free, + }; + + fn alloc( + ctx: *anyopaque, + len: usize, + log2_ptr_align: u8, + ret_addr: usize, + ) ?[*]u8 { + _ = ret_addr; + const self: *RawPoolAllocator = @ptrCast(@alignCast(ctx)); + + // UEFI pool allocations are 8 byte aligned, so we can't do better than that. + std.debug.assert(log2_ptr_align <= 3); + + const buf = uefi.system_table.boot_services.?.allocatePool(self.memory_type, len) catch return null; + return buf.ptr; + } + + fn resize( + ctx: *anyopaque, + buf: []u8, + log2_buf_align: u8, + new_len: usize, + ret_addr: usize, + ) bool { + _ = .{ ctx, log2_buf_align, ret_addr }; + + // The original capacity is not known, so we can't ever grow the buffer. + if (new_len > buf.len) return false; + + // If this is a shrink, it will happen in place. + return true; + } + + fn free( + ctx: *anyopaque, + buf: []u8, + log2_buf_align: u8, + ret_addr: usize, + ) void { + _ = .{ ctx, log2_buf_align, ret_addr }; + uefi.system_table.boot_services.?.freePool(@alignCast(buf)); + } +}; diff --git a/lib/std/os/uefi/pool_allocator.zig b/lib/std/os/uefi/pool_allocator.zig deleted file mode 100644 index 649fd8485884..000000000000 --- a/lib/std/os/uefi/pool_allocator.zig +++ /dev/null @@ -1,139 +0,0 @@ -const std = @import("../../std.zig"); - -const mem = std.mem; -const uefi = std.os.uefi; - -const assert = std.debug.assert; - -const Allocator = mem.Allocator; - -const UefiPoolAllocator = struct { - fn getHeader(ptr: [*]u8) *[*]align(8) u8 { - return @as(*[*]align(8) u8, @ptrFromInt(@intFromPtr(ptr) - @sizeOf(usize))); - } - - fn alloc( - _: *anyopaque, - len: usize, - log2_ptr_align: u8, - ret_addr: usize, - ) ?[*]u8 { - _ = ret_addr; - - assert(len > 0); - - const ptr_align = @as(usize, 1) << @as(Allocator.Log2Align, @intCast(log2_ptr_align)); - - const metadata_len = mem.alignForward(usize, @sizeOf(usize), ptr_align); - - const full_len = metadata_len + len; - - var unaligned_ptr: [*]align(8) u8 = undefined; - if (uefi.system_table.boot_services.?.allocatePool(uefi.efi_pool_memory_type, full_len, &unaligned_ptr) != .Success) return null; - - const unaligned_addr = @intFromPtr(unaligned_ptr); - const aligned_addr = mem.alignForward(usize, unaligned_addr + @sizeOf(usize), ptr_align); - - const aligned_ptr = unaligned_ptr + (aligned_addr - unaligned_addr); - getHeader(aligned_ptr).* = unaligned_ptr; - - return aligned_ptr; - } - - fn resize( - _: *anyopaque, - buf: []u8, - log2_old_ptr_align: u8, - new_len: usize, - ret_addr: usize, - ) bool { - _ = ret_addr; - - if (new_len > buf.len) return false; - - _ = mem.alignAllocLen(buf.len, new_len, log2_old_ptr_align); - - return true; - } - - fn free( - _: *anyopaque, - buf: []u8, - log2_old_ptr_align: u8, - ret_addr: usize, - ) void { - _ = log2_old_ptr_align; - _ = ret_addr; - _ = uefi.system_table.boot_services.?.freePool(getHeader(buf.ptr).*); - } -}; - -/// Supports the full Allocator interface, including alignment. -/// For a direct call of `allocatePool`, see `raw_pool_allocator`. -pub const pool_allocator = Allocator{ - .ptr = undefined, - .vtable = &pool_allocator_vtable, -}; - -const pool_allocator_vtable = Allocator.VTable{ - .alloc = UefiPoolAllocator.alloc, - .resize = UefiPoolAllocator.resize, - .free = UefiPoolAllocator.free, -}; - -/// Asserts allocations are 8 byte aligned and calls `boot_services.allocatePool`. -pub const raw_pool_allocator = Allocator{ - .ptr = undefined, - .vtable = &raw_pool_allocator_table, -}; - -const raw_pool_allocator_table = Allocator.VTable{ - .alloc = uefi_alloc, - .resize = uefi_resize, - .free = uefi_free, -}; - -fn uefi_alloc( - _: *anyopaque, - len: usize, - log2_ptr_align: u8, - ret_addr: usize, -) ?[*]u8 { - _ = ret_addr; - - std.debug.assert(log2_ptr_align <= 3); - - var ptr: [*]align(8) u8 = undefined; - if (uefi.system_table.boot_services.?.allocatePool(uefi.efi_pool_memory_type, len, &ptr) != .Success) return null; - - return ptr; -} - -fn uefi_resize( - _: *anyopaque, - buf: []u8, - log2_old_ptr_align: u8, - new_len: usize, - ret_addr: usize, -) bool { - _ = ret_addr; - - std.debug.assert(log2_old_ptr_align <= 3); - - if (new_len > buf.len) return false; - - _ = mem.alignAllocLen(buf.len, new_len, 8); - - return true; -} - -fn uefi_free( - _: *anyopaque, - buf: []u8, - log2_old_ptr_align: u8, - ret_addr: usize, -) void { - _ = log2_old_ptr_align; - _ = ret_addr; - _ = uefi.system_table.boot_services.?.freePool(@alignCast(buf.ptr)); -} diff --git a/lib/std/os/uefi/status.zig b/lib/std/os/uefi/status.zig index 476e0b7f44fd..561a4155149a 100644 --- a/lib/std/os/uefi/status.zig +++ b/lib/std/os/uefi/status.zig @@ -186,7 +186,7 @@ pub const Status = enum(usize) { HostUnreachable, ProtocolUnreachable, PortUnreachable, - ConnectionFin, + ConnectionFinished, ConnectionReset, ConnectionRefused, }; @@ -238,7 +238,7 @@ pub const Status = enum(usize) { .host_unreachable => return error.HostUnreachable, .protocol_unreachable => return error.ProtocolUnreachable, .port_unreachable => return error.PortUnreachable, - .connection_finished => return error.ConnectionFin, + .connection_finished => return error.ConnectionFinished, .connection_reset => return error.ConnectionReset, .connection_refused => return error.ConnectionRefused, else => return error.Unknown, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index df1994c6f80e..ef64e8b66a73 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -38,6 +38,7 @@ const use_libc = builtin.link_libc or switch (native_os) { const linux = std.os.linux; const windows = std.os.windows; const wasi = std.os.wasi; +const uefi = std.os.uefi; /// A libc-compatible API layer. pub const system = if (use_libc) @@ -751,7 +752,7 @@ pub fn exit(status: u8) noreturn { // exit() is only available if exitBootServices() has not been called yet. // This call to exit should not fail, so we don't care about its return value. if (uefi.system_table.boot_services) |bs| { - _ = bs.exit(uefi.handle, @enumFromInt(status), 0, null); + bs.exit(uefi.handle, @enumFromInt(status), 0, null) catch {}; } // If we can't exit, reboot the system instead. uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), 0, null); @@ -794,6 +795,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { if (native_os == .windows) { return windows.ReadFile(fd, buf, null); } + if (builtin.os.tag == .uefi) { + return uefi.read(fd, buf); + } if (native_os == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ .iov_base = buf.ptr, @@ -1183,6 +1187,9 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { if (native_os == .windows) { return windows.WriteFile(fd, bytes, null); } + if (builtin.os.tag == .uefi) { + return uefi.write(fd, bytes); + } if (native_os == .wasi and !builtin.link_libc) { const ciovs = [_]iovec_const{iovec_const{ From 1a150d6be75301c4e65b015023242faeac4221f2 Mon Sep 17 00:00:00 2001 From: Nameless Date: Tue, 16 Jan 2024 20:22:33 -0600 Subject: [PATCH 08/23] std.os.uefi: fix bugs, add basic debug support --- lib/std/builtin.zig | 18 --- lib/std/debug.zig | 69 ++++++++++- lib/std/fs.zig | 16 +++ lib/std/fs/Dir.zig | 38 ++++++ lib/std/fs/File.zig | 31 +++++ lib/std/heap.zig | 2 + lib/std/io/tty.zig | 2 +- lib/std/os/uefi.zig | 70 +++++++++-- lib/std/os/uefi/allocator.zig | 6 +- lib/std/os/uefi/bits.zig | 2 +- .../protocol/console/simple_text_input.zig | 5 +- lib/std/os/uefi/table/boot_services.zig | 50 ++++---- lib/std/os/uefi/table/header.zig | 2 +- lib/std/os/uefi/table/system.zig | 2 +- lib/std/os/uefi/ucontext.zig | 110 ++++++++++++++++++ lib/std/start.zig | 29 ++++- 16 files changed, 388 insertions(+), 64 deletions(-) create mode 100644 lib/std/os/uefi/ucontext.zig diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 208a621eefe3..e43402acd6bf 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -784,24 +784,6 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace, ret_addr std.debug.print("{s}", .{msg}); std.posix.abort(); }, - .uefi => { - const uefi = std.os.uefi; - - if (uefi.system_table.boot_services) |bs| { - uefi.system_table.std_err.?.setAttribute(.{ .foreground = .red }) catch {}; - std.debug.print("{s}", .{msg}); - uefi.system_table.std_err.?.setAttribute(.{}) catch {}; - - if (std.unicode.utf8ToUtf16LeWithNull(uefi.raw_pool_allocator, msg)) |data| { - _ = bs.exit(uefi.handle, .aborted, data[0 .. data.len + 1]); - } else |_| { - _ = bs.exit(uefi.handle, .aborted, null); - } - } - - // Didn't have boot_services, just fallback to whatever. - std.posix.abort(); - }, .cuda, .amdhsa => std.posix.abort(), .plan9 => { var status: [std.os.plan9.ERRMAX]u8 = undefined; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index d1d6201b807e..1eb66ede659d 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -14,6 +14,7 @@ const pdb = std.pdb; const root = @import("root"); const File = std.fs.File; const windows = std.os.windows; +const uefi = std.os.uefi; const native_arch = builtin.cpu.arch; const native_os = builtin.os.tag; const native_endian = native_arch.endian(); @@ -687,6 +688,29 @@ pub const StackIterator = struct { } return true; + } else if (native_os == .uefi) { + if (uefi.system_table.boot_services) |boot_services| { + var buffer = uefi.global_page_allocator.allocator().alloc(u8, 4096) catch return true; + defer uefi.global_page_allocator.allocator().free(buffer); + + while (boot_services.getMemoryMapSize(buffer.len) catch return true) |new_len| { + buffer = uefi.global_page_allocator.allocator().realloc(buffer, new_len) catch return true; + } + + var map = uefi.table.BootServices.MemoryMap.initFromBuffer(buffer); + boot_services.getMemoryMap(&map) catch return true; + + var it = map.iterator(); + while (it.next()) |entry| { + if (entry.physical_start <= address and address < entry.physical_start + entry.number_of_pages * mem.page_size) { + return true; + } + } + + return false; + } else { + return true; + } } else if (@hasDecl(posix.system, "msync") and native_os != .wasi and native_os != .emscripten) { posix.msync(aligned_memory, posix.MSF.ASYNC) catch |err| { switch (err) { @@ -1051,6 +1075,7 @@ pub fn openSelfDebugInfo(allocator: mem.Allocator) OpenSelfDebugInfoError!DebugI .solaris, .illumos, .windows, + .uefi, => return try DebugInfo.init(allocator), else => return error.UnsupportedOperatingSystem, } @@ -1758,6 +1783,8 @@ pub const DebugInfo = struct { return self.lookupModuleHaiku(address); } else if (comptime builtin.target.isWasm()) { return self.lookupModuleWasm(address); + } else if (native_os == .uefi) { + return self.lookupModuleUefi(address); } else { return self.lookupModuleDl(address); } @@ -1775,6 +1802,8 @@ pub const DebugInfo = struct { return null; } else if (comptime builtin.target.isWasm()) { return null; + } else if (native_os == .uefi) { + return null; } else { return self.lookupModuleNameDl(address); } @@ -1983,6 +2012,37 @@ pub const DebugInfo = struct { return null; } + fn lookupModuleUefi(self: *DebugInfo, address: usize) !*ModuleDebugInfo { + if (uefi.system_table.boot_services) |boot_services| { + const handles = try boot_services.locateHandleBuffer(.{ .by_protocol = &uefi.protocol.LoadedImage.guid }); + defer boot_services.freePool(mem.sliceAsBytes(handles)); + + for (handles) |handle| { + const loaded_image: *const uefi.protocol.LoadedImage = try boot_services.openProtocol(handle, uefi.protocol.LoadedImage, uefi.handle, null, .{ .by_handle_protocol = true }) orelse continue; + + if (address >= @intFromPtr(loaded_image.image_base) and address < @intFromPtr(loaded_image.image_base) + loaded_image.image_size) { + if (self.address_map.get(@intFromPtr(loaded_image.image_base))) |obj_di| { + return obj_di; + } + + const obj_di = try self.allocator.create(ModuleDebugInfo); + errdefer self.allocator.destroy(obj_di); + + const mapped_module = @as([*]const u8, @ptrFromInt(@intFromPtr(loaded_image.image_base)))[0..loaded_image.image_size]; + var coff_obj = try coff.Coff.init(mapped_module, true); + + obj_di.* = try readCoffDebugInfo(self.allocator, &coff_obj); + obj_di.base_address = @intFromPtr(loaded_image.image_base); + + try self.address_map.putNoClobber(@intFromPtr(loaded_image.image_base), obj_di); + return obj_di; + } + } + } + + return error.MissingDebugInfo; + } + fn lookupModuleNameDl(self: *DebugInfo, address: usize) ?[]const u8 { _ = self; @@ -2416,10 +2476,11 @@ pub const ModuleDebugInfo = switch (native_os) { _ = allocator; _ = address; - return switch (self.debug_data) { - .dwarf => |*dwarf| dwarf, - else => null, - }; + if (self.dwarf) |*dwarf| { + return dwarf; + } + + return null; } }, .linux, .netbsd, .freebsd, .dragonfly, .openbsd, .haiku, .solaris, .illumos => struct { diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 109ce7fe2347..79c1dae9487b 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -258,6 +258,8 @@ pub fn cwd() Dir { return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle }; } else if (native_os == .wasi) { return .{ .fd = std.options.wasiCwd() }; + } else if (native_os == .uefi) { + return defaultUefiCwd(); } else { return .{ .fd = posix.AT.FDCWD }; } @@ -268,6 +270,20 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t { return 3; } +pub fn defaultUefiCwd() Dir { + const uefi = std.os.uefi; + + if (uefi.system_table.boot_services) |boot_services| blk: { + const loaded_image = boot_services.openProtocol(uefi.handle, uefi.protocol.LoadedImage, uefi.handle, null, .{ .by_handle_protocol = true }) catch break :blk orelse break :blk; + + const simple_file_system = boot_services.openProtocol(loaded_image.device_handle.?, uefi.protocol.SimpleFileSystem, uefi.handle, null, .{ .by_handle_protocol = true }) catch break :blk orelse break :blk; + + return Dir{ .fd = .{ .file = simple_file_system.openVolume() catch break :blk } }; + } + + return Dir{ .fd = .none }; +} + /// Opens a directory at the given path. The directory is a system resource that remains /// open until `close` is called on the result. /// See `openDirAbsoluteZ` for a function that accepts a null-terminated path. diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 74d91239f810..e15eba2017b2 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -797,6 +797,25 @@ pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.Ope const fd = try posix.openatWasi(self.fd, sub_path, .{}, .{}, .{}, base, .{}); return .{ .handle = fd }; } + if (native_os == .uefi) { + var temp_path: std.os.windows.PathSpace = undefined; + temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, sub_path); + temp_path.data[temp_path.len] = 0; + + const uefi = std.os.uefi; + + const handle = try uefi.openat(self.fd, temp_path.span(), .{ + .read = flags.isRead(), + .write = flags.isWrite(), + }); + errdefer uefi.close(handle); + + return File{ + .handle = handle, + .capable_io_mode = std.io.default_mode, + .intended_io_mode = flags.intended_io_mode, + }; + } const path_c = try posix.toPosixPath(sub_path); return self.openFileZ(&path_c, flags); } @@ -951,6 +970,25 @@ pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File }, .{}), }; } + if (native_os == .uefi) { + var temp_path: std.os.windows.PathSpace = undefined; + temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, sub_path); + temp_path.data[temp_path.len] = 0; + const uefi = std.os.uefi; + + const handle = try uefi.openat(self.fd, temp_path.span(), .{ + .read = true, + .write = true, + .create = true, + }); + errdefer uefi.close(handle); + + return File{ + .handle = handle, + .capable_io_mode = std.io.default_mode, + .intended_io_mode = flags.intended_io_mode, + }; + } const path_c = try posix.toPosixPath(sub_path); return self.createFileZ(&path_c, flags); } diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index daac1b632b85..2e189b875a0c 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -290,6 +290,14 @@ pub const SeekError = posix.SeekError; /// Repositions read/write file offset relative to the current offset. /// TODO: integrate with async I/O pub fn seekBy(self: File, offset: i64) SeekError!void { + if (builtin.os.tag == .uefi) { + if (self.handle == .file) { + return self.handle.file.movePosition(offset) catch return error.Unseekable; + } else { + return error.Unseekable; + } + } + return posix.lseek_CUR(self.handle, offset); } @@ -302,6 +310,14 @@ pub fn seekFromEnd(self: File, offset: i64) SeekError!void { /// Repositions read/write file offset relative to the beginning. /// TODO: integrate with async I/O pub fn seekTo(self: File, offset: u64) SeekError!void { + if (builtin.os.tag == .uefi) { + if (self.handle == .file) { + return self.handle.file.setPosition(offset) catch return error.Unseekable; + } else { + return error.Unseekable; + } + } + return posix.lseek_SET(self.handle, offset); } @@ -309,6 +325,14 @@ pub const GetSeekPosError = posix.SeekError || posix.FStatError; /// TODO: integrate with async I/O pub fn getPos(self: File) GetSeekPosError!u64 { + if (builtin.os.tag == .uefi) { + if (self.handle == .file) { + return self.handle.file.getPosition() catch return error.Unseekable; + } else { + return error.Unseekable; + } + } + return posix.lseek_CUR_get(self.handle); } @@ -317,6 +341,13 @@ pub fn getEndPos(self: File) GetSeekPosError!u64 { if (builtin.os.tag == .windows) { return windows.GetFileSizeEx(self.handle); } + if (builtin.os.tag == .uefi) { + if (self.handle == .file) { + return self.handle.file.getEndPosition() catch return error.Unseekable; + } else { + return error.Unseekable; + } + } return (try self.stat()).size; } diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 9b99f7e1d9af..bc66b9d81883 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -236,6 +236,8 @@ else if (builtin.target.os.tag == .plan9) .ptr = undefined, .vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable, } +else if (builtin.target.os.tag == .uefi) + os.uefi.global_page_allocator.allocator() else Allocator{ .ptr = undefined, diff --git a/lib/std/io/tty.zig b/lib/std/io/tty.zig index c02e3714409e..738387b8fc65 100644 --- a/lib/std/io/tty.zig +++ b/lib/std/io/tty.zig @@ -9,7 +9,7 @@ const native_os = builtin.os.tag; /// This includes feature checks for ANSI escape codes and the Windows console API, as well as /// respecting the `NO_COLOR` and `YES_COLOR` environment variables to override the default. pub fn detectConfig(file: File) Config { - const force_color: ?bool = if (builtin.os.tag == .wasi) + const force_color: ?bool = if (builtin.os.tag == .wasi or builtin.os.tag == .uefi) null // wasi does not support environment variables else if (process.hasEnvVarConstant("NO_COLOR")) false diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index aa34ce3a9f3e..68685182ca55 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -15,34 +15,69 @@ pub const PageAllocator = allocator.PageAllocator; pub const PoolAllocator = allocator.PoolAllocator; pub const RawPoolAllocator = allocator.RawPoolAllocator; +pub var global_page_allocator = PageAllocator{}; + /// The EFI image's handle that is passed to its entry point. pub var handle: bits.Handle = undefined; /// A pointer to the EFI System Table that is passed to the EFI image's entry point. pub var system_table: *table.System = undefined; +pub const ino_t = u64; +pub const mode_t = u64; + pub const fd_t = union(enum) { file: *const protocol.File, simple_output: *const protocol.SimpleTextOutput, simple_input: *const protocol.SimpleTextInput, + none: void, // used to refer to a file descriptor that is not open and cannot do anything }; +fn unexpectedError(err: anyerror) error{Unexpected} { + std.log.err("unexpected error: {}\n", .{err}); + return error.Unexpected; +} + pub fn close(fd: fd_t) void { switch (fd) { - .file => |p| p.close(fd.file), + .file => |p| p.close(), .simple_output => |p| p.reset(true) catch {}, .simple_input => |p| p.reset(true) catch {}, + .none => {}, + } +} + +pub fn openat(dirfd: fd_t, path: [:0]const u16, flags: protocol.File.OpenMode) !fd_t { + switch (dirfd) { + .file => |p| { + const fd = p.open(path, flags, .{}) catch |err| switch (err) { + error.NotFound => return error.FileNotFound, + error.NoMedia => return error.NoDevice, + error.MediaChanged => return error.NoDevice, + error.DeviceError => return error.NoDevice, + error.VolumeCorrupted => return error.NoDevice, + error.WriteProtected => return error.AccessDenied, + error.AccessDenied => return error.AccessDenied, + error.OutOfResources => return error.SystemResources, + error.InvalidParameter => return error.FileNotFound, + else => |e| return unexpectedError(e), + }; + return .{ .file = fd }; + }, + .simple_output => return error.NotDir, + .simple_input => return error.NotDir, + .none => return error.NotDir, } } pub fn read(fd: fd_t, buf: []u8) std.os.ReadError!usize { switch (fd) { .file => |p| { - return p.read(fd.file, buf) catch |err| switch (err) { + return p.read(buf) catch |err| switch (err) { error.NoMedia => return error.InputOutput, error.DeviceError => return error.InputOutput, error.VolumeCorrupted => return error.InputOutput, - else => return error.Unexpected, + else => |e| return unexpectedError(e), }; }, .simple_input => |p| { @@ -50,9 +85,9 @@ pub fn read(fd: fd_t, buf: []u8) std.os.ReadError!usize { while (index == 0) { while (p.readKeyStroke() catch |err| switch (err) { error.DeviceError => return error.InputOutput, - else => return error.Unexpected, + else => |e| return unexpectedError(e), }) |key| { - if (key.unicodeChar != 0) { + if (key.unicode_char != 0) { // this definitely isn't the right way to handle this, and it may fail on towards the limit of a single utf16 item. index += std.unicode.utf16leToUtf8(buf, &.{key.unicode_char}) catch continue; } @@ -67,14 +102,14 @@ pub fn read(fd: fd_t, buf: []u8) std.os.ReadError!usize { pub fn write(fd: fd_t, buf: []const u8) std.os.WriteError!usize { switch (fd) { .file => |p| { - return p.write(fd.file, buf) catch |err| switch (err) { + return p.write(buf) catch |err| switch (err) { error.Unsupported => return error.NotOpenForWriting, error.NoMedia => return error.InputOutput, error.DeviceError => return error.InputOutput, error.VolumeCorrupted => return error.InputOutput, error.WriteProtected => return error.NotOpenForWriting, error.AccessDenied => return error.AccessDenied, - else => return error.Unexpected, + else => |e| return unexpectedError(e), }; }, .simple_output => |p| { @@ -96,7 +131,12 @@ pub fn write(fd: fd_t, buf: []const u8) std.os.WriteError!usize { } if (rune < 0x10000) { - utf16[index] = rune; + if (rune == '\n') { + utf16[index] = '\r'; + index += 1; + } + + utf16[index] = @intCast(rune); index += 1; } else { const high = @as(u16, @intCast((rune - 0x10000) >> 10)) + 0xD800; @@ -130,6 +170,20 @@ pub fn write(fd: fd_t, buf: []const u8) std.os.WriteError!usize { } } +pub fn getFileSize(fd: fd_t) !u64 { + switch (fd) { + .file => |p| { + return p.getEndPosition() catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +const uctx = @import("uefi/ucontext.zig"); +pub const getcontext = uctx.getcontext; +pub const ucontext_t = uctx.ucontext_t; +pub const REG = uctx.REG; + test { _ = table; _ = protocol; diff --git a/lib/std/os/uefi/allocator.zig b/lib/std/os/uefi/allocator.zig index f7c134dfc18e..67c7afc4a395 100644 --- a/lib/std/os/uefi/allocator.zig +++ b/lib/std/os/uefi/allocator.zig @@ -109,7 +109,7 @@ pub const PoolAllocator = struct { len: usize, }; - fn getHeader(ptr: [*]u8) *Header { + fn getHeader(ptr: [*]u8) *align(1) Header { return @ptrCast(ptr - @sizeOf(Header)); } @@ -167,7 +167,9 @@ pub const PoolAllocator = struct { ret_addr: usize, ) void { _ = .{ ctx, log2_buf_align, ret_addr }; - uefi.system_table.boot_services.?.freePool(getHeader(buf.ptr).ptr); + const header = getHeader(buf.ptr); + + uefi.system_table.boot_services.?.freePool(header.ptr[0..header.len]); } }; diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig index 0a7fd626989a..14e3a6a69576 100644 --- a/lib/std/os/uefi/bits.zig +++ b/lib/std/os/uefi/bits.zig @@ -397,7 +397,7 @@ pub const FileInfo = extern struct { /// The attributes of a file. pub const Attributes = packed struct(u64) { - read_only: bool = true, + read_only: bool = false, hidden: bool = false, system: bool = false, reserved: bool = false, diff --git a/lib/std/os/uefi/protocol/console/simple_text_input.zig b/lib/std/os/uefi/protocol/console/simple_text_input.zig index 4cfdcb9b0b65..6c766ef1422e 100644 --- a/lib/std/os/uefi/protocol/console/simple_text_input.zig +++ b/lib/std/os/uefi/protocol/console/simple_text_input.zig @@ -29,7 +29,10 @@ pub const SimpleTextInput = extern struct { switch (self._read_key_stroke(self, &input_key)) { .success => return input_key, .not_ready => return null, - else => |s| return s.err(), + else => |s| { + try s.err(); + unreachable; + }, } } diff --git a/lib/std/os/uefi/table/boot_services.zig b/lib/std/os/uefi/table/boot_services.zig index 69f138972736..9c22291f11db 100644 --- a/lib/std/os/uefi/table/boot_services.zig +++ b/lib/std/os/uefi/table/boot_services.zig @@ -33,7 +33,7 @@ pub const BootServices = extern struct { _raiseTpl: *const fn (new_tpl: TaskPriorityLevel) callconv(cc) TaskPriorityLevel, _restoreTpl: *const fn (old_tpl: TaskPriorityLevel) callconv(cc) void, - _allocatePages: *const fn (alloc_type: AllocateType, mem_type: bits.MemoryDescriptor.Type, pages: usize, memory: PhysicalAddress) callconv(cc) Status, + _allocatePages: *const fn (alloc_type: AllocateType.Enum, mem_type: bits.MemoryDescriptor.Type, pages: usize, memory: PhysicalAddress) callconv(cc) Status, _freePages: *const fn (memory: [*]align(4096) u8, pages: usize) callconv(cc) Status, _getMemoryMap: *const fn (mmap_size: *usize, mmap: ?*anyopaque, mapKey: *MemoryMap.Key, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status, _allocatePool: *const fn (pool_type: bits.MemoryDescriptor.Type, size: usize, buffer: *[*]align(8) u8) callconv(cc) Status, @@ -56,7 +56,7 @@ pub const BootServices = extern struct { reserved: *const anyopaque, _registerProtocolNotify: *const fn (protocol: *align(8) const Guid, event: Event, registration: *RegistrationValue) callconv(cc) Status, - _locateHandle: *const fn (search_type: LocateSearchType, protocol: ?*align(8) const Guid, search_key: ?RegistrationValue, buffer_size: *usize, buffer: [*]Handle) callconv(cc) Status, + _locateHandle: *const fn (search_type: LocateSearchType.Enum, protocol: ?*align(8) const Guid, search_key: ?RegistrationValue, buffer_size: *usize, buffer: [*]Handle) callconv(cc) Status, _locateDevicePath: *const fn (protocol: *align(8) const Guid, device_path: **const DevicePathProtocol, device: *Handle) callconv(cc) Status, _installConfigurationTable: *const fn (guid: *align(8) const Guid, table: ?*const anyopaque) callconv(cc) Status, @@ -78,7 +78,7 @@ pub const BootServices = extern struct { _closeProtocol: *const fn (handle: Handle, protocol: *align(8) const Guid, agent_handle: Handle, controller_handle: ?Handle) callconv(cc) Status, _openProtocolInformation: *const fn (handle: Handle, protocol: *align(8) const Guid, entry_buffer: *[*]const ProtocolInformationEntry, entry_count: *usize) callconv(cc) Status, _protocolsPerHandle: *const fn (handle: Handle, protocol_buffer: *[*]*align(8) const Guid, protocol_buffer_count: *usize) callconv(cc) Status, - _locateHandleBuffer: *const fn (search_type: LocateSearchType, protocol: ?*align(8) const Guid, registration: ?RegistrationValue, num_handles: *usize, buffer: *[*]Handle) callconv(cc) Status, + _locateHandleBuffer: *const fn (search_type: LocateSearchType.Enum, protocol: ?*align(8) const Guid, registration: ?RegistrationValue, num_handles: *usize, buffer: *[*]Handle) callconv(cc) Status, _locateProtocol: *const fn (protocol: *align(8) const Guid, registration: ?RegistrationValue, interface: *?ProtocolInterface) callconv(cc) Status, // TODO: use callconv(cc) instead once that works @@ -421,17 +421,19 @@ pub const BootServices = extern struct { /// The number of contiguous 4 KiB pages to allocate. pages: usize, ) ![]align(4096) u8 { - var buffer: [*]align(4096) u8 = switch (alloc_type) { - .any => @ptrFromInt(0), - .max_address => |addr| @ptrFromInt(addr), - .at_address => |addr| @ptrFromInt(addr), + var buffer_addr: usize = switch (alloc_type) { + .any => 0, + .max_address => |addr| addr, + .at_address => |addr| addr, }; // EFI memory addresses are always 64-bit, even on 32-bit systems - const pointer: PhysicalAddress = @intFromPtr(&buffer); + const pointer: PhysicalAddress = @intFromPtr(&buffer_addr); try self._allocatePages(alloc_type, mem_type, pages, pointer).err(); - return buffer[0 .. pages * 4096]; + const addr: [*]align(4096) u8 = @ptrFromInt(buffer_addr); + + return addr[0 .. pages * 4096]; } /// Frees memory pages. @@ -492,7 +494,7 @@ pub const BootServices = extern struct { return .{ .map = @ptrCast(buffer.ptr), .size = buffer.len, - .key = 0, + .key = @enumFromInt(0), .descriptor_size = 0, .descriptor_version = 0, }; @@ -541,14 +543,14 @@ pub const BootServices = extern struct { previous_size: usize, ) !?usize { var mmap_size: usize = previous_size; - var mmap_key: MemoryMap.Key = 0; + var mmap_key: MemoryMap.Key = @enumFromInt(0); var descriptor_size: usize = 0; var descriptor_version: u32 = 0; switch (self._getMemoryMap(&mmap_size, null, &mmap_key, &descriptor_size, &descriptor_version)) { .buffer_too_small => return mmap_size, .invalid_parameter => return null, - else => |s| return s.err(), + else => |s| { try s.err(); unreachable; }, } } @@ -802,18 +804,18 @@ pub const BootServices = extern struct { self: *const BootServices, /// The handle for the protocol interface that is being opened. handle: Handle, - /// The GUID of the protocol to open. - protocol_guid: *align(8) const Guid, + /// The protocol to open. + comptime Protocol: type, /// The handle of the agent that is opening the protocol interface specified by `protocol`. agent_handle: ?Handle, /// The handle of the controller that requires the protocol interface. controller_handle: ?Handle, /// Attributes to open the protocol with. attributes: OpenProtocolAttributes, - ) !?ProtocolInterface { + ) !?*const Protocol { var interface: ?ProtocolInterface = undefined; - try self._openProtocol(handle, protocol_guid, &interface, agent_handle, controller_handle, attributes).err(); - return interface; + try self._openProtocol(handle, &Protocol.guid, &interface, agent_handle, controller_handle, attributes).err(); + return @ptrCast(@alignCast(interface)); } /// Closes a protocol on a handle that was opened using `openProtocol()`. @@ -920,8 +922,8 @@ pub const BootServices = extern struct { switch (search_type) { .all => try self._locateHandleBuffer(search_type, null, null, &num_handles, &handle_buffer).err(), - .by_notify => |search_key| self._locateHandleBuffer(search_type, null, search_key, &num_handles, &handle_buffer), - .by_protocol => |protocol_guid| self._locateHandleBuffer(search_type, protocol_guid, null, &num_handles, &handle_buffer), + .by_notify => |search_key| try self._locateHandleBuffer(search_type, null, search_key, &num_handles, &handle_buffer).err(), + .by_protocol => |protocol_guid| try self._locateHandleBuffer(search_type, protocol_guid, null, &num_handles, &handle_buffer).err(), } return handle_buffer[0..num_handles]; @@ -930,14 +932,14 @@ pub const BootServices = extern struct { /// Returns the first protocol instance that matches the given protocol. pub fn locateProtocol( self: *const BootServices, - /// The GUID of the protocol. - protocol_guid: *align(8) const Guid, + /// The protocol to locate. + comptime Protocol: type, /// An optional registration key returned from `registerProtocolNotify()`. registration: ?*const anyopaque, - ) !?ProtocolInterface { + ) !?*const Protocol { var interface: ?ProtocolInterface = undefined; - switch (self._locateProtocol(protocol_guid, registration, &interface)) { - .success => return interface, + switch (self._locateProtocol(&Protocol.guid, registration, &interface)) { + .success => return @ptrCast(@alignCast(interface)), .not_found => return null, else => |status| return status.err(), } diff --git a/lib/std/os/uefi/table/header.zig b/lib/std/os/uefi/table/header.zig index 8a93933c6597..61f58ce7085c 100644 --- a/lib/std/os/uefi/table/header.zig +++ b/lib/std/os/uefi/table/header.zig @@ -24,7 +24,7 @@ pub const Header = extern struct { crc.update(byte_ptr[0..16]); crc.update(&.{ 0, 0, 0, 0 }); // crc32 field replaced with 0 crc.update(byte_ptr[20..self.header_size]); - return crc.finish() == self.crc32; + return crc.final() == self.crc32; } /// Checks if the table is at least the specified revision. diff --git a/lib/std/os/uefi/table/system.zig b/lib/std/os/uefi/table/system.zig index de044ff61f65..1de9371cb38e 100644 --- a/lib/std/os/uefi/table/system.zig +++ b/lib/std/os/uefi/table/system.zig @@ -59,7 +59,7 @@ pub const System = extern struct { number_of_table_entries: usize, /// A pointer to the system configuration tables. - configuration_table: [*]table.ConfigurationTable, + configuration_table: [*]table.Configuration, pub const signature: u64 = 0x5453595320494249; }; diff --git a/lib/std/os/uefi/ucontext.zig b/lib/std/os/uefi/ucontext.zig new file mode 100644 index 000000000000..3c7a73e1ef4f --- /dev/null +++ b/lib/std/os/uefi/ucontext.zig @@ -0,0 +1,110 @@ +const builtin = @import("builtin"); + +pub const REG = switch (builtin.cpu.arch) { + .x86_64 => struct { + pub const RAX = 0; + pub const RBX = 1; + pub const RCX = 2; + pub const RDX = 3; + pub const RSI = 4; + pub const RDI = 5; + pub const RBP = 6; + pub const RSP = 7; + pub const R8 = 8; + pub const R9 = 9; + pub const R10 = 10; + pub const R11 = 11; + pub const R12 = 12; + pub const R13 = 13; + pub const R14 = 14; + pub const R15 = 15; + pub const RIP = 16; + pub const EFL = 17; + }, + else => @compileError("arch not supported"), +}; + +pub const fpregset = struct { + xmm: [16]usize, +}; + +pub const mcontext_t = struct { + gregs: [23]usize, + fpregs: fpregset, +}; + +pub const ucontext_t = struct { + mcontext: mcontext_t, +}; + +fn gpRegisterOffset(comptime reg_index: comptime_int) usize { + return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; +} + +fn getContextInternal_x86_64() callconv(.Naked) void { + asm volatile ( + \\ movq %%r8, %[r8_offset:c](%%rdi) + \\ movq %%r9, %[r9_offset:c](%%rdi) + \\ movq %%r10, %[r10_offset:c](%%rdi) + \\ movq %%r11, %[r11_offset:c](%%rdi) + \\ movq %%r12, %[r12_offset:c](%%rdi) + \\ movq %%r13, %[r13_offset:c](%%rdi) + \\ movq %%r14, %[r14_offset:c](%%rdi) + \\ movq %%r15, %[r15_offset:c](%%rdi) + \\ movq %%rax, %[rax_offset:c](%%rdi) + \\ movq %%rbx, %[rbx_offset:c](%%rdi) + \\ movq %%rcx, %[rcx_offset:c](%%rdi) + \\ movq %%rdx, %[rdx_offset:c](%%rdi) + \\ movq %%rsi, %[rsi_offset:c](%%rdi) + \\ movq %%rdi, %[rdi_offset:c](%%rdi) + \\ movq %%rbp, %[rbp_offset:c](%%rdi) + \\ movq (%%rsp), %%rcx + \\ movq %%rcx, %[rip_offset:c](%%rdi) + \\ leaq 8(%%rsp), %%rcx + \\ movq %%rcx, %[efl_offset:c](%%rdi) + \\ pushfq + \\ popq %[efl_offset:c](%%rdi) + \\ retq + : + : [r8_offset] "i" (comptime gpRegisterOffset(REG.R8)), + [r9_offset] "i" (comptime gpRegisterOffset(REG.R9)), + [r10_offset] "i" (comptime gpRegisterOffset(REG.R10)), + [r11_offset] "i" (comptime gpRegisterOffset(REG.R11)), + [r12_offset] "i" (comptime gpRegisterOffset(REG.R12)), + [r13_offset] "i" (comptime gpRegisterOffset(REG.R13)), + [r14_offset] "i" (comptime gpRegisterOffset(REG.R14)), + [r15_offset] "i" (comptime gpRegisterOffset(REG.R15)), + [rax_offset] "i" (comptime gpRegisterOffset(REG.RAX)), + [rbx_offset] "i" (comptime gpRegisterOffset(REG.RBX)), + [rcx_offset] "i" (comptime gpRegisterOffset(REG.RCX)), + [rdx_offset] "i" (comptime gpRegisterOffset(REG.RDX)), + [rsi_offset] "i" (comptime gpRegisterOffset(REG.RSI)), + [rdi_offset] "i" (comptime gpRegisterOffset(REG.RDI)), + [rbp_offset] "i" (comptime gpRegisterOffset(REG.RBP)), + [rip_offset] "i" (comptime gpRegisterOffset(REG.RIP)), + [efl_offset] "i" (comptime gpRegisterOffset(REG.EFL)), + : "cc", "memory", "rcx" + ); +} + +inline fn getContext_x86_64(context: *ucontext_t) usize { + var clobber_rdi: usize = undefined; + asm volatile ( + \\ callq %[getContextInternal:P] + : [_] "={rdi}" (clobber_rdi), + : [_] "{rdi}" (context), + [getContextInternal] "X" (&getContextInternal_x86_64), + : "cc", "memory", "rcx", "rdx", "rsi", "r8", "r10", "r11" + ); + + return 0; +} + +pub fn getcontext(context: *ucontext_t) usize { + switch (builtin.cpu.arch) { + .x86_64 => return getContext_x86_64(context), + else => {}, + } + + return 1; +} diff --git a/lib/std/start.zig b/lib/std/start.zig index bcd39a27bf40..f55da5ce0a70 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -212,7 +212,7 @@ fn wasi_start() callconv(.C) void { } } -fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv(.C) usize { +fn EfiMain(handle: uefi.bits.Handle, system_table: *uefi.table.System) callconv(.C) usize { uefi.handle = handle; uefi.system_table = system_table; @@ -224,16 +224,39 @@ fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv root.main(); return 0; }, - usize => { + u8, usize => { return root.main(); }, uefi.Status => { return @intFromEnum(root.main()); }, - else => @compileError("expected return type of main to be 'void', 'noreturn', 'usize', or 'std.os.uefi.Status'"), + else => |T| if (@typeInfo(T) == .ErrorUnion) { + const result = root.main() catch |err| { + std.log.err("{s}", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + return @intFromEnum(uefi.Status.aborted); + }; + + switch (@TypeOf(result)) { + void => return 0, + u8, usize => { + return result; + }, + uefi.Status => { + return @intFromEnum(root.main()); + }, + else => @compileError(bad_efi_main_ret), + } + } else { + @compileError(bad_efi_main_ret); + }, } } +const bad_efi_main_ret = "expected return type of main to be 'void', '!void', 'noreturn', 'u8', '!u8', 'usize', '!usize', 'uefi.Status', or '!uefi.Status'"; + fn _start() callconv(.Naked) noreturn { // TODO set Top of Stack on non x86_64-plan9 if (native_os == .plan9 and native_arch == .x86_64) { From 2fa76b37339aacdfc929eff2cecfb5dca32b2a24 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 24 Jan 2024 17:04:14 -0600 Subject: [PATCH 09/23] std.debug: preliminary current stack trace for uefi * don't even attempt stack unwinding on UEFI, no unwinding information is available yet. std.pdb: include the address *after* the last instruction as part of the function. This is required when using `@returnAddress` on a tailcall function. This is possible with panics. --- lib/std/debug.zig | 16 ++++++++++++++++ lib/std/pdb.zig | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 1eb66ede659d..95a689512896 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -811,6 +811,22 @@ pub fn writeCurrentStackTrace( return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); } + if (native_os == .uefi) { + // problem: zig does not have a way to emit dwarf debug info for a COFF object + // result: we cannot use the DWARF unwinder on UEFI + // problem: UEFI uses the windows x64 frame layout, which means the frame pointer is basically useless + // result: we cannot use the frame pointer unwinder on UEFI + // problem: zig does not emit the .pdata section for UEFI binaries + // result: we cannot use the windows x64 unwinder (well, a port of one) on UEFI + // until one of these problems is solved, just don't even try to unwind the stack on UEFI, print the address of the first frame and give up + + if (start_addr) |address| { + try printSourceAtAddress(debug_info, out_stream, address, tty_config); + } + + return; + } + var it = (if (has_context) blk: { break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; } else null) orelse StackIterator.init(start_addr, null); diff --git a/lib/std/pdb.zig b/lib/std/pdb.zig index ece1cc63dc92..51a8830acd75 100644 --- a/lib/std/pdb.zig +++ b/lib/std/pdb.zig @@ -729,7 +729,7 @@ pub const Pdb = struct { const frag_vaddr_start = line_hdr.RelocOffset; const frag_vaddr_end = frag_vaddr_start + line_hdr.CodeSize; - if (address >= frag_vaddr_start and address < frag_vaddr_end) { + if (address >= frag_vaddr_start and address <= frag_vaddr_end) { // There is an unknown number of LineBlockFragmentHeaders (and their accompanying line and column records) // from now on. We will iterate through them, and eventually find a LineInfo that we're interested in, // breaking out to :subsections. If not, we will make sure to not read anything outside of this subsection. From e10ff35a847f72c2934ce4d0b5051b92d88c219a Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 24 Jan 2024 19:55:07 -0600 Subject: [PATCH 10/23] std.debug: transform uefi case into generic unwind failure --- lib/std/debug.zig | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 95a689512896..49b2348b199a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -811,28 +811,16 @@ pub fn writeCurrentStackTrace( return writeStackTraceWindows(out_stream, debug_info, tty_config, &context, start_addr); } - if (native_os == .uefi) { - // problem: zig does not have a way to emit dwarf debug info for a COFF object - // result: we cannot use the DWARF unwinder on UEFI - // problem: UEFI uses the windows x64 frame layout, which means the frame pointer is basically useless - // result: we cannot use the frame pointer unwinder on UEFI - // problem: zig does not emit the .pdata section for UEFI binaries - // result: we cannot use the windows x64 unwinder (well, a port of one) on UEFI - // until one of these problems is solved, just don't even try to unwind the stack on UEFI, print the address of the first frame and give up - - if (start_addr) |address| { - try printSourceAtAddress(debug_info, out_stream, address, tty_config); - } - - return; - } - var it = (if (has_context) blk: { break :blk StackIterator.initWithContext(start_addr, debug_info, &context) catch null; } else null) orelse StackIterator.init(start_addr, null); defer it.deinit(); + // When true, the stack unwinder failed completely to produce a single frame. + var stack_unwind_missing = true; while (it.next()) |return_address| { + stack_unwind_missing = false; + printLastUnwindError(&it, debug_info, out_stream, tty_config); // On arm64 macOS, the address of the last frame is 0x0 rather than 0x1 as on x86_64 macOS, @@ -843,6 +831,11 @@ pub fn writeCurrentStackTrace( const address = if (return_address == 0) return_address else return_address - 1; try printSourceAtAddress(debug_info, out_stream, address, tty_config); } else printLastUnwindError(&it, debug_info, out_stream, tty_config); + + // If the stack unwinder failed, print the top level frame via the given start_addr if available. This is not ideal, but it is better than nothing. + if (stack_unwind_missing and start_addr != null) { + try printSourceAtAddress(debug_info, out_stream, start_addr.?, tty_config); + } } pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const windows.CONTEXT) usize { From 2be2ec49a02ffa26f2377066ed7618a9173c6a3f Mon Sep 17 00:00:00 2001 From: Nameless Date: Tue, 6 Feb 2024 16:27:14 -0600 Subject: [PATCH 11/23] improve std bindings, add path_to_text --- lib/std/debug.zig | 13 +- lib/std/fs.zig | 27 +++- lib/std/os/uefi.zig | 3 + lib/std/os/uefi/device_path.zig | 66 ++++++--- lib/std/os/uefi/protocol.zig | 3 + lib/std/os/uefi/protocol/device_path.zig | 133 ++++++++++++++++-- .../protocol/device_path/path_to_text.zig | 51 +++++++ lib/std/os/uefi/protocol/loaded_image.zig | 2 +- lib/std/os/uefi/table/boot_services.zig | 94 +++++++++++-- lib/std/os/uefi/ucontext.zig | 125 ++++++++-------- 10 files changed, 388 insertions(+), 129 deletions(-) create mode 100644 lib/std/os/uefi/protocol/device_path/path_to_text.zig diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 49b2348b199a..aaa99f31776d 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -690,15 +690,8 @@ pub const StackIterator = struct { return true; } else if (native_os == .uefi) { if (uefi.system_table.boot_services) |boot_services| { - var buffer = uefi.global_page_allocator.allocator().alloc(u8, 4096) catch return true; - defer uefi.global_page_allocator.allocator().free(buffer); - - while (boot_services.getMemoryMapSize(buffer.len) catch return true) |new_len| { - buffer = uefi.global_page_allocator.allocator().realloc(buffer, new_len) catch return true; - } - - var map = uefi.table.BootServices.MemoryMap.initFromBuffer(buffer); - boot_services.getMemoryMap(&map) catch return true; + var map = boot_services.getMemoryMap(std.heap.page_allocator) catch return true; + defer map.deinit(std.heap.page_allocator); var it = map.iterator(); while (it.next()) |entry| { @@ -2027,7 +2020,7 @@ pub const DebugInfo = struct { defer boot_services.freePool(mem.sliceAsBytes(handles)); for (handles) |handle| { - const loaded_image: *const uefi.protocol.LoadedImage = try boot_services.openProtocol(handle, uefi.protocol.LoadedImage, uefi.handle, null, .{ .by_handle_protocol = true }) orelse continue; + const loaded_image: *const uefi.protocol.LoadedImage = try boot_services.openProtocol(handle, uefi.protocol.LoadedImage, .{}); if (address >= @intFromPtr(loaded_image.image_base) and address < @intFromPtr(loaded_image.image_base) + loaded_image.image_size) { if (self.address_map.get(@intFromPtr(loaded_image.image_base))) |obj_di| { diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 79c1dae9487b..27081f32f095 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -53,7 +53,7 @@ pub const MAX_PATH_BYTES = max_path_bytes; /// * On other platforms, `[]u8` file paths are opaque sequences of bytes with /// no particular encoding. pub const max_path_bytes = switch (native_os) { - .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten, .wasi => posix.PATH_MAX, + .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten, .wasi, .uefi => posix.PATH_MAX, // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes. // If it would require 4 WTF-8 bytes, then there would be a surrogate // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. @@ -274,11 +274,30 @@ pub fn defaultUefiCwd() Dir { const uefi = std.os.uefi; if (uefi.system_table.boot_services) |boot_services| blk: { - const loaded_image = boot_services.openProtocol(uefi.handle, uefi.protocol.LoadedImage, uefi.handle, null, .{ .by_handle_protocol = true }) catch break :blk orelse break :blk; + const loaded_image = boot_services.openProtocol(uefi.handle, uefi.protocol.LoadedImage, .{}) catch break :blk; - const simple_file_system = boot_services.openProtocol(loaded_image.device_handle.?, uefi.protocol.SimpleFileSystem, uefi.handle, null, .{ .by_handle_protocol = true }) catch break :blk orelse break :blk; + const file_path = if (loaded_image.file_path.node()) |node| file_path: { + if (node == .media and node.media == .file_path) + break :file_path node.media.file_path.path(); - return Dir{ .fd = .{ .file = simple_file_system.openVolume() catch break :blk } }; + break :blk; + } else break :blk; + + if (file_path.len + 4 > MAX_PATH_BYTES) break :blk; + + // required because device paths are not aligned + var path_buffer: [MAX_PATH_BYTES]u16 = undefined; + @memcpy(path_buffer[0..file_path.len], file_path); + path_buffer[file_path.len] = '\\'; + path_buffer[file_path.len + 1] = '.'; + path_buffer[file_path.len + 2] = '.'; + path_buffer[file_path.len + 3] = 0; + + const file_system = boot_services.openProtocol(loaded_image.device_handle.?, uefi.protocol.SimpleFileSystem, .{}) catch break :blk; + + const volume = file_system.openVolume() catch break :blk; + const fd = volume.open(path_buffer[0 .. file_path.len + 3 :0], .{}, .{}) catch break :blk; + return Dir{ .fd = .{ .file = fd } }; } return Dir{ .fd = .none }; diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 68685182ca55..dfeac1a0f3ea 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -23,6 +23,9 @@ pub var handle: bits.Handle = undefined; /// A pointer to the EFI System Table that is passed to the EFI image's entry point. pub var system_table: *table.System = undefined; +// A reasonable default value, UEFI does not specify an upper limit. +pub const PATH_MAX = 4096; + pub const ino_t = u64; pub const mode_t = u64; diff --git a/lib/std/os/uefi/device_path.zig b/lib/std/os/uefi/device_path.zig index 5c616b55d50a..2296b7a8d9be 100644 --- a/lib/std/os/uefi/device_path.zig +++ b/lib/std/os/uefi/device_path.zig @@ -25,8 +25,8 @@ pub const DevicePathNode = union(Type) { pub fn toGeneric(self: *const DevicePathNode) *const Generic { switch (self) { - inline else => |typ| switch (typ) { - inline else => |subtype| return @ptrCast(subtype), + else => |typ| switch (typ) { + else => |subtype| return @ptrCast(subtype), }, } } @@ -351,7 +351,17 @@ pub const DevicePathNode = union(Type) { length: u16 align(1) = 8, /// NFIT device handle - handle: u32, + handle: u32 align(1), + + comptime { + assert(8 == @sizeOf(Nvdimm)); + assert(1 == @alignOf(Nvdimm)); + + assert(0 == @offsetOf(Nvdimm, "type")); + assert(1 == @offsetOf(Nvdimm, "subtype")); + assert(2 == @offsetOf(Nvdimm, "length")); + assert(4 == @offsetOf(Nvdimm, "handle")); + } }; }; @@ -359,23 +369,23 @@ pub const DevicePathNode = union(Type) { atapi: *const Atapi, scsi: *const Scsi, fibre_channel: *const FibreChannel, - fibre_channel_ex: *const FibreChannelEx, @"1394": *const F1394, usb: *const Usb, - sata: *const Sata, - usb_wwid: *const UsbWwid, - lun: *const DeviceLogicalUnit, - usb_class: *const UsbClass, i2o: *const I2o, + infiniband: *const InfiniBand, + vendor: *const Vendor, mac_address: *const MacAddress, ipv4: *const Ipv4, ipv6: *const Ipv6, - vlan: *const Vlan, - infiniband: *const InfiniBand, uart: *const Uart, - vendor: *const Vendor, - scsi_extended: *const ScsiExtended, + usb_class: *const UsbClass, + usb_wwid: *const UsbWwid, + lun: *const DeviceLogicalUnit, + sata: *const Sata, iscsi: *const Iscsi, + vlan: *const Vlan, + fibre_channel_ex: *const FibreChannelEx, + scsi_extended: *const ScsiExtended, nvme_namespace: *const NvmeNamespace, uri: *const Uri, ufs: *const Ufs, @@ -410,7 +420,7 @@ pub const DevicePathNode = union(Type) { vlan = 20, fibre_channel_ex = 21, scsi_extended = 22, - nvme = 23, + nvme_namespace = 23, uri = 24, ufs = 25, sd = 26, @@ -981,6 +991,8 @@ pub const DevicePathNode = union(Type) { /// SAS Address for the SCSI Target port address: [8]u8, + _pad: [8]u8, + /// SAS Logical unit number logical_unit_number: [8]u8, @@ -1044,7 +1056,7 @@ pub const DevicePathNode = union(Type) { pub const NvmeNamespace = extern struct { type: Type = .messaging, - subtype: Subtype = .nvme, + subtype: Subtype = .nvme_namespace, length: u16 align(1) = 16, /// Namespace ID, 0 and 0xFFFFFFFF are invalid. @@ -1119,7 +1131,7 @@ pub const DevicePathNode = union(Type) { slot_number: u8, comptime { - assert(6 == @sizeOf(Sd)); + assert(5 == @sizeOf(Sd)); assert(1 == @alignOf(Sd)); assert(0 == @offsetOf(Sd, "type")); @@ -1297,7 +1309,7 @@ pub const DevicePathNode = union(Type) { pub const NvmeOverFabric = extern struct { type: Type = .messaging, subtype: Subtype = .nvme_over_fabric, - length: u16 align(1), // 20 + x + length: u16 align(1), // 21 + x /// Namespace identifier type nidt: u8, @@ -1313,13 +1325,14 @@ pub const DevicePathNode = union(Type) { } comptime { - assert(16 == @sizeOf(NvmeOverFabric)); + assert(21 == @sizeOf(NvmeOverFabric)); assert(1 == @alignOf(NvmeOverFabric)); assert(0 == @offsetOf(NvmeOverFabric, "type")); assert(1 == @offsetOf(NvmeOverFabric, "subtype")); assert(2 == @offsetOf(NvmeOverFabric, "length")); - assert(4 == @offsetOf(NvmeOverFabric, "namespace_uuid")); + assert(4 == @offsetOf(NvmeOverFabric, "nidt")); + assert(5 == @offsetOf(NvmeOverFabric, "nid")); } }; }; @@ -1472,7 +1485,7 @@ pub const DevicePathNode = union(Type) { subtype: Subtype = .file_path, length: u16 align(1), // 4 + x - ///A NULL-terminated Path string including directory and file names. The length of this string n can be + /// A NULL-terminated Path string including directory and file names. The length of this string n can be /// determined by subtracting 4 from the Length entry. A device path may contain one or more of these /// nodes. Each node can optionally add a “" separator to the beginning and/or the end of the Path Name /// string. The complete path to a file can be found by logically concatenating all the Path Name @@ -1481,7 +1494,20 @@ pub const DevicePathNode = union(Type) { const ptr: [*:0]align(1) const u16 = @ptrCast(self); const entries = @divExact(self.length, 2); - return ptr[2..entries]; + return ptr[2 .. entries - 1 :0]; + } + + /// Creates a new file path device path node. + pub fn create(allocator: std.mem.Allocator, filepath: []const u16) !DevicePathNode { + const buffer = try allocator.alloc(u8, 4 + filepath.len + 1); + const ptr: *FilePath = @ptrCast(buffer); + + ptr = .{ .length = 4 + filepath.len + 1 }; + + @memcpy(buffer[4 .. buffer.len - 1], filepath); + buffer[buffer.len - 1] = 0; + + return DevicePathNode{ .media = .{ .file_path = ptr } }; } comptime { diff --git a/lib/std/os/uefi/protocol.zig b/lib/std/os/uefi/protocol.zig index 5e2c43de9092..70578ec7477d 100644 --- a/lib/std/os/uefi/protocol.zig +++ b/lib/std/os/uefi/protocol.zig @@ -8,6 +8,9 @@ pub const LoadedImage = @import("protocol/loaded_image.zig").LoadedImage; // 10. Device Path pub const DevicePath = @import("protocol/device_path.zig").DevicePath; +// DevicePathUtilities +pub const DevicePathToText = @import("protocol/device_path/path_to_text.zig").PathToText; +// DevicePathFromText // 12. Console Support pub const SimpleTextInputEx = @import("protocol/console/simple_text_input_ex.zig").SimpleTextInputEx; diff --git a/lib/std/os/uefi/protocol/device_path.zig b/lib/std/os/uefi/protocol/device_path.zig index 50e7ec1b9690..8a2ab6bd2e6c 100644 --- a/lib/std/os/uefi/protocol/device_path.zig +++ b/lib/std/os/uefi/protocol/device_path.zig @@ -1,6 +1,7 @@ const std = @import("../../../std.zig"); const bits = @import("../bits.zig"); +const uefi = std.os.uefi; const mem = std.mem; const cc = bits.cc; const DevicePathNode = @import("../device_path.zig").DevicePathNode; @@ -37,25 +38,71 @@ pub const DevicePath = extern struct { .node = [_]u8{ 0x2d, 0x3b, 0x36, 0xd7, 0x50, 0xdf }, }; - /// Returns the next DevicePath node in the sequence, if any. - fn next(self: *const DevicePath) ?*const DevicePath { - const subtype: DevicePathNode.End.Subtype = @enumFromInt(self.subtype); - if (self.type != .end or subtype != .entire) - return null; - + /// Return the next node in this device path instance, if any. + pub fn next(self: *const DevicePath) ?*const DevicePath { const next_addr = @intFromPtr(self) + self.length; - return @ptrFromInt(next_addr); + const next_node: *const DevicePath = @ptrFromInt(next_addr); + + if (next_node.type == .end) { + const subtype: DevicePathNode.End.Subtype = @enumFromInt(next_node.subtype); + if (subtype == .entire or subtype == .this_instance) + return null; + } + + return next_node; } - /// Calculates the total length of the device path structure in bytes, including the end of device path node. + /// Returns the next instance in this device path, if any, skipping over any remaining nodes if present. + pub fn nextInstance(self: *const DevicePath) ?*const DevicePath { + var this_node: *const DevicePath = self; + while (true) { + const next_addr = @intFromPtr(this_node) + this_node.length; + const next_node: *const DevicePath = @ptrFromInt(next_addr); + + if (this_node.type == .end) { + const subtype: DevicePathNode.End.Subtype = @enumFromInt(this_node.subtype); + if (subtype == .entire) { + return null; + } else if (subtype == .this_instance) { + return next_node; + } + } + + this_node = next_node; + } + } + + /// Calculates the length of this device path instance in bytes, including the end of device path node. pub fn size(self: *const DevicePath) usize { - var cur_node = self; + var this_node: *const DevicePath = self; + while (true) { + const next_addr = @intFromPtr(this_node) + this_node.length; + const next_node: *const DevicePath = @ptrFromInt(next_addr); - while (cur_node.next()) |next_node| { - cur_node = next_node; + if (this_node.type == .end) { + return next_addr - @intFromPtr(self); + } + + this_node = next_node; } + } + + /// Calculates the total length of the device path structure in bytes, including the end of device path node. + pub fn sizeEntire(self: *const DevicePath) usize { + var this_node: *const DevicePath = self; + while (true) { + const next_addr = @intFromPtr(this_node) + this_node.length; + const next_node: *const DevicePath = @ptrFromInt(next_addr); + + if (this_node.type == .end) { + const subtype: DevicePathNode.End.Subtype = @enumFromInt(this_node.subtype); + if (subtype == .entire) { + return next_addr - @intFromPtr(self); + } + } - return (@intFromPtr(cur_node) + cur_node.length) - @intFromPtr(self); + this_node = next_node; + } } /// Creates a new device path with only the end entire node. The device path will be owned by the caller. @@ -72,11 +119,11 @@ pub const DevicePath = extern struct { /// Appends a device path node to the end of an existing device path. `allocator` must own the memory of the /// existing device path. The existing device path must be the start of the entire device path chain. - /// + /// /// This will reallocate the existing device path. The pointer returned here must be used instead of any dangling /// references to the previous device path. - pub fn append(self: *DevicePath, allocator: Allocator, node_to_append: DevicePathNode) !*DevicePath { - const original_size = self.size(); + pub fn appendNode(self: *DevicePath, allocator: Allocator, node_to_append: *const DevicePathNode) !*DevicePath { + const original_size = self.sizeEntire(); const new_size = original_size + node_to_append.toGeneric().length; const original_bytes: [*]u8 = @ptrCast(self); @@ -93,6 +140,62 @@ pub const DevicePath = extern struct { return @ptrCast(new_bytes); } + /// Appends a device path to the end of an existing device path. `allocator` must own the memory of the existing + /// device path. The existing device path must be the start of the entire device path chain. + /// + /// This will reallocate the existing device path. The pointer returned here must be used instead of any dangling + /// references to the previous device path. + pub fn appendPath(self: *DevicePath, allocator: Allocator, path: *DevicePath) !*DevicePath { + const original_size = self.sizeEntire(); + const other_size = path.sizeEntire(); + + const new_size = original_size + other_size - 4; + + const original_bytes: [*]u8 = @ptrCast(self); + const new_bytes = try allocator.realloc(original_bytes[0..original_size], new_size); + + const path_bytes: [*]const u8 = @ptrCast(path); + + // Copy path on top of the previous end entire node. + @memcpy(new_bytes[original_size - 4 ..], path_bytes[0..other_size]); + + return @ptrCast(new_bytes); + } + + /// Appends a device path instance to the end of an existing device path. `allocator` must own the memory of the existing + /// device path. The existing device path must be the start of the entire device path chain. + /// + /// The end of entire device path node will be replaced with the end of this instance node. + /// The end of entire device path node will be copied from the appended device path instance. + /// + /// This will reallocate the existing device path. The pointer returned here must be used instead of any dangling + /// references to the previous device path. + pub fn appendPathInstance(self: *DevicePath, allocator: Allocator, path: *DevicePath) !*DevicePath { + const original_size = self.sizeEntire(); + const other_size = path.sizeEntire(); + + const new_size = original_size + other_size; + + const original_bytes: [*]u8 = @ptrCast(self); + const new_bytes = try allocator.realloc(original_bytes[0..original_size], new_size); + + const path_bytes: [*]const u8 = @ptrCast(path); + + // Copy path after of the previous end entire node. + @memcpy(new_bytes[original_size..], path_bytes[0..other_size]); + + // change end entire node to end this instance node + const end_of_existing: *DevicePath = @ptrCast(new_bytes + original_size - 4); + end_of_existing.subtype = @intFromEnum(DevicePathNode.End.Subtype.this_instance); + + return @ptrCast(new_bytes); + } + + /// Returns true if this device path is a multi-instance device path. + pub fn isMultiInstance(self: *const DevicePath) bool { + return self.nextInstance() != null; + } + /// Returns the DevicePathNode union for this device path protocol pub fn node(self: *const DevicePath) ?DevicePathNode { inline for (@typeInfo(DevicePathNode).Union.fields) |type_field| { diff --git a/lib/std/os/uefi/protocol/device_path/path_to_text.zig b/lib/std/os/uefi/protocol/device_path/path_to_text.zig new file mode 100644 index 000000000000..971606f1404c --- /dev/null +++ b/lib/std/os/uefi/protocol/device_path/path_to_text.zig @@ -0,0 +1,51 @@ +const std = @import("../../../../std.zig"); +const bits = @import("../../bits.zig"); +const protocol = @import("../../protocol.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; + +const Guid = bits.Guid; +const DevicePath = protocol.DevicePath; + +pub const PathToText = extern struct { + _convert_device_node_to_text: *const fn (node: *const DevicePath, display_only: bool, allow_shortcuts: bool) callconv(cc) ?[*:0]const u16, + _convert_device_path_to_text: *const fn (path: *const DevicePath, display_only: bool, allow_shortcuts: bool) callconv(cc) ?[*:0]const u16, + + /// Convert a device node to its text representation. + pub fn convertNodeToText( + self: *const PathToText, + /// Points to the device path to be converted. + node: *const DevicePath, + /// If true, the shorter non-parseable text representation is used. + display_only: bool, + /// If true, the shortcut forms of text representation are used. + allow_shortcuts: bool, + ) ?[:0]const u16 { + const ptr = self._convert_device_node_to_text(node, display_only, allow_shortcuts) orelse return null; + return std.mem.span(ptr); + } + + /// Convert a device path to its text representation. + pub fn convertPathToText( + self: *const PathToText, + /// Points to the device path to be converted. + path: *const DevicePath, + /// If true, the shorter non-parseable text representation is used. + display_only: bool, + /// If true, the shortcut forms of text representation are used. + allow_shortcuts: bool, + ) ?[:0]const u16 { + const ptr = self._convert_device_path_to_text(path, display_only, allow_shortcuts) orelse return null; + return std.mem.span(ptr); + } + + pub const guid align(8) = Guid{ + .time_low = 0x8b843e20, + .time_mid = 0x8132, + .time_high_and_version = 0x4852, + .clock_seq_high_and_reserved = 0x90, + .clock_seq_low = 0xcc, + .node = [_]u8{ 0x55, 0x1a, 0x4e, 0x4a, 0x7f, 0x1c }, + }; +}; diff --git a/lib/std/os/uefi/protocol/loaded_image.zig b/lib/std/os/uefi/protocol/loaded_image.zig index f525110c5313..56a30d144746 100644 --- a/lib/std/os/uefi/protocol/loaded_image.zig +++ b/lib/std/os/uefi/protocol/loaded_image.zig @@ -13,7 +13,7 @@ pub const LoadedImage = extern struct { parent_handle: Handle, system_table: *table.System, device_handle: ?Handle, - file_path: *protocol.DevicePath, + file_path: *const protocol.DevicePath, reserved: *anyopaque, load_options_size: u32, load_options: ?*anyopaque, diff --git a/lib/std/os/uefi/table/boot_services.zig b/lib/std/os/uefi/table/boot_services.zig index 9c22291f11db..1a9447faaa9a 100644 --- a/lib/std/os/uefi/table/boot_services.zig +++ b/lib/std/os/uefi/table/boot_services.zig @@ -458,6 +458,9 @@ pub const BootServices = extern struct { /// The length of the memory map in bytes. size: usize, + /// Allocated size + allocated_size: usize, + /// The key for the current memory map. key: Key, @@ -494,6 +497,7 @@ pub const BootServices = extern struct { return .{ .map = @ptrCast(buffer.ptr), .size = buffer.len, + .allocated_size = buffer.len, .key = @enumFromInt(0), .descriptor_size = 0, .descriptor_version = 0, @@ -505,12 +509,19 @@ pub const BootServices = extern struct { return .{ .map = @ptrCast(list.ptr), .size = list.len * @sizeOf(bits.MemoryDescriptor), + .allocated_size = list.len * @sizeOf(bits.MemoryDescriptor), .key = 0, .descriptor_size = @sizeOf(bits.MemoryDescriptor), .descriptor_version = bits.MemoryDescriptor.revision, }; } + /// Uses the provided allocator to free the memory map. + pub fn deinit(self: *MemoryMap, allocator: std.mem.Allocator) void { + const bytes: [*]const u8 = @ptrCast(self.map); + allocator.free(bytes[0..self.allocated_size]); + } + /// Returns an iterator over the memory map. pub fn iterator(self: *const MemoryMap) Iterator { return Iterator{ .map = self }; @@ -550,14 +561,17 @@ pub const BootServices = extern struct { switch (self._getMemoryMap(&mmap_size, null, &mmap_key, &descriptor_size, &descriptor_version)) { .buffer_too_small => return mmap_size, .invalid_parameter => return null, - else => |s| { try s.err(); unreachable; }, + else => |s| { + try s.err(); + unreachable; + }, } } /// Fetches the current memory map. /// /// Use `getMemoryMapSize()` to determine the size of the buffer to allocate. - pub fn getMemoryMap( + pub fn fillMemoryMap( self: *const BootServices, /// The memory map to fill. map: *MemoryMap, @@ -575,6 +589,25 @@ pub const BootServices = extern struct { map.descriptor_version = descriptor_version; } + /// Fetches the current memory map using the provided allocator to allocate the buffer. + pub fn getMemoryMap( + self: *const BootServices, + /// The allocator to use to allocate the buffer. + allocator: std.mem.Allocator, + ) !MemoryMap { + var buf = try allocator.alloc(u8, 1024); + errdefer allocator.free(buf); + + while (try self.getMemoryMapSize(buf.len)) |new_len| { + buf = try allocator.realloc(buf, new_len); + } + + var map = MemoryMap.initFromBuffer(buf); + try self.fillMemoryMap(&map); + + return map; + } + /// Allocates pool memory. /// /// All allocations are 8-byte aligned. @@ -798,6 +831,20 @@ pub const BootServices = extern struct { reserved: u26 = 0, }; + pub const OpenProtocolOptions = struct { + /// The GUID of the protocol to open, will use the GUID of the associated Protocol type if omitted. + protocol_guid: ?*align(8) const Guid = null, + + /// The handle of the agent that is opening the protocol interface specified by `protocol`. + agent_handle: ?Handle = null, + + /// The handle of the controller that requires the protocol interface. + controller_handle: ?Handle = null, + + /// Attributes to open the protocol with. Defaults to behaving like handleProtocol() would. + attributes: OpenProtocolAttributes = .{ .by_handle_protocol = true }, + }; + /// Queries a handle to determine if it supports a specified protocol. If the protocol is supported by the handle, /// it opens the protocol on behalf of the calling agent. pub fn openProtocol( @@ -806,16 +853,22 @@ pub const BootServices = extern struct { handle: Handle, /// The protocol to open. comptime Protocol: type, - /// The handle of the agent that is opening the protocol interface specified by `protocol`. - agent_handle: ?Handle, - /// The handle of the controller that requires the protocol interface. - controller_handle: ?Handle, - /// Attributes to open the protocol with. - attributes: OpenProtocolAttributes, - ) !?*const Protocol { + /// The options to open the protocol with. + options: OpenProtocolOptions, + ) !*const Protocol { var interface: ?ProtocolInterface = undefined; - try self._openProtocol(handle, &Protocol.guid, &interface, agent_handle, controller_handle, attributes).err(); - return @ptrCast(@alignCast(interface)); + try self._openProtocol( + handle, + options.protocol_guid orelse &Protocol.guid, + &interface, + options.agent_handle, + options.controller_handle, + options.attributes, + ).err(); + if (interface == null) + return error.MissingProtocol; + + return @ptrCast(@alignCast(interface.?)); } /// Closes a protocol on a handle that was opened using `openProtocol()`. @@ -929,19 +982,30 @@ pub const BootServices = extern struct { return handle_buffer[0..num_handles]; } + pub const LocateProtocolOptions = struct { + /// The GUID of the protocol to locate. Will use the GUID of the associated Protocol type if omitted. + protocol_guid: ?*align(8) const Guid = null, + + /// An optional registration key returned from `registerProtocolNotify()`. + registration: ?*const anyopaque = null, + }; + /// Returns the first protocol instance that matches the given protocol. pub fn locateProtocol( self: *const BootServices, /// The protocol to locate. comptime Protocol: type, - /// An optional registration key returned from `registerProtocolNotify()`. - registration: ?*const anyopaque, + /// The options to locate the protocol with. + options: LocateProtocolOptions, ) !?*const Protocol { var interface: ?ProtocolInterface = undefined; - switch (self._locateProtocol(&Protocol.guid, registration, &interface)) { + switch (self._locateProtocol(options.protocol_guid orelse &Protocol.guid, options.registration, &interface)) { .success => return @ptrCast(@alignCast(interface)), .not_found => return null, - else => |status| return status.err(), + else => |status| { + try status.err(); + unreachable; + }, } } diff --git a/lib/std/os/uefi/ucontext.zig b/lib/std/os/uefi/ucontext.zig index 3c7a73e1ef4f..a17c188a9b35 100644 --- a/lib/std/os/uefi/ucontext.zig +++ b/lib/std/os/uefi/ucontext.zig @@ -1,4 +1,5 @@ const builtin = @import("builtin"); +const std = @import("../../std.zig"); pub const REG = switch (builtin.cpu.arch) { .x86_64 => struct { @@ -25,11 +26,22 @@ pub const REG = switch (builtin.cpu.arch) { }; pub const fpregset = struct { - xmm: [16]usize, + fcw: u16, + fsw: u16, + ftw: u8, + reserved1: u8, + fop: u16, + fip: u64, + fdp: u64, + mxcsr: u32, + mxcsr_mask: u32, + st: [8]u128, + xmm: [16]u128, + reserved2: [96]u8, }; pub const mcontext_t = struct { - gregs: [23]usize, + gregs: [18]usize, fpregs: fpregset, }; @@ -41,70 +53,55 @@ fn gpRegisterOffset(comptime reg_index: comptime_int) usize { return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; } -fn getContextInternal_x86_64() callconv(.Naked) void { - asm volatile ( - \\ movq %%r8, %[r8_offset:c](%%rdi) - \\ movq %%r9, %[r9_offset:c](%%rdi) - \\ movq %%r10, %[r10_offset:c](%%rdi) - \\ movq %%r11, %[r11_offset:c](%%rdi) - \\ movq %%r12, %[r12_offset:c](%%rdi) - \\ movq %%r13, %[r13_offset:c](%%rdi) - \\ movq %%r14, %[r14_offset:c](%%rdi) - \\ movq %%r15, %[r15_offset:c](%%rdi) - \\ movq %%rax, %[rax_offset:c](%%rdi) - \\ movq %%rbx, %[rbx_offset:c](%%rdi) - \\ movq %%rcx, %[rcx_offset:c](%%rdi) - \\ movq %%rdx, %[rdx_offset:c](%%rdi) - \\ movq %%rsi, %[rsi_offset:c](%%rdi) - \\ movq %%rdi, %[rdi_offset:c](%%rdi) - \\ movq %%rbp, %[rbp_offset:c](%%rdi) - \\ movq (%%rsp), %%rcx - \\ movq %%rcx, %[rip_offset:c](%%rdi) - \\ leaq 8(%%rsp), %%rcx - \\ movq %%rcx, %[efl_offset:c](%%rdi) - \\ pushfq - \\ popq %[efl_offset:c](%%rdi) - \\ retq - : - : [r8_offset] "i" (comptime gpRegisterOffset(REG.R8)), - [r9_offset] "i" (comptime gpRegisterOffset(REG.R9)), - [r10_offset] "i" (comptime gpRegisterOffset(REG.R10)), - [r11_offset] "i" (comptime gpRegisterOffset(REG.R11)), - [r12_offset] "i" (comptime gpRegisterOffset(REG.R12)), - [r13_offset] "i" (comptime gpRegisterOffset(REG.R13)), - [r14_offset] "i" (comptime gpRegisterOffset(REG.R14)), - [r15_offset] "i" (comptime gpRegisterOffset(REG.R15)), - [rax_offset] "i" (comptime gpRegisterOffset(REG.RAX)), - [rbx_offset] "i" (comptime gpRegisterOffset(REG.RBX)), - [rcx_offset] "i" (comptime gpRegisterOffset(REG.RCX)), - [rdx_offset] "i" (comptime gpRegisterOffset(REG.RDX)), - [rsi_offset] "i" (comptime gpRegisterOffset(REG.RSI)), - [rdi_offset] "i" (comptime gpRegisterOffset(REG.RDI)), - [rbp_offset] "i" (comptime gpRegisterOffset(REG.RBP)), - [rip_offset] "i" (comptime gpRegisterOffset(REG.RIP)), - [efl_offset] "i" (comptime gpRegisterOffset(REG.EFL)), - : "cc", "memory", "rcx" - ); -} - -inline fn getContext_x86_64(context: *ucontext_t) usize { - var clobber_rdi: usize = undefined; - asm volatile ( - \\ callq %[getContextInternal:P] - : [_] "={rdi}" (clobber_rdi), - : [_] "{rdi}" (context), - [getContextInternal] "X" (&getContextInternal_x86_64), - : "cc", "memory", "rcx", "rdx", "rsi", "r8", "r10", "r11" - ); - - return 0; -} - -pub fn getcontext(context: *ucontext_t) usize { +pub inline fn getcontext(context: *ucontext_t) usize { switch (builtin.cpu.arch) { - .x86_64 => return getContext_x86_64(context), + .x86_64 => { + asm volatile ( + \\ movq %%r8, %[r8_offset:c](%[context]) + \\ movq %%r9, %[r9_offset:c](%[context]) + \\ movq %%r10, %[r10_offset:c](%[context]) + \\ movq %%r11, %[r11_offset:c](%[context]) + \\ movq %%r12, %[r12_offset:c](%[context]) + \\ movq %%r13, %[r13_offset:c](%[context]) + \\ movq %%r14, %[r14_offset:c](%[context]) + \\ movq %%r15, %[r15_offset:c](%[context]) + \\ movq %%rdi, %[rdi_offset:c](%[context]) + \\ movq %%rsi, %[rsi_offset:c](%[context]) + \\ movq %%rbx, %[rbx_offset:c](%[context]) + \\ movq %%rdx, %[rdx_offset:c](%[context]) + \\ movq %%rax, %[rax_offset:c](%[context]) + \\ movq %%rcx, %[rcx_offset:c](%[context]) + \\ movq %%rbp, %[rbp_offset:c](%[context]) + \\ movq %%rsp, %[rsp_offset:c](%[context]) + \\ leaq (%%rip), %%rcx + \\ movq %%rcx, %[rip_offset:c](%[context]) + \\ pushfq + \\ popq %[efl_offset:c](%[context]) + : + : [context] "{rdi}" (context), + [r8_offset] "i" (comptime gpRegisterOffset(REG.R8)), + [r9_offset] "i" (comptime gpRegisterOffset(REG.R9)), + [r10_offset] "i" (comptime gpRegisterOffset(REG.R10)), + [r11_offset] "i" (comptime gpRegisterOffset(REG.R11)), + [r12_offset] "i" (comptime gpRegisterOffset(REG.R12)), + [r13_offset] "i" (comptime gpRegisterOffset(REG.R13)), + [r14_offset] "i" (comptime gpRegisterOffset(REG.R14)), + [r15_offset] "i" (comptime gpRegisterOffset(REG.R15)), + [rax_offset] "i" (comptime gpRegisterOffset(REG.RAX)), + [rbx_offset] "i" (comptime gpRegisterOffset(REG.RBX)), + [rcx_offset] "i" (comptime gpRegisterOffset(REG.RCX)), + [rdx_offset] "i" (comptime gpRegisterOffset(REG.RDX)), + [rsi_offset] "i" (comptime gpRegisterOffset(REG.RSI)), + [rdi_offset] "i" (comptime gpRegisterOffset(REG.RDI)), + [rsp_offset] "i" (comptime gpRegisterOffset(REG.RSP)), + [rbp_offset] "i" (comptime gpRegisterOffset(REG.RBP)), + [rip_offset] "i" (comptime gpRegisterOffset(REG.RIP)), + [efl_offset] "i" (comptime gpRegisterOffset(REG.EFL)), + : "cc", "memory", "rcx" + ); + }, else => {}, } - return 1; + return 0; } From 9fece05c1f1987e8e903a0a467494b3f88fc1df5 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 7 Feb 2024 17:04:43 -0600 Subject: [PATCH 12/23] std.os.uefi: add DiskIo, prepare for service bindings --- lib/std/os/uefi/protocol.zig | 2 +- lib/std/os/uefi/protocol/media/block_io.zig | 12 ++--- lib/std/os/uefi/protocol/media/disk_io.zig | 49 ++++++++++++++++++++ lib/std/os/uefi/protocol/service_binding.zig | 39 ++++++++++++++++ 4 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 lib/std/os/uefi/protocol/media/disk_io.zig create mode 100644 lib/std/os/uefi/protocol/service_binding.zig diff --git a/lib/std/os/uefi/protocol.zig b/lib/std/os/uefi/protocol.zig index 70578ec7477d..8f12b546d921 100644 --- a/lib/std/os/uefi/protocol.zig +++ b/lib/std/os/uefi/protocol.zig @@ -27,7 +27,7 @@ pub const LoadFile = @import("protocol/media/load_file.zig").LoadFile; pub const SimpleFileSystem = @import("protocol/media/simple_file_system.zig").SimpleFileSystem; pub const File = @import("protocol/media/file.zig").File; // TapeIo -// DiskIo +pub const DiskIo = @import("protocol/media/disk_io.zig").DiskIo; // DiskIo2 pub const BlockIo = @import("protocol/media/block_io.zig").BlockIo; // BlockIo2 diff --git a/lib/std/os/uefi/protocol/media/block_io.zig b/lib/std/os/uefi/protocol/media/block_io.zig index 747d3304855f..48b87b4b2843 100644 --- a/lib/std/os/uefi/protocol/media/block_io.zig +++ b/lib/std/os/uefi/protocol/media/block_io.zig @@ -6,11 +6,7 @@ const Status = @import("../../status.zig").Status; const Guid = bits.Guid; -const assert = std.debug.assert; - pub const BlockIo = extern struct { - const Self = @This(); - revision: u64, media: *const Media, @@ -21,7 +17,7 @@ pub const BlockIo = extern struct { /// Resets the block device hardware. pub fn reset( - self: *Self, + self: *BlockIo, /// Indicates that the driver may perform a more exhaustive verification operation of the device during reset. verify: bool, ) !void { @@ -30,7 +26,7 @@ pub const BlockIo = extern struct { /// Reads the number of requested blocks from the device. pub fn readBlocks( - self: *Self, + self: *BlockIo, /// The media ID that the read request is for. media_id: u32, /// The starting logical block address to read from on the device. @@ -43,7 +39,7 @@ pub const BlockIo = extern struct { /// Writes a specified number of blocks to the device. pub fn writeBlocks( - self: *Self, + self: *BlockIo, /// The media ID that the write request is for. media_id: u32, /// The starting logical block address to write from on the device. @@ -55,7 +51,7 @@ pub const BlockIo = extern struct { } /// Flushes all modified data to a physical block device. - pub fn flushBlocks(self: *Self) !void { + pub fn flushBlocks(self: *BlockIo) !void { try self._flush_blocks(self).err(); } diff --git a/lib/std/os/uefi/protocol/media/disk_io.zig b/lib/std/os/uefi/protocol/media/disk_io.zig new file mode 100644 index 000000000000..15441c0dfc5d --- /dev/null +++ b/lib/std/os/uefi/protocol/media/disk_io.zig @@ -0,0 +1,49 @@ +const std = @import("../../../../std.zig"); +const bits = @import("../../bits.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; + +const Guid = bits.Guid; + +pub const DiskIo = extern struct { + revision: u64, + + _read: *const fn (*const DiskIo, media_id: u32, offset: u64, buffer_size: usize, buf: [*]u8) callconv(cc) Status, + _write: *const fn (*const DiskIo, media_id: u32, offset: u64, buffer_size: usize, buf: [*]const u8) callconv(cc) Status, + + /// Reads a specified number of bytes from a device. + pub fn read( + self: *DiskIo, + /// The media ID that the read request is for. + media_id: u32, + /// The starting byte offset on the logical block I/O device to read from. + offset: u64, + /// The buffer into which the data is read. + buffer: []u8, + ) !void { + try self._read(self, media_id, offset, buffer.len, buffer.ptr).err(); + } + + /// Writes a specified number of bytes to the device. + pub fn write( + self: *DiskIo, + /// The media ID that the write request is for. + media_id: u32, + /// The starting byte offset on the logical block I/O device to write to. + offset: u64, + /// The buffer from which the data is written. + buffer: []const u8, + ) !void { + try self._write(self, media_id, offset, buffer.len, buffer.ptr).err(); + } + + pub const guid align(8) = Guid{ + .time_low = 0xce345171, + .time_mid = 0xba0b, + .time_high_and_version = 0x11d2, + .clock_seq_high_and_reserved = 0x8e, + .clock_seq_low = 0x4f, + .node = [_]u8{ 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, + }; +}; diff --git a/lib/std/os/uefi/protocol/service_binding.zig b/lib/std/os/uefi/protocol/service_binding.zig new file mode 100644 index 000000000000..3a9840744377 --- /dev/null +++ b/lib/std/os/uefi/protocol/service_binding.zig @@ -0,0 +1,39 @@ +const std = @import("../../../std.zig"); +const bits = @import("../bits.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; +const Handle = bits.Handle; + +pub fn ServiceBinding(comptime Protocol: type) type { + return extern struct { + const Binding = @This(); + + _create_child: *const fn (*const Binding, *Handle) callconv(cc) Status, + _destroy_child: *const fn (*const Binding, Handle) callconv(cc) Status, + + /// Creates a child handle and installs a protocol. The returned handle is guaranteed to have the protocol installed. + pub fn create( + this: *const Binding, + /// The child handle to install the protocol on. If null, a new handle will be created. + child: ?Handle, + ) !Handle { + var handle = child; + try this._create_child(this, &handle).err(); + return handle orelse unreachable; + } + + /// Destroys a child handle with a protocol installed on it + pub fn destroy( + this: *const Binding, + /// The child handle the protocol was installed on. + handle: Handle, + ) !void { + try this._destroy_child(this, handle).err(); + } + + pub const guid = Protocol.service_binding_guid; + }; +} From 91463717039d67331544adb6d74dfa99b02cd115 Mon Sep 17 00:00:00 2001 From: Nameless Date: Mon, 19 Feb 2024 20:37:12 -0600 Subject: [PATCH 13/23] work --- .../uefi/protocol/network/simple_network.zig | 306 ++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 lib/std/os/uefi/protocol/network/simple_network.zig diff --git a/lib/std/os/uefi/protocol/network/simple_network.zig b/lib/std/os/uefi/protocol/network/simple_network.zig new file mode 100644 index 000000000000..5de0f1fb3c9a --- /dev/null +++ b/lib/std/os/uefi/protocol/network/simple_network.zig @@ -0,0 +1,306 @@ +const bits = @import("../../bits.zig"); +const protocol = @import("../../protocol.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; + +const Guid = bits.Guid; +const Event = bits.Event; + +pub const SimpleNetwork = extern struct { + revision: u64, + _start: *const fn (*const SimpleNetwork) callconv(cc) Status, + _stop: *const fn (*const SimpleNetwork) callconv(cc) Status, + _initialize: *const fn (*const SimpleNetwork, usize, usize) callconv(cc) Status, + _reset: *const fn (*const SimpleNetwork, bool) callconv(cc) Status, + _shutdown: *const fn (*const SimpleNetwork) callconv(cc) Status, + _receive_filters: *const fn (*const SimpleNetwork, ReceiveFilter, ReceiveFilter, bool, usize, ?[*]const bits.MacAddress) callconv(cc) Status, + _station_address: *const fn (*const SimpleNetwork, bool, ?*const bits.MacAddress) callconv(cc) Status, + _statistics: *const fn (*const SimpleNetwork, bool, ?*usize, ?*Statistics) callconv(cc) Status, + _mcast_ip_to_mac: *const fn (*const SimpleNetwork, bool, *const bits.IpAddress, *bits.MacAddress) callconv(cc) Status, + _nvdata: *const fn (*const SimpleNetwork, bool, usize, usize, [*]u8) callconv(cc) Status, + _get_status: *const fn (*const SimpleNetwork, *InterruptStatus, ?*?[*]u8) callconv(cc) Status, + _transmit: *const fn (*const SimpleNetwork, usize, usize, [*]const u8, ?*const bits.MacAddress, ?*const bits.MacAddress, ?*const u16) callconv(cc) Status, + _receive: *const fn (*const SimpleNetwork, ?*usize, *usize, [*]u8, ?*bits.MacAddress, ?*bits.MacAddress, ?*u16) callconv(cc) Status, + wait_for_packet: Event, + mode: *Mode, + + /// Changes the state of a network interface from "stopped" to "started". + pub fn start(self: *const SimpleNetwork) !void { + try self._start(self).err(); + } + + /// Changes the state of a network interface from "started" to "stopped". + pub fn stop(self: *const SimpleNetwork) !void { + try self._stop(self).err(); + } + + /// Resets a network adapter and allocates the transmit and receive buffers required by the network interface. + pub fn initialize( + self: *const SimpleNetwork, + /// The size, in bytes, of the extra receive buffer space that the driver should allocate for the network + /// interface. Some network interfaces will not be able to use the extra buffer, and the caller will not know if + /// it is actually being used. + extra_rx_buffer_size: usize, + /// The size, in bytes, of the extra transmit buffer space that the driver should allocate for the network + /// interface. Some network interfaces will not be able to use the extra buffer, and the caller will not know if + /// it is actually being used. + extra_tx_buffer_size: usize, + ) !void { + try self._initialize(self, extra_rx_buffer_size, extra_tx_buffer_size).err(); + } + + /// Resets a network adapter and re-initializes it with the parameters that were provided in the previous call to initialize(). + pub fn reset( + self: *const SimpleNetwork, + /// Indicates that the driver may perform a more exhaustive verification operation of the device during reset. + extended_verification: bool, + ) !void { + try self._reset(self, extended_verification).err(); + } + + /// Resets a network adapter and leaves it in a state that is safe for another driver to initialize. + pub fn shutdown(self: *const SimpleNetwork) !void { + try self._shutdown(self).err(); + } + + /// Manages the multicast receive filters of a network interface. + pub fn receiveFilters( + self: *const SimpleNetwork, + /// A bit mask of receive filters to enable on the network interface. + enable: ReceiveFilter, + /// A bit mask of receive filters to disable on the network interface. `.receive_multicast` must be true when + /// `reset_mcast_filter` is true. + disable: ReceiveFilter, + /// When true, resets the multicast receive filter list to the default value. + reset_mcast_filter: bool, + /// A pointer to a list of new multicast receive filter HW MAC addresses. + /// This list will replace any existing multicast HW MAC address list. + /// + /// Only optional when `reset_mcast_filter` is true. + mcast_filter: ?[]const bits.MacAddress, + ) !void { + if (mcast_filter) |filter| { + try self._receive_filters(self, enable, disable, reset_mcast_filter, filter.len, filter.ptr).err(); + } else { + try self._receive_filters(self, enable, disable, reset_mcast_filter, 0, null).err(); + } + } + + /// Modifies or resets the current station address, if supported. + pub fn stationAddress( + self: *const SimpleNetwork, + /// New station address to be used for the network interface. or null to reset the station address to the network + /// interface's permanent station address. + new: ?bits.MacAddress, + ) !void { + if (new) |addr| { + try self._station_address(self, false, &addr).err(); + } else { + try self._station_address(self, true, null).err(); + } + } + + /// Resets the statistics on a network interface. + pub fn resetStatistics(self: *const SimpleNetwork) !void { + try self._statistics(self, true, null, null).err(); + } + + /// Collects the statistics on a network interface. + pub fn collectStatistics(self: *const SimpleNetwork) !Statistics { + var stats: Statistics = undefined; + var size: usize = @sizeOf(Statistics); + switch (self._statistics(self, false, &size, &stats)) { + .success, .buffer_too_small => return stats, + else => |e| { + try e.err(); + return stats; // not an error? assume the buffer was filled and a warning was issued, this is not specified. + }, + } + } + + /// Converts a multicast IP address to a multicast HW MAC address. + pub fn mcastIpToMac( + self: *const SimpleNetwork, + /// If true, the IP address is an IPv6 address. If false, the IP address is an IPv4 address. + ipv6: bool, + /// The multicast IP address that is to be converted to a multicast HW MAC address. + ip: bits.IpAddress, + ) !bits.MacAddress { + var mac: bits.MacAddress = undefined; + try self._mcast_ip_to_mac(self, ipv6, &ip, &mac).err(); + return mac; + } + + /// Performs read and write operations on the NVRAM device attached to a network interface. + pub fn nvdata( + self: *const SimpleNetwork, + /// If true, the operation is a read operation. If false, the operation is a write operation. + is_read: bool, + /// Byte offset in the NVRAM device at which to start the read or write operation. + offset: usize, + buffer: []u8, + ) !void { + try self._nvdata(self, is_read, offset, buffer.len, buffer.ptr).err(); + } + + /// Reads the current interrupt status and recycled transmit buffer status from a network interface. + pub fn getInterruptStatus(self: *const SimpleNetwork) !InterruptStatus { + var interrupt_status: InterruptStatus = null; + try self._get_status(self, &interrupt_status).err(); + return interrupt_status; + } + + /// Reads the current interrupt status and recycled transmit buffer status from a network interface. + pub fn getTransmitBuffer(self: *const SimpleNetwork) !?[*]u8 { + var tx_buf: ?[*]u8 = null; + try self._get_status(self, null, &tx_buf).err(); + return tx_buf; + } + + /// Places a packet in the transmit queue of a network interface. The caller must have filled in the media header in + /// the packet buffer. + /// + /// The provided buffer must not be modified until the transmit operation is complete. + pub fn transmitDirect( + self: *const SimpleNetwork, + /// A pointer to the packet (media header followed by data) to be transmitted. + buffer: []const u8, + ) !void { + try self._transmit(self, 0, buffer.len, buffer.ptr, null, null, null).err(); + } + + /// Places a packet in the transmit queue of a network interface. + /// + /// The provided buffer must not be modified until the transmit operation is complete. + pub fn transmitWithHeader( + self: *const SimpleNetwork, + /// The size, in bytes, of the media header to be filled in by the Transmit() function. Must be non-zero and equal + /// to mode.media_header_size. + header_size: usize, + /// A pointer to the packet (media header followed by data) to be transmitted. The media header will be filled in + /// the first `header_size` bytes of the buffer. + buffer: []u8, + /// The source HW MAC address. If null, will be mode.current_address. + src_addr: ?bits.MacAddress, + /// The destination HW MAC address. + dest_addr: bits.MacAddress, + /// The type of header to build + proto: u16, + ) !void { + return self._transmit(self, header_size, buffer.len, buffer.ptr, if (src_addr) |addr| &addr else null, &dest_addr, &proto); + } + + /// Receives a packet from a network interface. The media header is not parsed from the packet. + pub fn receiveDirect( + self: *const SimpleNetwork, + /// A pointer to the data buffer to receive the data. + buffer: []u8, + ) !usize { + var len = buffer.len; + try self._receive(self, null, &len, buffer.ptr, null, null, null).err(); + return len; + } + + pub const MediaHeader = struct { + size: usize, + src_addr: bits.MacAddress, + dest_addr: bits.MacAddress, + proto: u16, + }; + + /// Receives a packet from a network interface. + pub fn receiveWithHeader( + self: *const SimpleNetwork, + /// A pointer to the data buffer to receive both the media header and the data. + buffer: []u8, + ) !struct { usize, MediaHeader } { + var len = buffer.len; + var header: MediaHeader = undefined; + try self._receive(self, &header.size, &len, buffer.ptr, &header.src_addr, &header.dest_addr, &header.proto).err(); + return .{ len, header }; + } + + pub const guid align(8) = Guid{ + .time_low = 0xa19832b9, + .time_mid = 0xac25, + .time_high_and_version = 0x11d3, + .clock_seq_high_and_reserved = 0x9a, + .clock_seq_low = 0x2d, + .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, + }; + + pub const Mode = extern struct { + state: State, + hw_address_size: u32, + media_header_size: u32, + max_packet_size: u32, + nvram_size: u32, + nvram_access_size: u32, + receive_filter_mask: ReceiveFilter, + receive_filter_setting: ReceiveFilter, + max_mcast_filter_count: u32, + mcast_filter_count: u32, + mcast_filter: [16]bits.MacAddress, + current_address: bits.MacAddress, + broadcast_address: bits.MacAddress, + permanent_address: bits.MacAddress, + if_type: u8, + mac_address_changeable: bool, + multiple_tx_supported: bool, + media_present_supported: bool, + media_present: bool, + }; + + pub const ReceiveFilter = packed struct(u32) { + receive_unicast: bool, + receive_multicast: bool, + receive_broadcast: bool, + receive_promiscuous: bool, + receive_promiscuous_multicast: bool, + _pad: u27 = 0, + }; + + pub const State = enum(u32) { + Stopped, + Started, + Initialized, + }; + + pub const Statistics = extern struct { + rx_total_frames: u64, + rx_good_frames: u64, + rx_undersize_frames: u64, + rx_oversize_frames: u64, + rx_dropped_frames: u64, + rx_unicast_frames: u64, + rx_broadcast_frames: u64, + rx_multicast_frames: u64, + rx_crc_error_frames: u64, + rx_total_bytes: u64, + tx_total_frames: u64, + tx_good_frames: u64, + tx_undersize_frames: u64, + tx_oversize_frames: u64, + tx_dropped_frames: u64, + tx_unicast_frames: u64, + tx_broadcast_frames: u64, + tx_multicast_frames: u64, + tx_crc_error_frames: u64, + tx_total_bytes: u64, + collisions: u64, + unsupported_protocol: u64, + rx_duplicated_frames: u64, + rx_decryptError_frames: u64, + tx_error_frames: u64, + tx_retry_frames: u64, + }; + + pub const InterruptStatus = packed struct(u32) { + receive_interrupt: bool, + transmit_interrupt: bool, + command_interrupt: bool, + software_interrupt: bool, + _pad: u28 = 0, + }; +}; From f365d4b94599643f2f006e2099978913b2699729 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 21 Feb 2024 14:56:05 -0600 Subject: [PATCH 14/23] std.os.uefi: add DiskIo and PartitionInfo protocols --- lib/std/os/uefi/protocol.zig | 2 +- lib/std/os/uefi/protocol/media/disk_io.zig | 32 +++-- .../os/uefi/protocol/media/partition_info.zig | 129 ++++++++++++++++++ 3 files changed, 147 insertions(+), 16 deletions(-) create mode 100644 lib/std/os/uefi/protocol/media/partition_info.zig diff --git a/lib/std/os/uefi/protocol.zig b/lib/std/os/uefi/protocol.zig index 8f12b546d921..c8f14a314658 100644 --- a/lib/std/os/uefi/protocol.zig +++ b/lib/std/os/uefi/protocol.zig @@ -38,7 +38,7 @@ pub const BlockIo = @import("protocol/media/block_io.zig").BlockIo; // NvmExpressPassThrough // SdmmcPassThrough // RamDisk -// PartitionInfo +pub const PartitionInfo = @import("protocol/media/partition_info.zig").PartitionInfo; // NvdimmLabel // UfsDeviceConfig diff --git a/lib/std/os/uefi/protocol/media/disk_io.zig b/lib/std/os/uefi/protocol/media/disk_io.zig index 15441c0dfc5d..8a1b17437d4d 100644 --- a/lib/std/os/uefi/protocol/media/disk_io.zig +++ b/lib/std/os/uefi/protocol/media/disk_io.zig @@ -1,4 +1,3 @@ -const std = @import("../../../../std.zig"); const bits = @import("../../bits.zig"); const cc = bits.cc; @@ -6,36 +5,39 @@ const Status = @import("../../status.zig").Status; const Guid = bits.Guid; +/// This protocol is used to abstract the block accesses of the Block I/O protocol to a more general offset-length +/// protocol. The firmware is responsible for adding this protocol to any Block I/O interface that appears in the +/// system that does not already have a Disk I/O protocol. File systems and other disk access code utilize the +/// Disk I/O protocol pub const DiskIo = extern struct { revision: u64, - - _read: *const fn (*const DiskIo, media_id: u32, offset: u64, buffer_size: usize, buf: [*]u8) callconv(cc) Status, - _write: *const fn (*const DiskIo, media_id: u32, offset: u64, buffer_size: usize, buf: [*]const u8) callconv(cc) Status, + _read_disk: *const fn (*const DiskIo, media_id: u32, offset: u64, buf_size: usize, buf: [*]u8) callconv(cc) Status, + _write_disk: *const fn (*const DiskIo, media_id: u32, offset: u64, buf_size: usize, buf: [*]const u8) callconv(cc) Status, /// Reads a specified number of bytes from a device. pub fn read( - self: *DiskIo, - /// The media ID that the read request is for. + self: *const DiskIo, + /// The ID of the medium to read from. media_id: u32, /// The starting byte offset on the logical block I/O device to read from. offset: u64, - /// The buffer into which the data is read. - buffer: []u8, + /// The buffer to read data into + buf: []u8, ) !void { - try self._read(self, media_id, offset, buffer.len, buffer.ptr).err(); + try self._read_disk(self, media_id, offset, buf.len, buf.ptr).err(); } - /// Writes a specified number of bytes to the device. + /// Writes a specified number of bytes to a device. pub fn write( - self: *DiskIo, - /// The media ID that the write request is for. + self: *const DiskIo, + /// The ID of the medium to write to. media_id: u32, /// The starting byte offset on the logical block I/O device to write to. offset: u64, - /// The buffer from which the data is written. - buffer: []const u8, + /// The buffer to write data from + buf: []const u8, ) !void { - try self._write(self, media_id, offset, buffer.len, buffer.ptr).err(); + try self._write_disk(self, media_id, offset, buf.len, buf.ptr).err(); } pub const guid align(8) = Guid{ diff --git a/lib/std/os/uefi/protocol/media/partition_info.zig b/lib/std/os/uefi/protocol/media/partition_info.zig new file mode 100644 index 000000000000..42470f87db95 --- /dev/null +++ b/lib/std/os/uefi/protocol/media/partition_info.zig @@ -0,0 +1,129 @@ +const std = @import("../../../../std.zig"); +const bits = @import("../../bits.zig"); + +const cc = bits.cc; +const Status = @import("../../status.zig").Status; + +const Guid = bits.Guid; + +pub const PartitionInfo = extern struct { + pub const Type = enum(u32) { + other = 0, + mbr = 1, + gpt = 2, + _, + }; + + revision: u32 align(1), + + /// The type of partition info + type: Type align(1), + + /// If true, this partition is an EFI system partition + system: bool, + + reserved: [7]u8, + + info: extern union { + mbr: MbrEntry, + gpt: GptEntry, + }, + + pub const guid align(8) = Guid{ + .time_low = 0x8cf2f62c, + .time_mid = 0xbc9b, + .time_high_and_version = 0x4821, + .clock_seq_high_and_reserved = 0x80, + .clock_seq_low = 0x8d, + .node = [_]u8{ 0xec, 0x9e, 0xc4, 0x21, 0xa1, 0xa0 }, + }; + + pub const MbrEntry = extern struct { + pub const Record = extern struct { + pub const OsType = enum(u8) { + efi_system_partition = 0xef, + gpt_protective_mbr = 0xee, + _, + }; + + /// 0x80 indicates this is the legacy boot partition. Shall not be used by UEFI firmware. + boot_indicator: u8, + + /// Start of partiion in CHS address format. Shall not be used by UEFI firmware. + starting_chs: [3]u8, + + /// Type of partition, + os_type: OsType, + + /// End of partition in CHS address format. Shall not be used by UEFI firmware. + ending_chs: [3]u8, + + /// Starting LBA of the partition on the disk. This is used by UEFI to find the start of the partition. + starting_lba: u32 align(1), + + /// Size of the partiion in LBA units. This is used by UEFI to find the size of the partition. + size_in_lba: u32 align(1), + }; + + /// Shall not be used by UEFI firmware. + boot_code: [424]u8, + + /// Used to identify the disk, never written by UEFI firmware. + unique_disk_signature: u32 align(1), + + /// Shall not be used by UEFI firmware. + unknown: [2]u8, + + /// The 4 partition records. + partition_record: [4]Record, + + /// The MBR signature + signature: [2]u8 = [_]u8{ 0x55, 0xaa }, + }; + + pub const GptEntry = extern struct { + pub const Attributes = packed struct { + /// If this bit is set, the partition is required for the platform to function. + required: bool, + + /// If this bit is set, then firmware must not produce an EFI_BLOCK_IO_PROTOCOL device for this partition. + no_block: bool, + + /// This bit is set aside by this specification to let systems with traditional PC-AT BIOS firmware + /// implementations inform certain limited, special-purpose software running on these systems that a GPT + /// partition may be bootable. + legacy_bios_bootable: bool, + + /// Must be zero + reserved: u45, + + /// Reserved for GUID specific use. + guid_specific: u16, + }; + + /// Unique ID that defines the purpose and type of this Partition. A value of zero defines that this + /// partition entry is not being used. + partition_type: Guid align(1), + + /// GUID that is unique for every partition entry. Every partition ever created will have a unique GUID. This + /// GUID must be assigned when the GPT Partition Entry is created. + unique_partition_guid: Guid align(1), + + /// Starting LBA of the partition defined by this entry. + starting_lba: u64 align(1), + + /// Ending LBA of the partition defined by this entry. + ending_lba: u64 align(1), + + /// Attribute bits, all bits reserved by UEFI + attributes: Attributes align(1), + + /// Null-terminated string containing a human-readable name of the partition. + partition_name: [36]u16 align(1), + + /// The human readable name of the partition. + pub fn getName(self: *const GptEntry) []const u16 { + return std.mem.sliceTo(&self.partition_name, 0); + } + }; +}; From d5733d203934141482f28706c2a51db1f652e0e9 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 3 Apr 2024 14:56:07 -0500 Subject: [PATCH 15/23] std.os.uefi: revert ucontext, it is no longer necessary --- lib/std/heap.zig | 2 +- lib/std/os/uefi.zig | 5 -- lib/std/os/uefi/allocator.zig | 8 +- lib/std/os/uefi/protocol.zig | 1 - lib/std/os/uefi/table/boot_services.zig | 2 +- lib/std/os/uefi/table/header.zig | 2 +- lib/std/os/uefi/ucontext.zig | 107 ------------------------ 7 files changed, 7 insertions(+), 120 deletions(-) delete mode 100644 lib/std/os/uefi/ucontext.zig diff --git a/lib/std/heap.zig b/lib/std/heap.zig index bc66b9d81883..60a8626969c2 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -236,7 +236,7 @@ else if (builtin.target.os.tag == .plan9) .ptr = undefined, .vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable, } -else if (builtin.target.os.tag == .uefi) +else if (builtin.target.os.tag == .uefi) os.uefi.global_page_allocator.allocator() else Allocator{ diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index dfeac1a0f3ea..1c44a9599c50 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -182,11 +182,6 @@ pub fn getFileSize(fd: fd_t) !u64 { } } -const uctx = @import("uefi/ucontext.zig"); -pub const getcontext = uctx.getcontext; -pub const ucontext_t = uctx.ucontext_t; -pub const REG = uctx.REG; - test { _ = table; _ = protocol; diff --git a/lib/std/os/uefi/allocator.zig b/lib/std/os/uefi/allocator.zig index 67c7afc4a395..8a732bbba8b9 100644 --- a/lib/std/os/uefi/allocator.zig +++ b/lib/std/os/uefi/allocator.zig @@ -8,7 +8,7 @@ const Allocator = mem.Allocator; const assert = std.debug.assert; /// Allocates memory in pages. -/// +/// /// This allocator is backed by `allocatePages` and is therefore only suitable for usage when Boot Services are available. pub const PageAllocator = struct { memory_type: uefi.bits.MemoryDescriptor.Type = .loader_data, @@ -86,7 +86,7 @@ pub const PageAllocator = struct { }; /// Supports the full std.mem.Allocator interface, including up to page alignment. -/// +/// /// This allocator is backed by `allocatePool` and is therefore only suitable for usage when Boot Services are available. pub const PoolAllocator = struct { memory_type: uefi.bits.MemoryDescriptor.Type = .loader_data, @@ -174,7 +174,7 @@ pub const PoolAllocator = struct { }; /// Asserts all allocations are at most 8 byte aligned. This is the highest alignment UEFI will give us directly. -/// +/// /// This allocator is backed by `allocatePool` and is therefore only suitable for usage when Boot Services are available. pub const RawPoolAllocator = struct { memory_type: uefi.bits.MemoryDescriptor.Type = .loader_data, @@ -231,6 +231,6 @@ pub const RawPoolAllocator = struct { ret_addr: usize, ) void { _ = .{ ctx, log2_buf_align, ret_addr }; - uefi.system_table.boot_services.?.freePool(@alignCast(buf)); + uefi.system_table.boot_services.?.freePool(@alignCast(buf)); } }; diff --git a/lib/std/os/uefi/protocol.zig b/lib/std/os/uefi/protocol.zig index c8f14a314658..f5fa2f8e849a 100644 --- a/lib/std/os/uefi/protocol.zig +++ b/lib/std/os/uefi/protocol.zig @@ -1,4 +1,3 @@ - // ** UEFI Specification Version 2.10, August 29, 2022 // 3.2 BootManagerPolicy diff --git a/lib/std/os/uefi/table/boot_services.zig b/lib/std/os/uefi/table/boot_services.zig index 1a9447faaa9a..5e6f32106d19 100644 --- a/lib/std/os/uefi/table/boot_services.zig +++ b/lib/std/os/uefi/table/boot_services.zig @@ -1167,4 +1167,4 @@ pub const BootServices = extern struct { } pub const signature: u64 = 0x56524553544f4f42; -}; \ No newline at end of file +}; diff --git a/lib/std/os/uefi/table/header.zig b/lib/std/os/uefi/table/header.zig index 61f58ce7085c..2996eb36f0d1 100644 --- a/lib/std/os/uefi/table/header.zig +++ b/lib/std/os/uefi/table/header.zig @@ -4,7 +4,7 @@ pub const Header = extern struct { signature: u64, /// The revision of the EFI Specification to which this table conforms. - /// + /// /// Encoded as `(major << 16) | (minor * 10) | (patch)`. revision: u32, diff --git a/lib/std/os/uefi/ucontext.zig b/lib/std/os/uefi/ucontext.zig deleted file mode 100644 index a17c188a9b35..000000000000 --- a/lib/std/os/uefi/ucontext.zig +++ /dev/null @@ -1,107 +0,0 @@ -const builtin = @import("builtin"); -const std = @import("../../std.zig"); - -pub const REG = switch (builtin.cpu.arch) { - .x86_64 => struct { - pub const RAX = 0; - pub const RBX = 1; - pub const RCX = 2; - pub const RDX = 3; - pub const RSI = 4; - pub const RDI = 5; - pub const RBP = 6; - pub const RSP = 7; - pub const R8 = 8; - pub const R9 = 9; - pub const R10 = 10; - pub const R11 = 11; - pub const R12 = 12; - pub const R13 = 13; - pub const R14 = 14; - pub const R15 = 15; - pub const RIP = 16; - pub const EFL = 17; - }, - else => @compileError("arch not supported"), -}; - -pub const fpregset = struct { - fcw: u16, - fsw: u16, - ftw: u8, - reserved1: u8, - fop: u16, - fip: u64, - fdp: u64, - mxcsr: u32, - mxcsr_mask: u32, - st: [8]u128, - xmm: [16]u128, - reserved2: [96]u8, -}; - -pub const mcontext_t = struct { - gregs: [18]usize, - fpregs: fpregset, -}; - -pub const ucontext_t = struct { - mcontext: mcontext_t, -}; - -fn gpRegisterOffset(comptime reg_index: comptime_int) usize { - return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; -} - -pub inline fn getcontext(context: *ucontext_t) usize { - switch (builtin.cpu.arch) { - .x86_64 => { - asm volatile ( - \\ movq %%r8, %[r8_offset:c](%[context]) - \\ movq %%r9, %[r9_offset:c](%[context]) - \\ movq %%r10, %[r10_offset:c](%[context]) - \\ movq %%r11, %[r11_offset:c](%[context]) - \\ movq %%r12, %[r12_offset:c](%[context]) - \\ movq %%r13, %[r13_offset:c](%[context]) - \\ movq %%r14, %[r14_offset:c](%[context]) - \\ movq %%r15, %[r15_offset:c](%[context]) - \\ movq %%rdi, %[rdi_offset:c](%[context]) - \\ movq %%rsi, %[rsi_offset:c](%[context]) - \\ movq %%rbx, %[rbx_offset:c](%[context]) - \\ movq %%rdx, %[rdx_offset:c](%[context]) - \\ movq %%rax, %[rax_offset:c](%[context]) - \\ movq %%rcx, %[rcx_offset:c](%[context]) - \\ movq %%rbp, %[rbp_offset:c](%[context]) - \\ movq %%rsp, %[rsp_offset:c](%[context]) - \\ leaq (%%rip), %%rcx - \\ movq %%rcx, %[rip_offset:c](%[context]) - \\ pushfq - \\ popq %[efl_offset:c](%[context]) - : - : [context] "{rdi}" (context), - [r8_offset] "i" (comptime gpRegisterOffset(REG.R8)), - [r9_offset] "i" (comptime gpRegisterOffset(REG.R9)), - [r10_offset] "i" (comptime gpRegisterOffset(REG.R10)), - [r11_offset] "i" (comptime gpRegisterOffset(REG.R11)), - [r12_offset] "i" (comptime gpRegisterOffset(REG.R12)), - [r13_offset] "i" (comptime gpRegisterOffset(REG.R13)), - [r14_offset] "i" (comptime gpRegisterOffset(REG.R14)), - [r15_offset] "i" (comptime gpRegisterOffset(REG.R15)), - [rax_offset] "i" (comptime gpRegisterOffset(REG.RAX)), - [rbx_offset] "i" (comptime gpRegisterOffset(REG.RBX)), - [rcx_offset] "i" (comptime gpRegisterOffset(REG.RCX)), - [rdx_offset] "i" (comptime gpRegisterOffset(REG.RDX)), - [rsi_offset] "i" (comptime gpRegisterOffset(REG.RSI)), - [rdi_offset] "i" (comptime gpRegisterOffset(REG.RDI)), - [rsp_offset] "i" (comptime gpRegisterOffset(REG.RSP)), - [rbp_offset] "i" (comptime gpRegisterOffset(REG.RBP)), - [rip_offset] "i" (comptime gpRegisterOffset(REG.RIP)), - [efl_offset] "i" (comptime gpRegisterOffset(REG.EFL)), - : "cc", "memory", "rcx" - ); - }, - else => {}, - } - - return 0; -} From cdcf14e0563f963bca0f6860aede237f38f65a63 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 3 Apr 2024 18:20:12 -0500 Subject: [PATCH 16/23] work --- lib/std/fs.zig | 23 ++++++ lib/std/os/uefi/protocol/simple_network.zig | 89 ++++++++++++--------- 2 files changed, 74 insertions(+), 38 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 27081f32f095..cd3133c5326d 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -270,6 +270,7 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t { return 3; } +// Opens the directory that holds the current executable as the working directory. pub fn defaultUefiCwd() Dir { const uefi = std.os.uefi; @@ -717,6 +718,28 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { else => |e| return e, }; }, + .uefi => { + const uefi = std.os.uefi; + + if (uefi.system_table.boot_services) |boot_services| { + const loaded_image = boot_services.openProtocol(uefi.handle, uefi.protocol.LoadedImage, .{}) catch return error.FileNotFound; + + const file_path = if (loaded_image.file_path.node()) |node| file_path: { + if (node == .media and node.media == .file_path) + break :file_path node.media.file_path.path(); + + return error.FileNotFound; + } else return error.FileNotFound; + + // this is an overestimate, but it's the maximum possible wtf8 length. + if (file_path.len * 3 + 1 > MAX_PATH_BYTES) return error.NameTooLong; + + const len = std.unicode.wtf16LeToWtf8(out_buffer, file_path); + return out_buffer[0..len]; + } + + return error.FileNotFound; + }, else => @compileError("std.fs.selfExePath not supported for this target"), } } diff --git a/lib/std/os/uefi/protocol/simple_network.zig b/lib/std/os/uefi/protocol/simple_network.zig index 015addad8120..f18624f888e0 100644 --- a/lib/std/os/uefi/protocol/simple_network.zig +++ b/lib/std/os/uefi/protocol/simple_network.zig @@ -1,9 +1,11 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Event = uefi.Event; -const Guid = uefi.Guid; -const Status = uefi.Status; -const cc = uefi.cc; +const std = @import("../../../std.zig"); +const bits = @import("../bits.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; +const Event = bits.Event; pub const SimpleNetwork = extern struct { revision: u64, @@ -12,35 +14,35 @@ pub const SimpleNetwork = extern struct { _initialize: *const fn (*const SimpleNetwork, usize, usize) callconv(cc) Status, _reset: *const fn (*const SimpleNetwork, bool) callconv(cc) Status, _shutdown: *const fn (*const SimpleNetwork) callconv(cc) Status, - _receive_filters: *const fn (*const SimpleNetwork, ReceiveFilter, ReceiveFilter, bool, usize, ?[*]const MacAddress) callconv(cc) Status, - _station_address: *const fn (*const SimpleNetwork, bool, ?*const MacAddress) callconv(cc) Status, + _receive_filters: *const fn (*const SimpleNetwork, ReceiveFilter, ReceiveFilter, bool, usize, ?[*]const bits.MacAddress) callconv(cc) Status, + _station_address: *const fn (*const SimpleNetwork, bool, ?*const bits.MacAddress) callconv(cc) Status, _statistics: *const fn (*const SimpleNetwork, bool, ?*usize, ?*Statistics) callconv(cc) Status, - _mcast_ip_to_mac: *const fn (*const SimpleNetwork, bool, *const anyopaque, *MacAddress) callconv(cc) Status, + _mcast_ip_to_mac: *const fn (*const SimpleNetwork, bool, *const anyopaque, *bits.MacAddress) callconv(cc) Status, _nvdata: *const fn (*const SimpleNetwork, bool, usize, usize, [*]u8) callconv(cc) Status, _get_status: *const fn (*const SimpleNetwork, *InterruptStatus, ?*?[*]u8) callconv(cc) Status, - _transmit: *const fn (*const SimpleNetwork, usize, usize, [*]const u8, ?*const MacAddress, ?*const MacAddress, ?*const u16) callconv(cc) Status, - _receive: *const fn (*const SimpleNetwork, ?*usize, *usize, [*]u8, ?*MacAddress, ?*MacAddress, ?*u16) callconv(cc) Status, + _transmit: *const fn (*const SimpleNetwork, usize, usize, [*]const u8, ?*const bits.MacAddress, ?*const bits.MacAddress, ?*const u16) callconv(cc) Status, + _receive: *const fn (*const SimpleNetwork, ?*usize, *usize, [*]u8, ?*bits.MacAddress, ?*bits.MacAddress, ?*u16) callconv(cc) Status, wait_for_packet: Event, mode: *Mode, /// Changes the state of a network interface from "stopped" to "started". - pub fn start(self: *const SimpleNetwork) Status { - return self._start(self); + pub fn start(self: *const SimpleNetwork) !void { + try self._start(self).err(); } /// Changes the state of a network interface from "started" to "stopped". - pub fn stop(self: *const SimpleNetwork) Status { - return self._stop(self); + pub fn stop(self: *const SimpleNetwork) !void { + try self._stop(self).err(); } /// Resets a network adapter and allocates the transmit and receive buffers required by the network interface. - pub fn initialize(self: *const SimpleNetwork, extra_rx_buffer_size: usize, extra_tx_buffer_size: usize) Status { - return self._initialize(self, extra_rx_buffer_size, extra_tx_buffer_size); + pub fn initialize(self: *const SimpleNetwork, extra_rx_buffer_size: usize, extra_tx_buffer_size: usize) !void { + try self._initialize(self, extra_rx_buffer_size, extra_tx_buffer_size).err(); } /// Resets a network adapter and reinitializes it with the parameters that were provided in the previous call to initialize(). - pub fn reset(self: *const SimpleNetwork, extended_verification: bool) Status { - return self._reset(self, extended_verification); + pub fn reset(self: *const SimpleNetwork, extended_verification: bool) !void { + try self._reset(self, extended_verification).err(); } /// Resets a network adapter and leaves it in a state that is safe for another driver to initialize. @@ -49,28 +51,41 @@ pub const SimpleNetwork = extern struct { } /// Manages the multicast receive filters of a network interface. - pub fn receiveFilters(self: *const SimpleNetwork, enable: ReceiveFilter, disable: ReceiveFilter, reset_mcast_filter: bool, mcast_filter_cnt: usize, mcast_filter: ?[*]const MacAddress) Status { - return self._receive_filters(self, enable, disable, reset_mcast_filter, mcast_filter_cnt, mcast_filter); + /// + /// If mcast_filter is null, the receive filters are reset to their default values. + pub fn receiveFilters(self: *const SimpleNetwork, enable: ReceiveFilter, disable: ReceiveFilter, mcast_filter: ?[]const bits.MacAddress) !void { + if (mcast_filter) |filter| { + try self._receive_filters(self, enable, disable, false, filter.len, filter.ptr).err(); + } else { + try self._receive_filters(self, enable, disable, true, 0, null).err(); + } } /// Modifies or resets the current station address, if supported. - pub fn stationAddress(self: *const SimpleNetwork, reset_flag: bool, new: ?*const MacAddress) Status { - return self._station_address(self, reset_flag, new); + pub fn stationAddress(self: *const SimpleNetwork, new_addr: ?*const bits.MacAddress) !void { + try self._station_address(self, new_addr == null, new_addr).err(); } - /// Resets or collects the statistics on a network interface. - pub fn statistics(self: *const SimpleNetwork, reset_flag: bool, statistics_size: ?*usize, statistics_table: ?*Statistics) Status { - return self._statistics(self, reset_flag, statistics_size, statistics_table); + /// Resets or collects the statistics on a network interface. The number of bytes written to statistics_table + /// is returned, any fields farther than this value are not touched. + /// + /// If statistics_table is null, the statistics are reset. + pub fn statistics(self: *const SimpleNetwork, statistics_table: ?*Statistics) !usize { + var statistics_size = @sizeOf(Statistics); + try self._statistics(self, statistics_table == null, &statistics_size, statistics_table).err(); + return statistics_size; } /// Converts a multicast IP address to a multicast HW MAC address. - pub fn mcastIpToMac(self: *const SimpleNetwork, ipv6: bool, ip: *const anyopaque, mac: *MacAddress) Status { - return self._mcast_ip_to_mac(self, ipv6, ip, mac); + pub fn mcastIpToMac(self: *const SimpleNetwork, ipv6: bool, ip: *const bits.IpAddress) !bits.MacAddress { + var mac: bits.MacAddress = undefined; + try self._mcast_ip_to_mac(self, ipv6, ip, &mac).err(); + return mac; } /// Performs read and write operations on the NVRAM device attached to a network interface. - pub fn nvdata(self: *const SimpleNetwork, read_write: bool, offset: usize, buffer_size: usize, buffer: [*]u8) Status { - return self._nvdata(self, read_write, offset, buffer_size, buffer); + pub fn nvdata(self: *const SimpleNetwork, is_read: bool, offset: usize, buffer: []u8) !void { + try self._nvdata(self, is_read, offset, buffer.len, buffer.ptr).err(); } /// Reads the current interrupt status and recycled transmit buffer status from a network interface. @@ -79,12 +94,12 @@ pub const SimpleNetwork = extern struct { } /// Places a packet in the transmit queue of a network interface. - pub fn transmit(self: *const SimpleNetwork, header_size: usize, buffer_size: usize, buffer: [*]const u8, src_addr: ?*const MacAddress, dest_addr: ?*const MacAddress, protocol: ?*const u16) Status { + pub fn transmit(self: *const SimpleNetwork, header_size: usize, buffer_size: usize, buffer: [*]const u8, src_addr: ?*const bits.MacAddress, dest_addr: ?*const bits.MacAddress, protocol: ?*const u16) Status { return self._transmit(self, header_size, buffer_size, buffer, src_addr, dest_addr, protocol); } /// Receives a packet from a network interface. - pub fn receive(self: *const SimpleNetwork, header_size: ?*usize, buffer_size: *usize, buffer: [*]u8, src_addr: ?*MacAddress, dest_addr: ?*MacAddress, protocol: ?*u16) Status { + pub fn receive(self: *const SimpleNetwork, header_size: ?*usize, buffer_size: *usize, buffer: [*]u8, src_addr: ?*bits.MacAddress, dest_addr: ?*bits.MacAddress, protocol: ?*u16) Status { return self._receive(self, header_size, buffer_size, buffer, src_addr, dest_addr, protocol); } @@ -97,8 +112,6 @@ pub const SimpleNetwork = extern struct { .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, }; - pub const MacAddress = [32]u8; - pub const Mode = extern struct { state: State, hw_address_size: u32, @@ -110,10 +123,10 @@ pub const SimpleNetwork = extern struct { receive_filter_setting: ReceiveFilter, max_mcast_filter_count: u32, mcast_filter_count: u32, - mcast_filter: [16]MacAddress, - current_address: MacAddress, - broadcast_address: MacAddress, - permanent_address: MacAddress, + mcast_filter: [16]bits.MacAddress, + current_address: bits.MacAddress, + broadcast_address: bits.MacAddress, + permanent_address: bits.MacAddress, if_type: u8, mac_address_changeable: bool, multiple_tx_supported: bool, From d0b50e57b2f9f2e74f10f2d61c2e38d00543d750 Mon Sep 17 00:00:00 2001 From: Nameless Date: Wed, 3 Apr 2024 18:32:28 -0500 Subject: [PATCH 17/23] std.os.uefi: fix regressions introduced in rebase --- lib/std/fs.zig | 13 ++++++++----- lib/std/fs/Dir.zig | 2 -- lib/std/heap.zig | 2 +- lib/std/io.zig | 6 +++--- lib/std/os/uefi.zig | 7 ++----- lib/std/posix.zig | 6 +++++- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index cd3133c5326d..6474847f396e 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -53,12 +53,12 @@ pub const MAX_PATH_BYTES = max_path_bytes; /// * On other platforms, `[]u8` file paths are opaque sequences of bytes with /// no particular encoding. pub const max_path_bytes = switch (native_os) { - .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten, .wasi, .uefi => posix.PATH_MAX, + .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten, .wasi => posix.PATH_MAX, // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes. // If it would require 4 WTF-8 bytes, then there would be a surrogate // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. // +1 for the null byte at the end, which can be encoded in 1 byte. - .windows => windows.PATH_MAX_WIDE * 3 + 1, + .windows, .uefi => windows.PATH_MAX_WIDE * 3 + 1, else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX")) root.os.PATH_MAX else @@ -731,10 +731,13 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { return error.FileNotFound; } else return error.FileNotFound; - // this is an overestimate, but it's the maximum possible wtf8 length. - if (file_path.len * 3 + 1 > MAX_PATH_BYTES) return error.NameTooLong; + if (file_path.len > windows.PATH_MAX_WIDE) return error.NameTooLong; - const len = std.unicode.wtf16LeToWtf8(out_buffer, file_path); + // required because device paths are not aligned + var alignment_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; + @memcpy(alignment_buffer[0..file_path.len], file_path); + + const len = std.unicode.wtf16LeToWtf8(out_buffer, alignment_buffer[0..file_path.len]); return out_buffer[0..len]; } diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index e15eba2017b2..50b2a0eadd11 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -812,8 +812,6 @@ pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.Ope return File{ .handle = handle, - .capable_io_mode = std.io.default_mode, - .intended_io_mode = flags.intended_io_mode, }; } const path_c = try posix.toPosixPath(sub_path); diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 60a8626969c2..c59af4ccf875 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -237,7 +237,7 @@ else if (builtin.target.os.tag == .plan9) .vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable, } else if (builtin.target.os.tag == .uefi) - os.uefi.global_page_allocator.allocator() + std.os.uefi.global_page_allocator.allocator() else Allocator{ .ptr = undefined, diff --git a/lib/std/io.zig b/lib/std/io.zig index f60703b871d2..035c12a9b861 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -28,7 +28,7 @@ fn getStdOutHandle() posix.fd_t { } if (builtin.os.tag == .uefi) { - return .{ .simple_output = os.uefi.system_table.con_out.? }; + return .{ .simple_output = std.os.uefi.system_table.con_out.? }; } return posix.STDOUT_FILENO; @@ -52,7 +52,7 @@ fn getStdErrHandle() posix.fd_t { } if (builtin.os.tag == .uefi) { - return .{ .simple_output = os.uefi.system_table.std_err.? }; + return .{ .simple_output = std.os.uefi.system_table.std_err.? }; } return posix.STDERR_FILENO; @@ -76,7 +76,7 @@ fn getStdInHandle() posix.fd_t { } if (builtin.os.tag == .uefi) { - return .{ .simple_input = os.uefi.system_table.con_in.? }; + return .{ .simple_input = std.os.uefi.system_table.con_in.? }; } return posix.STDIN_FILENO; diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 1c44a9599c50..92d41defe24e 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -23,9 +23,6 @@ pub var handle: bits.Handle = undefined; /// A pointer to the EFI System Table that is passed to the EFI image's entry point. pub var system_table: *table.System = undefined; -// A reasonable default value, UEFI does not specify an upper limit. -pub const PATH_MAX = 4096; - pub const ino_t = u64; pub const mode_t = u64; @@ -73,7 +70,7 @@ pub fn openat(dirfd: fd_t, path: [:0]const u16, flags: protocol.File.OpenMode) ! } } -pub fn read(fd: fd_t, buf: []u8) std.os.ReadError!usize { +pub fn read(fd: fd_t, buf: []u8) std.posix.ReadError!usize { switch (fd) { .file => |p| { return p.read(buf) catch |err| switch (err) { @@ -102,7 +99,7 @@ pub fn read(fd: fd_t, buf: []u8) std.os.ReadError!usize { } } -pub fn write(fd: fd_t, buf: []const u8) std.os.WriteError!usize { +pub fn write(fd: fd_t, buf: []const u8) std.posix.WriteError!usize { switch (fd) { .file => |p| { return p.write(buf) catch |err| switch (err) { diff --git a/lib/std/posix.zig b/lib/std/posix.zig index ef64e8b66a73..2362bdd9625b 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -46,6 +46,7 @@ pub const system = if (use_libc) else switch (native_os) { .linux => linux, .plan9 => std.os.plan9, + .uefi => std.os.uefi, else => struct {}, }; @@ -240,6 +241,10 @@ pub fn close(fd: fd_t) void { _ = std.os.wasi.fd_close(fd); return; } + if (native_os == .uefi) { + uefi.close(fd); + return; + } if (builtin.target.isDarwin()) { // This avoids the EINTR problem. switch (errno(std.c.@"close$NOCANCEL"(fd))) { @@ -748,7 +753,6 @@ pub fn exit(status: u8) noreturn { linux.exit_group(status); } if (native_os == .uefi) { - const uefi = std.os.uefi; // exit() is only available if exitBootServices() has not been called yet. // This call to exit should not fail, so we don't care about its return value. if (uefi.system_table.boot_services) |bs| { From b6505ab10d85595bd2e0047f281357e295dc27d7 Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 4 Apr 2024 08:34:45 -0500 Subject: [PATCH 18/23] std.os.uefi: add uefi to posix and fs.path membership, remove uefi workarounds in std.fs --- lib/std/debug.zig | 6 +- lib/std/fs.zig | 36 ---- lib/std/fs/Dir.zig | 36 ---- lib/std/fs/File.zig | 59 +++-- lib/std/fs/path.zig | 272 ++++++++++++++++++++++++ lib/std/os.zig | 23 ++ lib/std/os/uefi.zig | 71 ++++++- lib/std/os/uefi/bits.zig | 25 +-- lib/std/os/uefi/protocol/media/file.zig | 9 +- lib/std/posix.zig | 71 ++++++- lib/std/start.zig | 4 + lib/std/time.zig | 22 +- 12 files changed, 482 insertions(+), 152 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index aaa99f31776d..84ff794bfcc2 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1117,7 +1117,11 @@ fn readCoffDebugInfo(allocator: mem.Allocator, coff_obj: *coff.Coff) !ModuleDebu di.dwarf = dwarf; } - const raw_path = try coff_obj.getPdbPath() orelse return di; + const raw_path = if (native_os == .uefi) // this is a workaround because pdb paths are never UEFI paths + std.fs.path.basenameWindows(try coff_obj.getPdbPath() orelse return di) + else + try coff_obj.getPdbPath() orelse return di; + const path = blk: { if (fs.path.isAbsolute(raw_path)) { break :blk raw_path; diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 6474847f396e..0d69599eb7dc 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -258,8 +258,6 @@ pub fn cwd() Dir { return .{ .fd = windows.peb().ProcessParameters.CurrentDirectory.Handle }; } else if (native_os == .wasi) { return .{ .fd = std.options.wasiCwd() }; - } else if (native_os == .uefi) { - return defaultUefiCwd(); } else { return .{ .fd = posix.AT.FDCWD }; } @@ -270,40 +268,6 @@ pub fn defaultWasiCwd() std.os.wasi.fd_t { return 3; } -// Opens the directory that holds the current executable as the working directory. -pub fn defaultUefiCwd() Dir { - const uefi = std.os.uefi; - - if (uefi.system_table.boot_services) |boot_services| blk: { - const loaded_image = boot_services.openProtocol(uefi.handle, uefi.protocol.LoadedImage, .{}) catch break :blk; - - const file_path = if (loaded_image.file_path.node()) |node| file_path: { - if (node == .media and node.media == .file_path) - break :file_path node.media.file_path.path(); - - break :blk; - } else break :blk; - - if (file_path.len + 4 > MAX_PATH_BYTES) break :blk; - - // required because device paths are not aligned - var path_buffer: [MAX_PATH_BYTES]u16 = undefined; - @memcpy(path_buffer[0..file_path.len], file_path); - path_buffer[file_path.len] = '\\'; - path_buffer[file_path.len + 1] = '.'; - path_buffer[file_path.len + 2] = '.'; - path_buffer[file_path.len + 3] = 0; - - const file_system = boot_services.openProtocol(loaded_image.device_handle.?, uefi.protocol.SimpleFileSystem, .{}) catch break :blk; - - const volume = file_system.openVolume() catch break :blk; - const fd = volume.open(path_buffer[0 .. file_path.len + 3 :0], .{}, .{}) catch break :blk; - return Dir{ .fd = .{ .file = fd } }; - } - - return Dir{ .fd = .none }; -} - /// Opens a directory at the given path. The directory is a system resource that remains /// open until `close` is called on the result. /// See `openDirAbsoluteZ` for a function that accepts a null-terminated path. diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 50b2a0eadd11..74d91239f810 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -797,23 +797,6 @@ pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.Ope const fd = try posix.openatWasi(self.fd, sub_path, .{}, .{}, .{}, base, .{}); return .{ .handle = fd }; } - if (native_os == .uefi) { - var temp_path: std.os.windows.PathSpace = undefined; - temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, sub_path); - temp_path.data[temp_path.len] = 0; - - const uefi = std.os.uefi; - - const handle = try uefi.openat(self.fd, temp_path.span(), .{ - .read = flags.isRead(), - .write = flags.isWrite(), - }); - errdefer uefi.close(handle); - - return File{ - .handle = handle, - }; - } const path_c = try posix.toPosixPath(sub_path); return self.openFileZ(&path_c, flags); } @@ -968,25 +951,6 @@ pub fn createFile(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File }, .{}), }; } - if (native_os == .uefi) { - var temp_path: std.os.windows.PathSpace = undefined; - temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, sub_path); - temp_path.data[temp_path.len] = 0; - const uefi = std.os.uefi; - - const handle = try uefi.openat(self.fd, temp_path.span(), .{ - .read = true, - .write = true, - .create = true, - }); - errdefer uefi.close(handle); - - return File{ - .handle = handle, - .capable_io_mode = std.io.default_mode, - .intended_io_mode = flags.intended_io_mode, - }; - } const path_c = try posix.toPosixPath(sub_path); return self.createFileZ(&path_c, flags); } diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 2e189b875a0c..f055494bb0fb 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -290,14 +290,6 @@ pub const SeekError = posix.SeekError; /// Repositions read/write file offset relative to the current offset. /// TODO: integrate with async I/O pub fn seekBy(self: File, offset: i64) SeekError!void { - if (builtin.os.tag == .uefi) { - if (self.handle == .file) { - return self.handle.file.movePosition(offset) catch return error.Unseekable; - } else { - return error.Unseekable; - } - } - return posix.lseek_CUR(self.handle, offset); } @@ -310,14 +302,6 @@ pub fn seekFromEnd(self: File, offset: i64) SeekError!void { /// Repositions read/write file offset relative to the beginning. /// TODO: integrate with async I/O pub fn seekTo(self: File, offset: u64) SeekError!void { - if (builtin.os.tag == .uefi) { - if (self.handle == .file) { - return self.handle.file.setPosition(offset) catch return error.Unseekable; - } else { - return error.Unseekable; - } - } - return posix.lseek_SET(self.handle, offset); } @@ -325,14 +309,6 @@ pub const GetSeekPosError = posix.SeekError || posix.FStatError; /// TODO: integrate with async I/O pub fn getPos(self: File) GetSeekPosError!u64 { - if (builtin.os.tag == .uefi) { - if (self.handle == .file) { - return self.handle.file.getPosition() catch return error.Unseekable; - } else { - return error.Unseekable; - } - } - return posix.lseek_CUR_get(self.handle); } @@ -341,13 +317,6 @@ pub fn getEndPos(self: File) GetSeekPosError!u64 { if (builtin.os.tag == .windows) { return windows.GetFileSizeEx(self.handle); } - if (builtin.os.tag == .uefi) { - if (self.handle == .file) { - return self.handle.file.getEndPosition() catch return error.Unseekable; - } else { - return error.Unseekable; - } - } return (try self.stat()).size; } @@ -490,6 +459,34 @@ pub fn stat(self: File) StatError!Stat { .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime), }; } + if (builtin.os.tag == .uefi) { + if (self.handle != .file) + return error.Unexpected; // TODO + + const file: *const std.os.uefi.protocol.File = self.handle.file; + + var pool_allocator = std.os.uefi.PoolAllocator{}; + + const buffer_size = file.getInfoSize(std.os.uefi.bits.FileInfo) catch return error.Unexpected; + const buffer = pool_allocator.allocator().alignedAlloc( + u8, + @alignOf(std.os.uefi.bits.FileInfo), + buffer_size, + ) catch return error.SystemResources; + defer pool_allocator.allocator().free(buffer); + + const info = file.getInfo(std.os.uefi.bits.FileInfo, buffer) catch return error.Unexpected; + + return .{ + .inode = 0, + .size = info.file_size, + .mode = 0, + .kind = .file, + .atime = @bitCast(info.last_access_time.toUnixEpochNanoseconds()), + .mtime = @bitCast(info.modification_time.toUnixEpochNanoseconds()), + .ctime = @bitCast(info.create_time.toUnixEpochNanoseconds()), + }; + } if (builtin.os.tag == .wasi and !builtin.link_libc) { const st = try std.os.fstat_wasi(self.handle); diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index 3376771313d6..daa7355f0c36 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -243,6 +243,8 @@ test join { pub fn isAbsoluteZ(path_c: [*:0]const u8) bool { if (native_os == .windows) { return isAbsoluteWindowsZ(path_c); + } else if (native_os == .uefi) { + return isAbsoluteUefiZ(path_c); } else { return isAbsolutePosixZ(path_c); } @@ -251,6 +253,8 @@ pub fn isAbsoluteZ(path_c: [*:0]const u8) bool { pub fn isAbsolute(path: []const u8) bool { if (native_os == .windows) { return isAbsoluteWindows(path); + } else if (native_os == .uefi) { + return isAbsoluteUefi(path); } else { return isAbsolutePosix(path); } @@ -303,6 +307,14 @@ pub fn isAbsolutePosixZ(path_c: [*:0]const u8) bool { return isAbsolutePosix(mem.sliceTo(path_c, 0)); } +pub fn isAbsoluteUefi(path: []const u8) bool { + return path.len > 0 and path[0] == sep_windows; +} + +pub fn isAbsoluteUefiZ(path_c: [*:0]const u8) bool { + return isAbsoluteUefi(mem.sliceTo(path_c, 0)); +} + test isAbsoluteWindows { try testIsAbsoluteWindows("", false); try testIsAbsoluteWindows("/", true); @@ -334,6 +346,14 @@ test isAbsolutePosix { try testIsAbsolutePosix("./baz", false); } +test isAbsoluteUefi { + try testIsAbsoluteUefi("", false); + try testIsAbsoluteUefi("\\home\\foo", true); + try testIsAbsoluteUefi("\\home\\foo\\..", true); + try testIsAbsoluteUefi("bar\\", false); + try testIsAbsoluteUefi(".\\baz", false); +} + fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) !void { try testing.expectEqual(expected_result, isAbsoluteWindows(path)); } @@ -342,6 +362,10 @@ fn testIsAbsolutePosix(path: []const u8, expected_result: bool) !void { try testing.expectEqual(expected_result, isAbsolutePosix(path)); } +fn testIsAbsoluteUefi(path: []const u8, expected_result: bool) !void { + try testing.expectEqual(expected_result, isAbsoluteUefi(path)); +} + pub const WindowsPath = struct { is_abs: bool, kind: Kind, @@ -742,6 +766,85 @@ pub fn resolvePosix(allocator: Allocator, paths: []const []const u8) Allocator.E } } +/// This function is like a series of `cd` statements executed one after another. +/// It resolves "." and "..", but will not convert relative path to absolute path, use std.fs.Dir.realpath instead. +/// The result does not have a trailing path separator. +/// This function does not perform any syscalls. Executing this series of path +/// lookups on the actual filesystem may produce different results due to +/// symlinks. +pub fn resolveUefi(allocator: Allocator, paths: []const []const u8) Allocator.Error![]u8 { + assert(paths.len > 0); + + var result = std.ArrayList(u8).init(allocator); + defer result.deinit(); + + var negative_count: usize = 0; + var is_abs = false; + + for (paths) |p| { + if (isAbsoluteUefi(p)) { + is_abs = true; + negative_count = 0; + result.clearRetainingCapacity(); + } + var it = mem.tokenizeScalar(u8, p, '\\'); + while (it.next()) |component| { + if (mem.eql(u8, component, ".")) { + continue; + } else if (mem.eql(u8, component, "..")) { + if (result.items.len == 0) { + negative_count += @intFromBool(!is_abs); + continue; + } + while (true) { + const ends_with_slash = result.items[result.items.len - 1] == '\\'; + result.items.len -= 1; + if (ends_with_slash or result.items.len == 0) break; + } + } else if (result.items.len > 0 or is_abs) { + try result.ensureUnusedCapacity(1 + component.len); + result.appendAssumeCapacity('\\'); + result.appendSliceAssumeCapacity(component); + } else { + try result.appendSlice(component); + } + } + } + + if (result.items.len == 0) { + if (is_abs) { + return allocator.dupe(u8, "\\"); + } + if (negative_count == 0) { + return allocator.dupe(u8, "."); + } else { + const real_result = try allocator.alloc(u8, 3 * negative_count - 1); + var count = negative_count - 1; + var i: usize = 0; + while (count > 0) : (count -= 1) { + real_result[i..][0..3].* = "..\\".*; + i += 3; + } + real_result[i..][0..2].* = "..".*; + return real_result; + } + } + + if (negative_count == 0) { + return result.toOwnedSlice(); + } else { + const real_result = try allocator.alloc(u8, 3 * negative_count + result.items.len); + var count = negative_count; + var i: usize = 0; + while (count > 0) : (count -= 1) { + real_result[i..][0..3].* = "..\\".*; + i += 3; + } + @memcpy(real_result[i..][0..result.items.len], result.items); + return real_result; + } +} + test resolve { try testResolveWindows(&[_][]const u8{ "a\\b\\c\\", "..\\..\\.." }, "."); try testResolveWindows(&[_][]const u8{"."}, "."); @@ -798,6 +901,24 @@ test resolvePosix { try testResolvePosix(&.{ ".", "src/test.zig", "..", "../test/cases.zig" }, "test/cases.zig"); } +test resolveUefi { + try testResolveUefi(&[_][]const u8{ "\\a\\b", "c" }, "\\a\\b\\c"); + try testResolveUefi(&[_][]const u8{ "\\a\\b", "c", "\\\\d", "e\\\\" }, "\\d\\e"); + try testResolveUefi(&[_][]const u8{ "\\a\\b\\c", "..", "..\\" }, "\\a"); + try testResolveUefi(&[_][]const u8{ "\\", "..", ".." }, "\\"); + try testResolveUefi(&[_][]const u8{"\\a\\b\\c\\"}, "\\a\\b\\c"); + + try testResolveUefi(&[_][]const u8{ "\\var\\lib", "..\\", "file\\" }, "\\var\\file"); + try testResolveUefi(&[_][]const u8{ "\\var\\lib", "\\..\\", "file\\" }, "\\file"); + try testResolveUefi(&[_][]const u8{ "\\some\\dir", ".", "\\absolute\\" }, "\\absolute"); + try testResolveUefi(&[_][]const u8{ "\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }, "\\foo\\tmp.3\\cycles\\root.js"); + + // Keep relative paths relative. + try testResolveUefi(&[_][]const u8{"a\\b"}, "a\\b"); + try testResolveUefi(&[_][]const u8{"."}, "."); + try testResolveUefi(&[_][]const u8{".", "src\\test.zig", "..", "..\\test\\cases.zig"}, "test\\cases.zig"); +} + fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void { const actual = try resolveWindows(testing.allocator, paths); defer testing.allocator.free(actual); @@ -810,6 +931,12 @@ fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void { try testing.expectEqualStrings(expected, actual); } +fn testResolveUefi(paths: []const []const u8, expected: []const u8) !void { + const actual = try resolveUefi(testing.allocator, paths); + defer testing.allocator.free(actual); + try testing.expectEqualStrings(expected, actual); +} + /// Strip the last component from a file path. /// /// If the path is a file in the current directory (no directory component) @@ -819,6 +946,8 @@ fn testResolvePosix(paths: []const []const u8, expected: []const u8) !void { pub fn dirname(path: []const u8) ?[]const u8 { if (native_os == .windows) { return dirnameWindows(path); + } else if (native_os == .uefi) { + return dirnameUefi(path); } else { return dirnamePosix(path); } @@ -884,6 +1013,32 @@ pub fn dirnamePosix(path: []const u8) ?[]const u8 { return path[0..end_index]; } +pub fn dirnameUefi(path: []const u8) ?[]const u8 { + if (path.len == 0) + return null; + + var end_index: usize = path.len - 1; + while (path[end_index] == '\\') { + if (end_index == 0) + return null; + end_index -= 1; + } + + while (path[end_index] != '\\') { + if (end_index == 0) + return null; + end_index -= 1; + } + + if (end_index == 0 and path[0] == '\\') + return path[0..1]; + + if (end_index == 0) + return null; + + return path[0..end_index]; +} + test dirnamePosix { try testDirnamePosix("/a/b/c", "/a/b"); try testDirnamePosix("/a/b/c///", "/a/b"); @@ -934,6 +1089,20 @@ test dirnameWindows { try testDirnameWindows("foo", null); } +test dirnameUefi { + try testDirnameUefi("\\a\\b\\c", "\\a\\b"); + try testDirnameUefi("\\a\\b\\c\\\\\\", "\\a\\b"); + try testDirnameUefi("\\a", "\\"); + try testDirnameUefi("\\", null); + try testDirnameUefi("\\\\", null); + try testDirnameUefi("\\\\\\", null); + try testDirnameUefi("\\\\\\\\", null); + try testDirnameUefi("", null); + try testDirnameUefi("a", null); + try testDirnameUefi("a\\", null); + try testDirnameUefi("a\\\\", null); +} + fn testDirnamePosix(input: []const u8, expected_output: ?[]const u8) !void { if (dirnamePosix(input)) |output| { try testing.expect(mem.eql(u8, output, expected_output.?)); @@ -950,9 +1119,19 @@ fn testDirnameWindows(input: []const u8, expected_output: ?[]const u8) !void { } } +fn testDirnameUefi(input: []const u8, expected_output: ?[]const u8) !void { + if (dirnameUefi(input)) |output| { + try testing.expect(mem.eql(u8, output, expected_output.?)); + } else { + try testing.expect(expected_output == null); + } +} + pub fn basename(path: []const u8) []const u8 { if (native_os == .windows) { return basenameWindows(path); + } else if (native_os == .uefi) { + return basenameUefi(path); } else { return basenamePosix(path); } @@ -1011,6 +1190,27 @@ pub fn basenameWindows(path: []const u8) []const u8 { return path[start_index + 1 .. end_index]; } +pub fn basenameUefi(path: []const u8) []const u8 { + if (path.len == 0) + return &[_]u8{}; + + var end_index: usize = path.len - 1; + while (path[end_index] == '\\') { + if (end_index == 0) + return &[_]u8{}; + end_index -= 1; + } + var start_index: usize = end_index; + end_index += 1; + while (path[start_index] != '\\') { + if (start_index == 0) + return path[0..end_index]; + start_index -= 1; + } + + return path[start_index + 1 .. end_index]; +} + test basename { try testBasename("", ""); try testBasename("/", ""); @@ -1048,6 +1248,13 @@ test basename { try testBasenameWindows("C:basename.ext\\\\", "basename.ext"); try testBasenameWindows("C:foo", "foo"); try testBasenameWindows("file:stream", "file:stream"); + + try testBasenameUefi("\\dir\\basename.ext", "basename.ext"); + try testBasenameUefi("\\basename.ext", "basename.ext"); + try testBasenameUefi("basename.ext", "basename.ext"); + try testBasenameUefi("basename.ext\\", "basename.ext"); + try testBasenameUefi("basename.ext\\\\", "basename.ext"); + try testBasenameUefi("foo", "foo"); } fn testBasename(input: []const u8, expected_output: []const u8) !void { @@ -1062,6 +1269,10 @@ fn testBasenameWindows(input: []const u8, expected_output: []const u8) !void { try testing.expectEqualSlices(u8, expected_output, basenameWindows(input)); } +fn testBasenameUefi(input: []const u8, expected_output: []const u8) !void { + try testing.expectEqualSlices(u8, expected_output, basenameUefi(input)); +} + /// Returns the relative path from `from` to `to`. If `from` and `to` each /// resolve to the same path (after calling `resolve` on each), a zero-length /// string is returned. @@ -1186,6 +1397,48 @@ pub fn relativePosix(allocator: Allocator, from: []const u8, to: []const u8) ![] return [_]u8{}; } +pub fn relativeUefi(allocator: Allocator, from: []const u8, to: []const u8) ![]u8 { + const cwd = try process.getCwdAlloc(allocator); + defer allocator.free(cwd); + const resolved_from = try resolveUefi(allocator, &[_][]const u8{ cwd, from }); + defer allocator.free(resolved_from); + const resolved_to = try resolveUefi(allocator, &[_][]const u8{ cwd, to }); + defer allocator.free(resolved_to); + + var from_it = mem.tokenizeScalar(u8, resolved_from, '\\'); + var to_it = mem.tokenizeScalar(u8, resolved_to, '\\'); + while (true) { + const from_component = from_it.next() orelse return allocator.dupe(u8, to_it.rest()); + const to_rest = to_it.rest(); + if (to_it.next()) |to_component| { + if (mem.eql(u8, from_component, to_component)) + continue; + } + var up_count: usize = 1; + while (from_it.next()) |_| { + up_count += 1; + } + const up_index_end = up_count * "..\\".len; + const result = try allocator.alloc(u8, up_index_end + to_rest.len); + errdefer allocator.free(result); + + var result_index: usize = 0; + while (result_index < up_index_end) { + result[result_index..][0..3].* = "..\\".*; + result_index += 3; + } + if (to_rest.len == 0) { + // shave off the trailing slash + return allocator.realloc(result, result_index - 1); + } + + @memcpy(result[result_index..][0..to_rest.len], to_rest); + return result; + } + + return [_]u8{}; +} + test relative { try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); @@ -1232,6 +1485,19 @@ test relative { try testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); try testRelativePosix("/baz-quux", "/baz", "../baz"); try testRelativePosix("/baz", "/baz-quux", "../baz-quux"); + + try testRelativeUefi("\\var\\lib", "\\var", ".."); + try testRelativeUefi("\\var\\lib", "\\bin", "..\\..\\bin"); + try testRelativeUefi("\\var\\lib", "\\var\\lib", ""); + try testRelativeUefi("\\var\\lib", "\\var\\apache", "..\\apache"); + try testRelativeUefi("\\var\\", "\\var\\lib", "lib"); + try testRelativeUefi("\\", "\\var\\lib", "var\\lib"); + try testRelativeUefi("\\foo\\test", "\\foo\\test\\bar\\package.json", "bar\\package.json"); + try testRelativeUefi("\\Users\\a\\web\\b\\test\\mails", "\\Users\\a\\web\\b", "..\\.."); + try testRelativeUefi("\\foo\\bar\\baz-quux", "\\foo\\bar\\baz", "..\\baz"); + try testRelativeUefi("\\foo\\bar\\baz", "\\foo\\bar\\baz-quux", "..\\baz-quux"); + try testRelativeUefi("\\baz-quux", "\\baz", "..\\baz"); + try testRelativeUefi("\\baz", "\\baz-quux", "..\\baz-quux"); } fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) !void { @@ -1246,6 +1512,12 @@ fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []cons try testing.expectEqualStrings(expected_output, result); } +fn testRelativeUefi(from: []const u8, to: []const u8, expected_output: []const u8) !void { + const result = try relativeUefi(testing.allocator, from, to); + defer testing.allocator.free(result); + try testing.expectEqualStrings(expected_output, result); +} + /// Searches for a file extension separated by a `.` and returns the string after that `.`. /// Files that end or start with `.` and have no other `.` in their name /// are considered to have no extension, in which case this returns "". diff --git a/lib/std/os.zig b/lib/std/os.zig index 53f1bef4fedb..62b8dfbac0c8 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -80,6 +80,7 @@ pub fn isGetFdPathSupportedOnTarget(os: std.Target.Os) bool { .solaris, .illumos, .freebsd, + .uefi, => true, .dragonfly => os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt, @@ -231,6 +232,28 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[MAX_PATH_BYTES]u8) std.posix. const len = mem.indexOfScalar(u8, out_buffer[0..], 0) orelse MAX_PATH_BYTES; return out_buffer[0..len]; }, + .uefi => { + if (fd != .file) + return error.FileNotFound; + + const file: *const uefi.protocol.File = fd.file; + + var pool_allocator = uefi.PoolAllocator{}; + + const buffer_size = file.getInfoSize(uefi.bits.FileInfo) catch return error.FileNotFound; + const buffer = pool_allocator.allocator().alignedAlloc( + u8, + @alignOf(uefi.bits.FileInfo), + buffer_size, + ) catch return error.NameTooLong; + defer pool_allocator.allocator().free(buffer); + + const info = file.getInfo(uefi.bits.FileInfo, buffer) catch return error.NameTooLong; + const path = info.getFileName(); + + const len = std.unicode.wtf16LeToWtf8(out_buffer, path); + return out_buffer[0..len]; + }, else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above } } diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 92d41defe24e..4e62abad6365 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -23,6 +23,8 @@ pub var handle: bits.Handle = undefined; /// A pointer to the EFI System Table that is passed to the EFI image's entry point. pub var system_table: *table.System = undefined; +pub var working_directory: fd_t = .none; + pub const ino_t = u64; pub const mode_t = u64; @@ -31,6 +33,7 @@ pub const fd_t = union(enum) { simple_output: *const protocol.SimpleTextOutput, simple_input: *const protocol.SimpleTextInput, none: void, // used to refer to a file descriptor that is not open and cannot do anything + cwd: void, // used to refer to the current working directory }; fn unexpectedError(err: anyerror) error{Unexpected} { @@ -38,12 +41,45 @@ fn unexpectedError(err: anyerror) error{Unexpected} { return error.Unexpected; } +pub fn cwd() fd_t { + const uefi = std.os.uefi; + + if (uefi.system_table.boot_services) |boot_services| blk: { + const loaded_image = boot_services.openProtocol(uefi.handle, uefi.protocol.LoadedImage, .{}) catch break :blk; + + const file_path = if (loaded_image.file_path.node()) |node| file_path: { + if (node == .media and node.media == .file_path) + break :file_path node.media.file_path.path(); + + break :blk; + } else break :blk; + + if (file_path.len + 4 > std.fs.max_path_bytes) break :blk; + + // required because device paths are not aligned + var path_buffer: [std.fs.max_path_bytes]u16 = undefined; + @memcpy(path_buffer[0..file_path.len], file_path); + path_buffer[file_path.len] = '\\'; + path_buffer[file_path.len + 1] = '.'; + path_buffer[file_path.len + 2] = '.'; + path_buffer[file_path.len + 3] = 0; + + const file_system = boot_services.openProtocol(loaded_image.device_handle.?, uefi.protocol.SimpleFileSystem, .{}) catch break :blk; + + const volume = file_system.openVolume() catch break :blk; + return .{ .file = volume.open(path_buffer[0 .. file_path.len + 3 :0], .{}, .{}) catch break :blk }; + } + + return .none; +} + pub fn close(fd: fd_t) void { switch (fd) { .file => |p| p.close(), .simple_output => |p| p.reset(true) catch {}, .simple_input => |p| p.reset(true) catch {}, .none => {}, + .cwd => {}, } } @@ -67,6 +103,7 @@ pub fn openat(dirfd: fd_t, path: [:0]const u16, flags: protocol.File.OpenMode) ! .simple_output => return error.NotDir, .simple_input => return error.NotDir, .none => return error.NotDir, + .cwd => return openat(working_directory, path, flags), } } @@ -170,7 +207,7 @@ pub fn write(fd: fd_t, buf: []const u8) std.posix.WriteError!usize { } } -pub fn getFileSize(fd: fd_t) !u64 { +pub fn getFileEndPosition(fd: fd_t) !u64 { switch (fd) { .file => |p| { return p.getEndPosition() catch return error.Unseekable; @@ -179,6 +216,38 @@ pub fn getFileSize(fd: fd_t) !u64 { } } +pub fn getFilePosition(fd: fd_t) !u64 { + switch (fd) { + .file => |p| { + return p.getPosition() catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +pub fn setFilePosition(fd: fd_t, pos: u64) !void { + switch (fd) { + .file => |p| { + return p.setPosition(pos) catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +pub const PATH_MAX = 8192; + +pub const O = packed struct { + ACCMODE: std.posix.ACCMODE = .RDONLY, + NONBLOCK: bool = false, + CLOEXEC: bool = false, + CREAT: bool = false, + TRUNC: bool = false, +}; + +pub const AT = struct { + pub const FDCWD: fd_t = .cwd; +}; + test { _ = table; _ = protocol; diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig index 14e3a6a69576..3f48d32b44bd 100644 --- a/lib/std/os/uefi/bits.zig +++ b/lib/std/os/uefi/bits.zig @@ -72,7 +72,7 @@ pub const Time = extern struct { /// Allowed values are -1440 to 1440 or unspecified_timezone timezone: i16, daylight: packed struct { - _pad1: u6, + _pad1: u6 = 0, /// If true, the time has been adjusted for daylight savings time. in_daylight: bool, @@ -100,7 +100,7 @@ pub const Time = extern struct { days += time.epoch.getDaysInMonth(leap_kind, @enumFromInt(month)); } - days += self.day - 1; + days += self.day -| 1; return days * time.s_per_day + @as(u32, self.hour) * time.s_per_hour + @@ -109,24 +109,13 @@ pub const Time = extern struct { } /// Returns the time in nanoseconds since 1900-01-01 00:00:00. - pub fn toEpochNanoseconds(self: Time) u128 { - return @as(u128, self.toEpochSeconds()) * time.ns_per_s + self.nanosecond; + pub fn toEpochNanoseconds(self: Time) i128 { + return @as(i128, self.toEpochSeconds()) * time.ns_per_s + self.nanosecond; } - /// Returns the time in nanoseconds since 1970-01-01 00:00:00, or null if the time does not fit. - pub fn toUnixEpochNanoseconds(self: Time) ?u64 { - const nanoseconds = self.toEpochNanoseconds(); - - if (nanoseconds < time.epoch.unix_epoch_nanoseconds) { - return null; - } - - const unix = nanoseconds - time.epoch.unix_epoch_nanoseconds; - if (unix > std.math.maxInt(u64)) { - return null; - } - - return @intCast(unix); + /// Returns the time in nanoseconds since 1970-01-01 00:00:00. + pub fn toUnixEpochNanoseconds(self: Time) i128 { + return self.toEpochNanoseconds() -| unix_epoch_nanoseconds; } pub const unix_epoch = Time{ diff --git a/lib/std/os/uefi/protocol/media/file.zig b/lib/std/os/uefi/protocol/media/file.zig index 6874aee860b2..1787e72b7013 100644 --- a/lib/std/os/uefi/protocol/media/file.zig +++ b/lib/std/os/uefi/protocol/media/file.zig @@ -145,7 +145,10 @@ pub const File = extern struct { comptime Information: type, ) !usize { var buffer_size: usize = 0; - try self._get_info(self, Information.guid, &buffer_size, null).err(); + self._get_info(self, &Information.guid, &buffer_size, null).err() catch |err| switch (err) { + error.BufferTooSmall => {}, + else => |e| return e, + }; return buffer_size; } @@ -156,9 +159,9 @@ pub const File = extern struct { self: *const File, comptime Information: type, buffer: []align(@alignOf(Information)) u8, - ) !*const Information { + ) !*Information { var size: usize = buffer.len; - try self._get_info(self, Information.guid, &size, buffer.ptr).err(); + try self._get_info(self, &Information.guid, &size, buffer.ptr).err(); return @ptrCast(buffer.ptr); } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 2362bdd9625b..612c42a5103a 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -869,8 +869,8 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { /// This function assumes that all vectors, including zero-length vectors, have /// a pointer within the address space of the application. pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { - if (native_os == .windows) { - // TODO improve this to use ReadFileScatter + if (native_os == .windows or native_os == .uefi) { + // TODO improve this to use ReadFileScatter on windows if (iov.len == 0) return 0; const first = iov[0]; return read(fd, first.iov_base[0..first.iov_len]); @@ -938,6 +938,12 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { if (native_os == .windows) { return windows.ReadFile(fd, buf, offset); } + if (native_os == .uefi) { + const pos = lseek_CUR_get(fd) catch return error.Unseekable; + lseek_SET(fd, offset) catch return error.Unseekable; + try read(fd, buf); + lseek_SET(fd, pos) catch return error.Unseekable; + } if (native_os == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ .iov_base = buf.ptr, @@ -1077,7 +1083,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { /// On these systems, the read races with concurrent writes to the same file descriptor. pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { const have_pread_but_not_preadv = switch (native_os) { - .windows, .macos, .ios, .watchos, .tvos, .haiku => true, + .windows, .macos, .ios, .watchos, .tvos, .haiku, .uefi => true, else => false, }; if (have_pread_but_not_preadv) { @@ -1270,8 +1276,8 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { /// This function assumes that all vectors, including zero-length vectors, have /// a pointer within the address space of the application. pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { - if (native_os == .windows) { - // TODO improve this to use WriteFileScatter + if (native_os == .windows or native_os == .uefi) { + // TODO improve this to use WriteFileScatter on windows if (iov.len == 0) return 0; const first = iov[0]; return write(fd, first.iov_base[0..first.iov_len]); @@ -1349,6 +1355,12 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { if (native_os == .windows) { return windows.WriteFile(fd, bytes, offset); } + if (native_os == .uefi) { + const pos = lseek_CUR_get(fd) catch return error.Unseekable; + lseek_SET(fd, offset) catch return error.Unseekable; + try write(fd, bytes); + lseek_SET(fd, pos) catch return error.Unseekable; + } if (native_os == .wasi and !builtin.link_libc) { const ciovs = [1]iovec_const{iovec_const{ .iov_base = bytes.ptr, @@ -1434,7 +1446,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { /// If `iov.len` is larger than `IOV_MAX`, a partial write will occur. pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize { const have_pwrite_but_not_pwritev = switch (native_os) { - .windows, .macos, .ios, .watchos, .tvos, .haiku => true, + .windows, .macos, .ios, .watchos, .tvos, .haiku, .uefi => true, else => false, }; @@ -1569,7 +1581,7 @@ pub const OpenError = error{ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t { if (native_os == .windows) { @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return openat(AT.FDCWD, file_path, flags, perm); } const file_path_c = try toPosixPath(file_path); @@ -1584,7 +1596,7 @@ pub fn open(file_path: []const u8, flags: O, perm: mode_t) OpenError!fd_t { pub fn openZ(file_path: [*:0]const u8, flags: O, perm: mode_t) OpenError!fd_t { if (native_os == .windows) { @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return open(mem.sliceTo(file_path, 0), flags, perm); } @@ -1648,6 +1660,16 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE } return fd; + } else if (native_os == .uefi) { + var temp_path: std.os.windows.PathSpace = undefined; + temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, file_path); + temp_path.data[temp_path.len] = 0; + + return try uefi.openat(dir_fd, temp_path.span(), .{ + .read = true, // must be specified when reading or writing, which is always the case + .write = flags.ACCMODE != .RDONLY or flags.CREAT, + .create = flags.CREAT, + }); } const file_path_c = try toPosixPath(file_path); return openatZ(dir_fd, &file_path_c, flags, mode); @@ -1749,7 +1771,7 @@ fn openOptionsFromFlagsWasi(oflag: O) OpenError!WasiOpenOptions { pub fn openatZ(dir_fd: fd_t, file_path: [*:0]const u8, flags: O, mode: mode_t) OpenError!fd_t { if (native_os == .windows) { @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.fs API"); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return openat(dir_fd, mem.sliceTo(file_path, 0), flags, mode); } @@ -5065,6 +5087,9 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { if (native_os == .windows) { return windows.SetFilePointerEx_BEGIN(fd, offset); } + if (native_os == .uefi) { + return uefi.setFilePosition(fd, offset); + } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { @@ -5108,6 +5133,18 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { if (native_os == .windows) { return windows.SetFilePointerEx_CURRENT(fd, offset); } + if (native_os == .uefi) { + var pos: i64 = @intCast(try uefi.getFilePosition(fd)); + const end = try uefi.getFileEndPosition(fd); + if (pos + offset < 0) { + pos = 0; + } else if (pos + offset > end) { + pos = end; + } else { + pos += offset; + } + return uefi.setFilePosition(@intCast(pos)); + } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) { @@ -5150,6 +5187,10 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { if (native_os == .windows) { return windows.SetFilePointerEx_END(fd, offset); } + if (native_os == .uefi) { + const end = try uefi.getFileEndPosition(fd); + return uefi.setFilePosition(end); + } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, offset, .END, &new_offset)) { @@ -5192,6 +5233,9 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { if (native_os == .windows) { return windows.SetFilePointerEx_CURRENT_get(fd); } + if (native_os == .uefi) { + return uefi.getFilePosition(fd); + } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, 0, .CUR, &new_offset)) { @@ -5428,6 +5472,15 @@ pub fn realpathW(pathname: []const u16, out_buffer: *[max_path_bytes]u8) RealPat /// Spurious wakeups are possible and no precision of timing is guaranteed. pub fn nanosleep(seconds: u64, nanoseconds: u64) void { + if (native_os == .uefi) { + if (uefi.system_table.boot_services) |boot_services| { + boot_services.stall(seconds * std.time.us_per_s + nanoseconds / std.time.ns_per_us); + return; + } else { + // no boot services, should we busy wait? + return; + } + } var req = timespec{ .tv_sec = cast(isize, seconds) orelse maxInt(isize), .tv_nsec = cast(isize, nanoseconds) orelse maxInt(isize), diff --git a/lib/std/start.zig b/lib/std/start.zig index f55da5ce0a70..c80287e41912 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -216,6 +216,9 @@ fn EfiMain(handle: uefi.bits.Handle, system_table: *uefi.table.System) callconv( uefi.handle = handle; uefi.system_table = system_table; + // This is not strictly necessary, but fetching the working directory is relatively allocation heavy, so we do it once here. + uefi.working_directory = uefi.cwd(); + switch (@typeInfo(@TypeOf(root.main)).Fn.return_type.?) { noreturn => { root.main(); @@ -236,6 +239,7 @@ fn EfiMain(handle: uefi.bits.Handle, system_table: *uefi.table.System) callconv( if (@errorReturnTrace()) |trace| { std.debug.dumpStackTrace(trace.*); } + std.time.sleep(5 * std.time.ns_per_s); return @intFromEnum(uefi.Status.aborted); }; diff --git a/lib/std/time.zig b/lib/std/time.zig index e8b37d3010ee..c6364b706c70 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -40,14 +40,6 @@ pub fn sleep(nanoseconds: u64) void { return; } - if (builtin.os.tag == .uefi) { - const boot_services = std.os.uefi.system_table.boot_services.?; - const us_from_ns = nanoseconds / ns_per_us; - const us = math.cast(usize, us_from_ns) orelse math.maxInt(usize); - _ = boot_services.stall(us); - return; - } - const s = nanoseconds / ns_per_s; const ns = nanoseconds % ns_per_s; posix.nanosleep(s, ns); @@ -108,10 +100,8 @@ pub fn nanoTimestamp() i128 { return ns; }, .uefi => { - var value: std.os.uefi.Time = undefined; - const status = std.os.uefi.system_table.runtime_services.getTime(&value, null); - assert(status == .Success); - return value.toEpoch(); + const value = std.os.uefi.system_table.runtime_services.getTime() catch return 0; + return value.toUnixEpochNanoseconds(); }, else => { var ts: posix.timespec = undefined; @@ -198,10 +188,8 @@ pub const Instant = struct { return .{ .timestamp = ns }; }, .uefi => { - var value: std.os.uefi.Time = undefined; - const status = std.os.uefi.system_table.runtime_services.getTime(&value, null); - if (status != .Success) return error.Unsupported; - return Instant{ .timestamp = value.toEpoch() }; + const value = std.os.uefi.system_table.runtime_services.getTime() catch return error.Unsupported; + return Instant{ .timestamp = @truncate(value.toEpochNanoseconds()) }; }, // On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while // suspended. @@ -262,7 +250,7 @@ pub const Instant = struct { } // WASI timestamps are directly in nanoseconds - if (builtin.os.tag == .wasi) { + if (!is_posix) { return self.timestamp - earlier.timestamp; } From 5e70d28f0d8839f56c82f2db9a5bcff9dfc44661 Mon Sep 17 00:00:00 2001 From: Nameless Date: Thu, 4 Apr 2024 10:28:46 -0500 Subject: [PATCH 19/23] std.os.uefi: fix getFdPath --- lib/std/os.zig | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/lib/std/os.zig b/lib/std/os.zig index 62b8dfbac0c8..4106479ec436 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -236,23 +236,47 @@ pub fn getFdPath(fd: std.posix.fd_t, out_buffer: *[MAX_PATH_BYTES]u8) std.posix. if (fd != .file) return error.FileNotFound; - const file: *const uefi.protocol.File = fd.file; + var file: *const uefi.protocol.File = fd.file; var pool_allocator = uefi.PoolAllocator{}; + var segments = std.ArrayList([]const u16).init(pool_allocator.allocator()); + defer segments.deinit(); - const buffer_size = file.getInfoSize(uefi.bits.FileInfo) catch return error.FileNotFound; - const buffer = pool_allocator.allocator().alignedAlloc( - u8, - @alignOf(uefi.bits.FileInfo), - buffer_size, - ) catch return error.NameTooLong; + var buffer: []align(@alignOf(uefi.bits.FileInfo)) u8 = &.{}; defer pool_allocator.allocator().free(buffer); - const info = file.getInfo(uefi.bits.FileInfo, buffer) catch return error.NameTooLong; - const path = info.getFileName(); + while (true) { + const buffer_size = file.getInfoSize(uefi.bits.FileInfo) catch return error.FileNotFound; + buffer = pool_allocator.allocator().realloc(buffer, buffer_size) catch return error.NameTooLong; - const len = std.unicode.wtf16LeToWtf8(out_buffer, path); - return out_buffer[0..len]; + const info = file.getInfo(uefi.bits.FileInfo, buffer) catch return error.NameTooLong; + segments.insert( + 0, + pool_allocator.allocator().dupe(u16, info.getFileName()) catch return error.NameTooLong, + ) catch return error.NameTooLong; + + const new_file = file.open(&.{ '.', '.' }, .{}, .{}) catch break; + + if (file != fd.file) { + file.close(); + } + + file = new_file; + } + + var index: usize = 0; + for (segments.items) |segment| { + const len = std.unicode.wtf16LeToWtf8(out_buffer[index..], segment); + index += len; + out_buffer[index] = '\\'; + index += 1; + + pool_allocator.allocator().free(segment); + } + + if (segments.items.len > 0) index -= 1; // strip final backslash + + return out_buffer[0..index]; }, else => unreachable, // made unreachable by isGetFdPathSupportedOnTarget above } From 84317674e45d5e039c619de48c9027433acc1cb1 Mon Sep 17 00:00:00 2001 From: Nameless Date: Sat, 6 Apr 2024 11:34:48 -0500 Subject: [PATCH 20/23] std.os.uefi: posix membership part 2 --- lib/std/fs.zig | 10 +- lib/std/fs/File.zig | 29 -- lib/std/os/uefi.zig | 216 +---------- lib/std/os/uefi/bits.zig | 12 +- lib/std/os/uefi/posix.zig | 491 ++++++++++++++++++++++++ lib/std/os/uefi/protocol/media/file.zig | 11 +- lib/std/posix.zig | 97 +++-- lib/std/time.zig | 10 +- 8 files changed, 574 insertions(+), 302 deletions(-) create mode 100644 lib/std/os/uefi/posix.zig diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 0d69599eb7dc..646697d50d88 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -20,7 +20,7 @@ pub const File = @import("fs/File.zig"); pub const path = @import("fs/path.zig"); pub const has_executable_bit = switch (native_os) { - .windows, .wasi => false, + .windows, .wasi, .uefi => false, else => true, }; @@ -53,12 +53,12 @@ pub const MAX_PATH_BYTES = max_path_bytes; /// * On other platforms, `[]u8` file paths are opaque sequences of bytes with /// no particular encoding. pub const max_path_bytes = switch (native_os) { - .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten, .wasi => posix.PATH_MAX, + .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .haiku, .solaris, .illumos, .plan9, .emscripten, .wasi, .uefi => posix.PATH_MAX, // Each WTF-16LE code unit may be expanded to 3 WTF-8 bytes. // If it would require 4 WTF-8 bytes, then there would be a surrogate // pair in the WTF-16LE, and we (over)account 3 bytes for it that way. // +1 for the null byte at the end, which can be encoded in 1 byte. - .windows, .uefi => windows.PATH_MAX_WIDE * 3 + 1, + .windows => windows.PATH_MAX_WIDE * 3 + 1, else => if (@hasDecl(root, "os") and @hasDecl(root.os, "PATH_MAX")) root.os.PATH_MAX else @@ -695,10 +695,10 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 { return error.FileNotFound; } else return error.FileNotFound; - if (file_path.len > windows.PATH_MAX_WIDE) return error.NameTooLong; + if (file_path.len > uefi.PATH_MAX_WIDE) return error.NameTooLong; // required because device paths are not aligned - var alignment_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; + var alignment_buffer: [uefi.PATH_MAX_WIDE]u16 = undefined; @memcpy(alignment_buffer[0..file_path.len], file_path); const len = std.unicode.wtf16LeToWtf8(out_buffer, alignment_buffer[0..file_path.len]); diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index f055494bb0fb..e914dc15fc6c 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -459,35 +459,6 @@ pub fn stat(self: File) StatError!Stat { .ctime = windows.fromSysTime(info.BasicInformation.ChangeTime), }; } - if (builtin.os.tag == .uefi) { - if (self.handle != .file) - return error.Unexpected; // TODO - - const file: *const std.os.uefi.protocol.File = self.handle.file; - - var pool_allocator = std.os.uefi.PoolAllocator{}; - - const buffer_size = file.getInfoSize(std.os.uefi.bits.FileInfo) catch return error.Unexpected; - const buffer = pool_allocator.allocator().alignedAlloc( - u8, - @alignOf(std.os.uefi.bits.FileInfo), - buffer_size, - ) catch return error.SystemResources; - defer pool_allocator.allocator().free(buffer); - - const info = file.getInfo(std.os.uefi.bits.FileInfo, buffer) catch return error.Unexpected; - - return .{ - .inode = 0, - .size = info.file_size, - .mode = 0, - .kind = .file, - .atime = @bitCast(info.last_access_time.toUnixEpochNanoseconds()), - .mtime = @bitCast(info.modification_time.toUnixEpochNanoseconds()), - .ctime = @bitCast(info.create_time.toUnixEpochNanoseconds()), - }; - } - if (builtin.os.tag == .wasi and !builtin.link_libc) { const st = try std.os.fstat_wasi(self.handle); return Stat.fromWasi(st); diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 4e62abad6365..363b5b0f042c 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -25,27 +25,24 @@ pub var system_table: *table.System = undefined; pub var working_directory: fd_t = .none; -pub const ino_t = u64; -pub const mode_t = u64; - -pub const fd_t = union(enum) { - file: *const protocol.File, - simple_output: *const protocol.SimpleTextOutput, - simple_input: *const protocol.SimpleTextInput, - none: void, // used to refer to a file descriptor that is not open and cannot do anything - cwd: void, // used to refer to the current working directory -}; - -fn unexpectedError(err: anyerror) error{Unexpected} { - std.log.err("unexpected error: {}\n", .{err}); - return error.Unexpected; -} +pub const posix = @import("uefi/posix.zig"); -pub fn cwd() fd_t { - const uefi = std.os.uefi; +pub const ino_t = posix.ino_t; +pub const mode_t = posix.mode_t; + +pub const fd_t = posix.fd_t; +pub const timespec = posix.timespec; +pub const Stat = posix.Stat; + +pub const PATH_MAX = posix.PATH_MAX; +pub const PATH_MAX_WIDE = posix.PATH_MAX_WIDE; +pub const O = posix.O; +pub const AT = posix.AT; +pub const S = posix.S; - if (uefi.system_table.boot_services) |boot_services| blk: { - const loaded_image = boot_services.openProtocol(uefi.handle, uefi.protocol.LoadedImage, .{}) catch break :blk; +pub fn cwd() fd_t { + if (system_table.boot_services) |boot_services| blk: { + const loaded_image = boot_services.openProtocol(handle, protocol.LoadedImage, .{}) catch break :blk; const file_path = if (loaded_image.file_path.node()) |node| file_path: { if (node == .media and node.media == .file_path) @@ -54,17 +51,17 @@ pub fn cwd() fd_t { break :blk; } else break :blk; - if (file_path.len + 4 > std.fs.max_path_bytes) break :blk; + if (file_path.len + 4 > posix.PATH_MAX) break :blk; // required because device paths are not aligned - var path_buffer: [std.fs.max_path_bytes]u16 = undefined; + var path_buffer: [posix.PATH_MAX]u16 = undefined; @memcpy(path_buffer[0..file_path.len], file_path); path_buffer[file_path.len] = '\\'; path_buffer[file_path.len + 1] = '.'; path_buffer[file_path.len + 2] = '.'; path_buffer[file_path.len + 3] = 0; - const file_system = boot_services.openProtocol(loaded_image.device_handle.?, uefi.protocol.SimpleFileSystem, .{}) catch break :blk; + const file_system = boot_services.openProtocol(loaded_image.device_handle.?, protocol.SimpleFileSystem, .{}) catch break :blk; const volume = file_system.openVolume() catch break :blk; return .{ .file = volume.open(path_buffer[0 .. file_path.len + 3 :0], .{}, .{}) catch break :blk }; @@ -73,181 +70,6 @@ pub fn cwd() fd_t { return .none; } -pub fn close(fd: fd_t) void { - switch (fd) { - .file => |p| p.close(), - .simple_output => |p| p.reset(true) catch {}, - .simple_input => |p| p.reset(true) catch {}, - .none => {}, - .cwd => {}, - } -} - -pub fn openat(dirfd: fd_t, path: [:0]const u16, flags: protocol.File.OpenMode) !fd_t { - switch (dirfd) { - .file => |p| { - const fd = p.open(path, flags, .{}) catch |err| switch (err) { - error.NotFound => return error.FileNotFound, - error.NoMedia => return error.NoDevice, - error.MediaChanged => return error.NoDevice, - error.DeviceError => return error.NoDevice, - error.VolumeCorrupted => return error.NoDevice, - error.WriteProtected => return error.AccessDenied, - error.AccessDenied => return error.AccessDenied, - error.OutOfResources => return error.SystemResources, - error.InvalidParameter => return error.FileNotFound, - else => |e| return unexpectedError(e), - }; - return .{ .file = fd }; - }, - .simple_output => return error.NotDir, - .simple_input => return error.NotDir, - .none => return error.NotDir, - .cwd => return openat(working_directory, path, flags), - } -} - -pub fn read(fd: fd_t, buf: []u8) std.posix.ReadError!usize { - switch (fd) { - .file => |p| { - return p.read(buf) catch |err| switch (err) { - error.NoMedia => return error.InputOutput, - error.DeviceError => return error.InputOutput, - error.VolumeCorrupted => return error.InputOutput, - else => |e| return unexpectedError(e), - }; - }, - .simple_input => |p| { - var index: usize = 0; - while (index == 0) { - while (p.readKeyStroke() catch |err| switch (err) { - error.DeviceError => return error.InputOutput, - else => |e| return unexpectedError(e), - }) |key| { - if (key.unicode_char != 0) { - // this definitely isn't the right way to handle this, and it may fail on towards the limit of a single utf16 item. - index += std.unicode.utf16leToUtf8(buf, &.{key.unicode_char}) catch continue; - } - } - } - return @intCast(index); - }, - else => return error.NotOpenForReading, // cannot read - } -} - -pub fn write(fd: fd_t, buf: []const u8) std.posix.WriteError!usize { - switch (fd) { - .file => |p| { - return p.write(buf) catch |err| switch (err) { - error.Unsupported => return error.NotOpenForWriting, - error.NoMedia => return error.InputOutput, - error.DeviceError => return error.InputOutput, - error.VolumeCorrupted => return error.InputOutput, - error.WriteProtected => return error.NotOpenForWriting, - error.AccessDenied => return error.AccessDenied, - else => |e| return unexpectedError(e), - }; - }, - .simple_output => |p| { - const view = std.unicode.Utf8View.init(buf) catch unreachable; - var iter = view.iterator(); - - // rudimentary utf16 writer - var index: usize = 0; - var utf16: [256]u16 = undefined; - while (iter.nextCodepoint()) |rune| { - if (index + 1 >= utf16.len) { - utf16[index] = 0; - p.outputString(utf16[0..index :0]) catch |err| switch (err) { - error.DeviceError => return error.InputOutput, - error.Unsupported => return error.NotOpenForWriting, - else => return error.Unexpected, - }; - index = 0; - } - - if (rune < 0x10000) { - if (rune == '\n') { - utf16[index] = '\r'; - index += 1; - } - - utf16[index] = @intCast(rune); - index += 1; - } else { - const high = @as(u16, @intCast((rune - 0x10000) >> 10)) + 0xD800; - const low = @as(u16, @intCast(rune & 0x3FF)) + 0xDC00; - switch (builtin.cpu.arch.endian()) { - .little => { - utf16[index] = high; - utf16[index] = low; - }, - .big => { - utf16[index] = low; - utf16[index] = high; - }, - } - index += 2; - } - } - - if (index != 0) { - utf16[index] = 0; - p.outputString(utf16[0..index :0]) catch |err| switch (err) { - error.DeviceError => return error.InputOutput, - error.Unsupported => return error.NotOpenForWriting, - else => return error.Unexpected, - }; - } - - return @intCast(buf.len); - }, - else => return error.NotOpenForWriting, // cannot write - } -} - -pub fn getFileEndPosition(fd: fd_t) !u64 { - switch (fd) { - .file => |p| { - return p.getEndPosition() catch return error.Unseekable; - }, - else => return error.Unseekable, // cannot read - } -} - -pub fn getFilePosition(fd: fd_t) !u64 { - switch (fd) { - .file => |p| { - return p.getPosition() catch return error.Unseekable; - }, - else => return error.Unseekable, // cannot read - } -} - -pub fn setFilePosition(fd: fd_t, pos: u64) !void { - switch (fd) { - .file => |p| { - return p.setPosition(pos) catch return error.Unseekable; - }, - else => return error.Unseekable, // cannot read - } -} - -pub const PATH_MAX = 8192; - -pub const O = packed struct { - ACCMODE: std.posix.ACCMODE = .RDONLY, - NONBLOCK: bool = false, - CLOEXEC: bool = false, - CREAT: bool = false, - TRUNC: bool = false, -}; - -pub const AT = struct { - pub const FDCWD: fd_t = .cwd; -}; - test { _ = table; _ = protocol; diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig index 3f48d32b44bd..b371a0337210 100644 --- a/lib/std/os/uefi/bits.zig +++ b/lib/std/os/uefi/bits.zig @@ -108,13 +108,18 @@ pub const Time = extern struct { self.second; } + /// Returns the time in seconds since 1970-01-01 00:00:00. + pub fn toUnixEpochSeconds(self: Time) u64 { + return self.toEpochSeconds() -| unix_epoch_seconds; + } + /// Returns the time in nanoseconds since 1900-01-01 00:00:00. - pub fn toEpochNanoseconds(self: Time) i128 { - return @as(i128, self.toEpochSeconds()) * time.ns_per_s + self.nanosecond; + pub fn toEpochNanoseconds(self: Time) u128 { + return @as(u128, self.toEpochSeconds()) * time.ns_per_s + self.nanosecond; } /// Returns the time in nanoseconds since 1970-01-01 00:00:00. - pub fn toUnixEpochNanoseconds(self: Time) i128 { + pub fn toUnixEpochNanoseconds(self: Time) u128 { return self.toEpochNanoseconds() -| unix_epoch_nanoseconds; } @@ -131,6 +136,7 @@ pub const Time = extern struct { }; pub const unix_epoch_nanoseconds = unix_epoch.toEpochNanoseconds(); + pub const unix_epoch_seconds = unix_epoch.toEpochSeconds(); }; /// Capabilities of the clock device diff --git a/lib/std/os/uefi/posix.zig b/lib/std/os/uefi/posix.zig new file mode 100644 index 000000000000..78363b874f92 --- /dev/null +++ b/lib/std/os/uefi/posix.zig @@ -0,0 +1,491 @@ +const builtin = @import("builtin"); +const std = @import("../../std.zig"); +const uefi = @import("../uefi.zig"); + +pub const ino_t = u32; +pub const dev_t = u32; +pub const mode_t = u64; +pub const off_t = u64; +pub const blksize_t = u64; +pub const blkcnt_t = u64; + +pub const fd_t = union(enum) { + file: *const uefi.protocol.File, + simple_output: *const uefi.protocol.SimpleTextOutput, + simple_input: *const uefi.protocol.SimpleTextInput, + none: void, // used to refer to a file descriptor that is not open and cannot do anything + cwd: void, // used to refer to the current working directory +}; + +pub const PATH_MAX_WIDE = 4096; +pub const PATH_MAX = PATH_MAX_WIDE * 3 + 1; + +pub const O = packed struct { + ACCMODE: std.posix.ACCMODE = .RDONLY, + NONBLOCK: bool = false, + CLOEXEC: bool = false, + CREAT: bool = false, + TRUNC: bool = false, +}; + +pub const timespec = struct { + tv_sec: u64, + tv_nsec: u64, +}; + +pub const AT = struct { + pub const FDCWD: fd_t = .cwd; +}; + +pub const CLOCK = struct { + pub const REALTIME = 0; +}; + +pub const S = struct { + pub const IFMT = 0o170000; + + pub const IFDIR = 0o040000; + pub const IFCHR = 0o020000; + pub const IFBLK = 0o060000; + pub const IFREG = 0o100000; + pub const IFIFO = 0o010000; + pub const IFLNK = 0o120000; + pub const IFSOCK = 0o140000; +}; + +pub const Stat = struct { + ino: ino_t, + mode: mode_t, + size: off_t, + atim: timespec, + mtim: timespec, + ctim: timespec, + + pub fn atime(self: @This()) timespec { + return self.atim; + } + + pub fn mtime(self: @This()) timespec { + return self.mtim; + } + + pub fn ctime(self: @This()) timespec { + return self.ctim; + } +}; + +fn unexpectedError(err: anyerror) error{Unexpected} { + std.log.err("unexpected error: {}\n", .{err}); + return error.Unexpected; +} + +pub fn chdir(dir_path: []const u8) std.posix.ChangeCurDirError!void { + var path_buffer: [PATH_MAX]u16 = undefined; + const len = try std.unicode.wtf8ToWtf16Le(&path_buffer, dir_path); + path_buffer[len] = 0; + + const fd = openat(.cwd, path_buffer[0..len :0], .{}) catch |err| switch (err) { + error.NotFound => return error.FileNotFound, + error.NoMedia => return error.InputOutput, + error.MediaChanged => return error.InputOutput, + error.DeviceError => return error.InputOutput, + error.VolumeCorrupted => return error.InputOutput, + error.AccessDenied => return error.AccessDenied, + error.OutOfResources => return error.SystemResources, + else => |e| return unexpectedError(e), + }; + + try fchdir(fd); +} + +pub fn clock_getres(clk_id: i32, res: *uefi.timespec) std.posix.ClockGetTimeError!void { + if (clk_id != CLOCK.REALTIME) + return error.UnsupportedClock; + + const capabilities = uefi.system_table.runtime_services.getTimeCapabilities() catch return error.UnsupportedClock; + + if (capabilities.resolution == 0) + return error.UnsupportedClock; + + res.tv_sec = 1 / capabilities.resolution; + res.tv_nsec = (std.time.ns_per_s / capabilities.resolution) % std.time.ns_per_s; +} + +pub fn clock_gettime(clk_id: i32, tp: *uefi.timespec) std.posix.ClockGetTimeError!void { + if (clk_id != CLOCK.REALTIME) + return error.UnsupportedClock; + + const time = uefi.system_table.runtime_services.getTime() catch return error.UnsupportedClock; + + const unix_ns = time.toUnixEpochNanoseconds(); + tp.tv_sec = @intCast(unix_ns / std.time.ns_per_s); + tp.tv_nsec = @intCast(unix_ns % std.time.ns_per_s); +} + +pub fn close(fd: fd_t) void { + switch (fd) { + .file => |p| p.close(), + .simple_output => |p| p.reset(true) catch {}, + .simple_input => |p| p.reset(true) catch {}, + .none => {}, + .cwd => {}, + } +} + +pub fn exit(status: u8) noreturn { + if (uefi.system_table.boot_services) |bs| { + bs.exit(uefi.handle, @enumFromInt(status), 0, null) catch {}; + } + + uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), 0, null); +} + +pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) std.posix.AccessError!void { + switch (dirfd) { + .file => |p| { + var path_buffer: [PATH_MAX]u16 = undefined; + const len = try std.unicode.wtf8ToWtf16Le(&path_buffer, path); + path_buffer[len] = 0; + + const fd = p.open(path_buffer[0..len :0], .{}, .{}) catch |err| switch (err) { + error.NotFound => return error.FileNotFound, + error.NoMedia => return error.InputOutput, + error.MediaChanged => return error.InputOutput, + error.DeviceError => return error.InputOutput, + error.VolumeCorrupted => return error.InputOutput, + error.AccessDenied => return error.PermissionDenied, + error.OutOfResources => return error.SystemResources, + else => |e| return unexpectedError(e), + }; + + fd.close(); + }, + .cwd => return faccessat(uefi.working_directory, path, mode, flags), + else => return error.FileNotFound, + } +} + +pub fn fchdir(fd: fd_t) std.posix.ChangeCurDirError!void { + switch (fd) { + .file => { + close(uefi.working_directory); + uefi.working_directory = fd; + }, + .simple_output => return error.NotDir, + .simple_input => return error.NotDir, + .none => return error.NotDir, + .cwd => {}, + } +} + +pub fn fstat(fd: fd_t) std.posix.FStatError!Stat { + switch (fd) { + .file => |p| { + var pool_allocator = std.os.uefi.PoolAllocator{}; + + const buffer_size = p.getInfoSize(std.os.uefi.bits.FileInfo) catch return error.Unexpected; + const buffer = pool_allocator.allocator().alignedAlloc( + u8, + @alignOf(std.os.uefi.bits.FileInfo), + buffer_size, + ) catch return error.SystemResources; + defer pool_allocator.allocator().free(buffer); + + const info = p.getInfo(std.os.uefi.bits.FileInfo, buffer) catch return error.Unexpected; + + return .{ + .ino = 0, + .mode = if (info.attribute.directory) S.IFDIR else S.IFREG, + .size = info.file_size, + .atim = timespec{ .tv_sec = info.last_access_time.toUnixEpochSeconds(), .tv_nsec = info.last_access_time.nanosecond }, + .mtim = timespec{ .tv_sec = info.modification_time.toUnixEpochSeconds(), .tv_nsec = info.modification_time.nanosecond }, + .ctim = timespec{ .tv_sec = info.create_time.toUnixEpochSeconds(), .tv_nsec = info.create_time.nanosecond }, + }; + }, + .simple_input, .simple_output => return Stat{ + .ino = 0, + .mode = S.IFCHR, + .size = 0, + .atim = timespec{ .tv_sec = 0, .tv_nsec = 0 }, + .mtim = timespec{ .tv_sec = 0, .tv_nsec = 0 }, + .ctim = timespec{ .tv_sec = 0, .tv_nsec = 0 }, + }, + .none => return error.AccessDenied, + .cwd => return fstat(uefi.working_directory), + } +} + +pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) std.posix.FStatAtError!Stat { + _ = flags; + + const fd = try openat(dirfd, pathname, .{}, 0); + defer close(fd); + + return try fstat(fd); +} + +pub fn fsync(fd: fd_t) std.posix.SyncError!void { + switch (fd) { + .file => |p| p.flush() catch return error.InputOutput, + else => return error.NoSpaceLeft, + } +} + +pub fn ftruncate(fd: fd_t, length: u64) std.posix.TruncateError!void { + if (fd != .file) + return error.AccessDenied; + + const p = fd.file; + + var pool_allocator = std.os.uefi.PoolAllocator{}; + + const buffer_size = p.getInfoSize(std.os.uefi.bits.FileInfo) catch return error.Unexpected; + const buffer = pool_allocator.allocator().alignedAlloc( + u8, + @alignOf(std.os.uefi.bits.FileInfo), + buffer_size, + ) catch return error.SystemResources; + defer pool_allocator.allocator().free(buffer); + + var info = p.getInfo(std.os.uefi.bits.FileInfo, buffer) catch return error.Unexpected; + + info.file_size = length; + + p.setInfo(std.os.uefi.bits.FileInfo, buffer[0..buffer_size]) catch return error.AccessDenied; +} + +// TODO: futimens + +pub fn getcwd(out_buffer: []u8) std.posix.GetCwdError![]u8 { + const fd = uefi.working_directory; + if (fd == .none) + return error.NoDevice; + + var buffer: [PATH_MAX]u8 = undefined; + const path = std.os.getFdPath(fd, &buffer); + if (path.len > out_buffer.len) + return error.NameTooLong; + + @memcpy(out_buffer[0..path.len], out_buffer); +} + +pub fn getrandom(buf: []u8) std.posix.GetRandomError!usize { + if (uefi.system_table.boot_services) |boot_services| { + const rng = (boot_services.locateProtocol(uefi.protocol.Rng, .{}) catch return error.NoDevice) orelse return error.NoDevice; + + while (true) { + rng.getRNG(null, buf.len, buf.ptr) catch |err| switch (err) { + error.NotReady => continue, + else => return error.FileNotFound, + }; + + break; + } + + return buf.len; + } else { + return 0; + } +} + +pub fn isatty(fd: fd_t) u8 { + switch (fd) { + .simple_input, .simple_output => 1, + else => 0, + } +} + +pub fn lseek_SET(fd: fd_t, pos: u64) std.posix.SeekError!void { + switch (fd) { + .file => |p| { + return p.setPosition(pos) catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +pub fn lseek_CUR(fd: fd_t, offset: i64) std.posix.SeekError!u64 { + switch (fd) { + .file => |p| { + const end = p.getEndPosition() catch return error.Unseekable; + const pos = p.getPosition() catch return error.Unseekable; + const new_pos = @as(i64, @intCast(pos)) + offset; + + var abs_pos = 0; + if (new_pos > end) + abs_pos = uefi.protocol.File.position_end_of_file + else if (new_pos > 0) + abs_pos = @intCast(new_pos); + + return p.setPosition(abs_pos) catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +pub fn lseek_END(fd: fd_t, offset: i64) std.posix.SeekError!u64 { + switch (fd) { + .file => |p| { + const end = p.getEndPosition() catch return error.Unseekable; + const new_pos = @as(i64, @intCast(end)) + offset; + + var abs_pos = 0; + if (new_pos > end) + abs_pos = uefi.protocol.File.position_end_of_file + else if (new_pos > 0) + abs_pos = @intCast(new_pos); + + return p.setPosition(abs_pos) catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +pub fn lseek_CUR_get(fd: fd_t) std.posix.SeekError!u64 { + switch (fd) { + .file => |p| { + return p.getPosition() catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) std.posix.OpenError!fd_t { + switch (dir_fd) { + .file => |p| { + var path_buffer: [PATH_MAX]u16 = undefined; + const len = try std.unicode.wtf8ToWtf16Le(&path_buffer, file_path); + path_buffer[len] = 0; + + const fd = p.open(path_buffer[0..len :0], .{ + .read = true, + .write = flags.CREAT or flags.TRUNC or flags.ACCMODE != .RDONLY, + .create = flags.CREAT, + }, .{}) catch |err| switch (err) { + error.NotFound => return error.FileNotFound, + error.NoMedia => return error.NoDevice, + error.MediaChanged => return error.NoDevice, + error.DeviceError => return error.NoDevice, + error.VolumeCorrupted => return error.NoDevice, + error.WriteProtected => return error.AccessDenied, + error.AccessDenied => return error.AccessDenied, + error.OutOfResources => return error.SystemResources, + error.InvalidParameter => return error.FileNotFound, + else => |e| return unexpectedError(e), + }; + + return .{ .file = fd }; + }, + .simple_output => return error.NotDir, + .simple_input => return error.NotDir, + .none => return error.NotDir, + .cwd => return openat(uefi.working_directory, file_path, flags, mode), + } +} + +pub fn read(fd: fd_t, buf: []u8) std.posix.ReadError!usize { + switch (fd) { + .file => |p| { + return p.read(buf) catch |err| switch (err) { + error.NoMedia => return error.InputOutput, + error.DeviceError => return error.InputOutput, + error.VolumeCorrupted => return error.InputOutput, + else => |e| return unexpectedError(e), + }; + }, + .simple_input => |p| { + var index: usize = 0; + while (index == 0) { + while (p.readKeyStroke() catch |err| switch (err) { + error.DeviceError => return error.InputOutput, + else => |e| return unexpectedError(e), + }) |key| { + if (key.unicode_char != 0) { + // this definitely isn't the right way to handle this, and it may fail on towards the limit of a single utf16 item. + index += std.unicode.utf16leToUtf8(buf, &.{key.unicode_char}) catch continue; + } + } + } + return @intCast(index); + }, + else => return error.NotOpenForReading, // cannot read + } +} + +pub fn realpath(pathname: []const u8, out_buffer: *[PATH_MAX]u8) std.posix.RealPathError![]u8 { + const fd = try openat(.cwd, pathname, .{}, 0); + defer close(fd); + + return std.os.getFdPath(fd, out_buffer); +} + +pub fn write(fd: fd_t, buf: []const u8) std.posix.WriteError!usize { + switch (fd) { + .file => |p| { + return p.write(buf) catch |err| switch (err) { + error.Unsupported => return error.NotOpenForWriting, + error.NoMedia => return error.InputOutput, + error.DeviceError => return error.InputOutput, + error.VolumeCorrupted => return error.InputOutput, + error.WriteProtected => return error.NotOpenForWriting, + error.AccessDenied => return error.AccessDenied, + else => |e| return unexpectedError(e), + }; + }, + .simple_output => |p| { + const view = std.unicode.Utf8View.init(buf) catch unreachable; + var iter = view.iterator(); + + // rudimentary utf16 writer + var index: usize = 0; + var utf16: [256]u16 = undefined; + while (iter.nextCodepoint()) |rune| { + if (index + 1 >= utf16.len) { + utf16[index] = 0; + p.outputString(utf16[0..index :0]) catch |err| switch (err) { + error.DeviceError => return error.InputOutput, + error.Unsupported => return error.NotOpenForWriting, + else => return error.Unexpected, + }; + index = 0; + } + + if (rune < 0x10000) { + if (rune == '\n') { + utf16[index] = '\r'; + index += 1; + } + + utf16[index] = @intCast(rune); + index += 1; + } else { + const high = @as(u16, @intCast((rune - 0x10000) >> 10)) + 0xD800; + const low = @as(u16, @intCast(rune & 0x3FF)) + 0xDC00; + switch (builtin.cpu.arch.endian()) { + .little => { + utf16[index] = high; + utf16[index] = low; + }, + .big => { + utf16[index] = low; + utf16[index] = high; + }, + } + index += 2; + } + } + + if (index != 0) { + utf16[index] = 0; + p.outputString(utf16[0..index :0]) catch |err| switch (err) { + error.DeviceError => return error.InputOutput, + error.Unsupported => return error.NotOpenForWriting, + else => return error.Unexpected, + }; + } + + return @intCast(buf.len); + }, + else => return error.NotOpenForWriting, // cannot write + } +} diff --git a/lib/std/os/uefi/protocol/media/file.zig b/lib/std/os/uefi/protocol/media/file.zig index 1787e72b7013..5e9838f38539 100644 --- a/lib/std/os/uefi/protocol/media/file.zig +++ b/lib/std/os/uefi/protocol/media/file.zig @@ -169,16 +169,9 @@ pub const File = extern struct { pub fn setInfo( self: *const File, comptime Information: type, - info: *const Information, + buffer: []align(@alignOf(Information)) u8, ) !void { - const size: usize = switch (Information) { - bits.FileInfo => @intCast(info.size), - bits.FileSystemInfo => @intCast(info.size), - bits.FileSystemVolumeLabel => 2 * info.getVolumeLabel().len + 2, - else => return error.InvalidParameter, - }; - - try self._set_info(self, Information.guid, size, @ptrCast(info)).err(); + try self._set_info(self, Information.guid, buffer.len, buffer.ptr).err(); } /// Flushes all modified data associated with a file to a device. diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 612c42a5103a..a64fdcb1af3e 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -236,22 +236,19 @@ pub fn errno(rc: anytype) E { pub fn close(fd: fd_t) void { if (native_os == .windows) { return windows.CloseHandle(fd); - } - if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc) { _ = std.os.wasi.fd_close(fd); return; - } - if (native_os == .uefi) { - uefi.close(fd); - return; - } - if (builtin.target.isDarwin()) { + } else if (native_os == .uefi) { + return uefi.posix.close(fd); + } else if (builtin.target.isDarwin()) { // This avoids the EINTR problem. switch (errno(std.c.@"close$NOCANCEL"(fd))) { .BADF => unreachable, // Always a race condition. else => return, } } + switch (errno(system.close(fd))) { .BADF => unreachable, // Always a race condition. .INTR => return, // This is still a success. See https://github.com/ziglang/zig/issues/2425 @@ -489,7 +486,7 @@ pub const FChownError = error{ /// specified as `null`, the ID is not changed. pub fn fchown(fd: fd_t, owner: ?uid_t, group: ?gid_t) FChownError!void { switch (native_os) { - .windows, .wasi => @compileError("Unsupported OS"), + .windows, .wasi, .uefi => @compileError("Unsupported OS"), else => {}, } @@ -576,7 +573,10 @@ pub const GetRandomError = OpenError; pub fn getrandom(buffer: []u8) GetRandomError!void { if (native_os == .windows) { return windows.RtlGenRandom(buffer); + } else if (native_os == .uefi) { + return uefi.posix.getrandom(buffer); } + if (native_os == .linux or native_os == .freebsd) { var buf = buffer; const use_c = native_os != .linux or @@ -753,13 +753,7 @@ pub fn exit(status: u8) noreturn { linux.exit_group(status); } if (native_os == .uefi) { - // exit() is only available if exitBootServices() has not been called yet. - // This call to exit should not fail, so we don't care about its return value. - if (uefi.system_table.boot_services) |bs| { - bs.exit(uefi.handle, @enumFromInt(status), 0, null) catch {}; - } - // If we can't exit, reboot the system instead. - uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), 0, null); + uefi.posix.exit(status); } system.exit(status); } @@ -800,7 +794,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { return windows.ReadFile(fd, buf, null); } if (builtin.os.tag == .uefi) { - return uefi.read(fd, buf); + return uefi.posix.read(fd, buf); } if (native_os == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ @@ -941,8 +935,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { if (native_os == .uefi) { const pos = lseek_CUR_get(fd) catch return error.Unseekable; lseek_SET(fd, offset) catch return error.Unseekable; - try read(fd, buf); + const nread = try read(fd, buf); lseek_SET(fd, pos) catch return error.Unseekable; + return nread; } if (native_os == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ @@ -1051,6 +1046,9 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { else => |err| return unexpectedErrno(err), } } + if (native_os == .uefi) { + return uefi.posix.ftruncate(fd, length); + } const ftruncate_sym = if (lfs64_abi) system.ftruncate64 else system.ftruncate; while (true) { @@ -1198,7 +1196,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { return windows.WriteFile(fd, bytes, null); } if (builtin.os.tag == .uefi) { - return uefi.write(fd, bytes); + return uefi.posix.write(fd, bytes); } if (native_os == .wasi and !builtin.link_libc) { @@ -1358,8 +1356,9 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { if (native_os == .uefi) { const pos = lseek_CUR_get(fd) catch return error.Unseekable; lseek_SET(fd, offset) catch return error.Unseekable; - try write(fd, bytes); + const written = try write(fd, bytes); lseek_SET(fd, pos) catch return error.Unseekable; + return written; } if (native_os == .wasi and !builtin.link_libc) { const ciovs = [1]iovec_const{iovec_const{ @@ -1661,15 +1660,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE return fd; } else if (native_os == .uefi) { - var temp_path: std.os.windows.PathSpace = undefined; - temp_path.len = try std.unicode.utf8ToUtf16Le(&temp_path.data, file_path); - temp_path.data[temp_path.len] = 0; - - return try uefi.openat(dir_fd, temp_path.span(), .{ - .read = true, // must be specified when reading or writing, which is always the case - .write = flags.ACCMODE != .RDONLY or flags.CREAT, - .create = flags.CREAT, - }); + return uefi.posix.openat(dir_fd, file_path, flags, mode); } const file_path_c = try toPosixPath(file_path); return openatZ(dir_fd, &file_path_c, flags, mode); @@ -3139,6 +3130,8 @@ pub fn chdir(dir_path: []const u8) ChangeCurDirError!void { const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], dir_path); if (len > wtf16_dir_path.len) return error.NameTooLong; return chdirW(wtf16_dir_path[0..len]); + } else if (native_os == .uefi) { + return uefi.posix.chdir(dir_path); } else { const dir_path_c = try toPosixPath(dir_path); return chdirZ(&dir_path_c); @@ -3155,8 +3148,8 @@ pub fn chdirZ(dir_path: [*:0]const u8) ChangeCurDirError!void { const len = try std.unicode.wtf8ToWtf16Le(wtf16_dir_path[0..], mem.span(dir_path)); if (len > wtf16_dir_path.len) return error.NameTooLong; return chdirW(wtf16_dir_path[0..len]); - } else if (native_os == .wasi and !builtin.link_libc) { - return chdir(mem.span(dir_path)); + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { + return chdir(mem.sliceTo(dir_path, 0)); } switch (errno(system.chdir(dir_path))) { .SUCCESS => return, @@ -4316,6 +4309,9 @@ pub fn fstat(fd: fd_t) FStatError!Stat { if (native_os == .windows) { @compileError("fstat is not yet implemented on Windows"); } + if (native_os == .uefi) { + return uefi.posix.fstat(fd); + } const fstat_sym = if (lfs64_abi) system.fstat64 else system.fstat; var stat = mem.zeroes(Stat); @@ -4761,7 +4757,7 @@ pub fn access(path: []const u8, mode: u32) AccessError!void { }; _ = try windows.GetFileAttributesW(path_w.span().ptr); return; - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return faccessat(wasi.AT.FDCWD, path, mode, 0); } const path_c = try toPosixPath(path); @@ -4777,7 +4773,7 @@ pub fn accessZ(path: [*:0]const u8, mode: u32) AccessError!void { }; _ = try windows.GetFileAttributesW(path_w.span().ptr); return; - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return access(mem.sliceTo(path, 0), mode); } switch (errno(system.access(path, mode))) { @@ -4852,6 +4848,8 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) AccessErr } } return; + } else if (native_os == .uefi) { + return uefi.posix.faccessat(dirfd, path, mode, flags); } const path_c = try toPosixPath(path); return faccessatZ(dirfd, &path_c, mode, flags); @@ -5088,7 +5086,7 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { return windows.SetFilePointerEx_BEGIN(fd, offset); } if (native_os == .uefi) { - return uefi.setFilePosition(fd, offset); + return uefi.posix.lseek_SET(fd, offset); } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; @@ -5134,16 +5132,7 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { return windows.SetFilePointerEx_CURRENT(fd, offset); } if (native_os == .uefi) { - var pos: i64 = @intCast(try uefi.getFilePosition(fd)); - const end = try uefi.getFileEndPosition(fd); - if (pos + offset < 0) { - pos = 0; - } else if (pos + offset > end) { - pos = end; - } else { - pos += offset; - } - return uefi.setFilePosition(@intCast(pos)); + return uefi.posix.lseek_CUR(fd, offset); } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; @@ -5188,8 +5177,7 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { return windows.SetFilePointerEx_END(fd, offset); } if (native_os == .uefi) { - const end = try uefi.getFileEndPosition(fd); - return uefi.setFilePosition(end); + return uefi.posix.lseek_END(fd, offset); } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; @@ -5234,7 +5222,7 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { return windows.SetFilePointerEx_CURRENT_get(fd); } if (native_os == .uefi) { - return uefi.getFilePosition(fd); + return uefi.lseek_CUR_get(fd); } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; @@ -5385,6 +5373,8 @@ pub fn realpath(pathname: []const u8, out_buffer: *[max_path_bytes]u8) RealPathE return realpathW(pathname_w.span(), out_buffer); } else if (native_os == .wasi and !builtin.link_libc) { @compileError("WASI does not support os.realpath"); + } else if (native_os == .uefi) { + return uefi.posix.realpath(pathname, out_buffer); } const pathname_c = try toPosixPath(pathname); return realpathZ(&pathname_c, out_buffer); @@ -5397,7 +5387,7 @@ pub fn realpathZ(pathname: [*:0]const u8, out_buffer: *[max_path_bytes]u8) RealP if (native_os == .windows) { const pathname_w = try windows.cStrToPrefixedFileW(null, pathname); return realpathW(pathname_w.span(), out_buffer); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return realpath(mem.sliceTo(pathname, 0), out_buffer); } if (!builtin.link_libc) { @@ -5608,8 +5598,7 @@ pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { else => |err| return unexpectedErrno(err), } return; - } - if (native_os == .windows) { + } else if (native_os == .windows) { if (clk_id == CLOCK.REALTIME) { var ft: windows.FILETIME = undefined; windows.kernel32.GetSystemTimeAsFileTime(&ft); @@ -5625,6 +5614,8 @@ pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { // TODO POSIX implementation of CLOCK.MONOTONIC on Windows. return error.UnsupportedClock; } + } else if (native_os == .uefi) { + return uefi.posix.clock_gettime(clk_id, tp); } switch (errno(system.clock_gettime(clk_id, tp))) { @@ -5647,6 +5638,8 @@ pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void { else => |err| return unexpectedErrno(err), } return; + } else if (native_os == .uefi) { + return uefi.posix.clock_getres(clk_id, res); } switch (errno(system.clock_getres(clk_id, res))) { @@ -6961,6 +6954,8 @@ pub fn fsync(fd: fd_t) SyncError!void { .UNEXP_NET_ERR => return error.InputOutput, else => return error.InputOutput, } + } else if (native_os == .uefi) { + return uefi.posix.fsync(fd); } const rc = system.fsync(fd); switch (errno(rc)) { @@ -6975,7 +6970,7 @@ pub fn fsync(fd: fd_t) SyncError!void { /// Write all pending file contents for the specified file descriptor to the underlying filesystem, but not necessarily the metadata. pub fn fdatasync(fd: fd_t) SyncError!void { - if (native_os == .windows) { + if (native_os == .windows or native_os == .uefi) { return fsync(fd) catch |err| switch (err) { SyncError.AccessDenied => return, // fdatasync doesn't promise that the access time was synced else => return err, diff --git a/lib/std/time.zig b/lib/std/time.zig index c6364b706c70..f65d298d6728 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -99,10 +99,6 @@ pub fn nanoTimestamp() i128 { assert(err == .SUCCESS); return ns; }, - .uefi => { - const value = std.os.uefi.system_table.runtime_services.getTime() catch return 0; - return value.toUnixEpochNanoseconds(); - }, else => { var ts: posix.timespec = undefined; posix.clock_gettime(posix.CLOCK.REALTIME, &ts) catch |err| switch (err) { @@ -187,10 +183,8 @@ pub const Instant = struct { if (rc != .SUCCESS) return error.Unsupported; return .{ .timestamp = ns }; }, - .uefi => { - const value = std.os.uefi.system_table.runtime_services.getTime() catch return error.Unsupported; - return Instant{ .timestamp = @truncate(value.toEpochNanoseconds()) }; - }, + // On uefi, use REALTIME because it's the only clock implemented right now. + .uefi => posix.CLOCK.REALTIME, // On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while // suspended. .macos, .ios, .tvos, .watchos => posix.CLOCK.UPTIME_RAW, From 2d5880be1c2ef2044547de62b12944a1c2f5c382 Mon Sep 17 00:00:00 2001 From: Nameless Date: Mon, 8 Apr 2024 20:50:16 -0500 Subject: [PATCH 21/23] std.os.uefi: posix membership part 3, and bug fixes galore --- lib/std/fs.zig | 2 +- lib/std/fs/get_app_data_dir.zig | 2 +- lib/std/fs/test.zig | 15 +- lib/std/os/uefi.zig | 14 +- lib/std/os/uefi/bits.zig | 58 ++++ lib/std/os/uefi/posix.zig | 321 ++++++++++++++++-- lib/std/os/uefi/protocol.zig | 14 +- .../uefi/protocol/console/graphics_output.zig | 40 +-- .../protocol/console/simple_text_input.zig | 14 +- .../protocol/console/simple_text_input_ex.zig | 2 +- lib/std/os/uefi/protocol/device_path.zig | 2 +- lib/std/os/uefi/protocol/managed_network.zig | 24 +- lib/std/os/uefi/protocol/media/file.zig | 8 +- lib/std/os/uefi/protocol/media/load_file.zig | 17 +- .../os/uefi/protocol/media/partition_info.zig | 18 +- .../uefi/protocol/network/simple_network.zig | 13 +- lib/std/os/uefi/protocol/rng.zig | 19 +- lib/std/os/uefi/protocol/simple_network.zig | 2 +- lib/std/os/uefi/status.zig | 4 +- lib/std/os/uefi/table/boot_services.zig | 123 +++---- lib/std/os/uefi/table/configuration.zig | 4 +- lib/std/os/uefi/table/runtime_services.zig | 109 +++--- lib/std/posix.zig | 101 ++++-- lib/std/posix/test.zig | 21 +- lib/std/time.zig | 2 +- 25 files changed, 662 insertions(+), 287 deletions(-) diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 646697d50d88..83cfc28e0391 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -74,7 +74,7 @@ pub const max_path_bytes = switch (native_os) { /// On WASI, file name components are encoded as valid UTF-8. /// On other platforms, `[]u8` components are an opaque sequence of bytes with no particular encoding. pub const MAX_NAME_BYTES = switch (native_os) { - .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos => posix.NAME_MAX, + .linux, .macos, .ios, .freebsd, .openbsd, .netbsd, .dragonfly, .solaris, .illumos, .uefi => posix.NAME_MAX, // Haiku's NAME_MAX includes the null terminator, so subtract one. .haiku => posix.NAME_MAX - 1, // Each WTF-16LE character may be expanded to 3 WTF-8 bytes. diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index 4d20452b3c49..1437a5fc0881 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -56,7 +56,7 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi } test getAppDataDir { - if (native_os == .wasi) return error.SkipZigTest; + if (native_os == .wasi or native_os == .uefi) return error.SkipZigTest; // We can't actually validate the result const dir = getAppDataDir(std.testing.allocator, "zig") catch return; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 3aa932cf0147..d87954fc93ca 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -392,7 +392,7 @@ test "openDir non-cwd parent '..'" { } test "readLinkAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; + if (native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1159,6 +1159,9 @@ test "makepath existing directories" { } test "makepath through existing valid symlink" { + if (native_os == .uefi) + return error.SkipZigTest; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1750,7 +1753,7 @@ test "'.' and '..' in fs.Dir functions" { } test "'.' and '..' in absolute functions" { - if (native_os == .wasi) return error.SkipZigTest; + if (native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -1794,7 +1797,7 @@ test "'.' and '..' in absolute functions" { } test "chmod" { - if (native_os == .windows or native_os == .wasi) + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1816,7 +1819,7 @@ test "chmod" { } test "chown" { - if (native_os == .windows or native_os == .wasi) + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1849,7 +1852,7 @@ test "File.Metadata" { } test "File.Permissions" { - if (native_os == .wasi) + if (native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -1875,7 +1878,7 @@ test "File.Permissions" { } test "File.PermissionsUnix" { - if (native_os == .windows or native_os == .wasi) + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 363b5b0f042c..1d07da7bdef9 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -27,19 +27,27 @@ pub var working_directory: fd_t = .none; pub const posix = @import("uefi/posix.zig"); +pub const fd_t = posix.fd_t; pub const ino_t = posix.ino_t; pub const mode_t = posix.mode_t; -pub const fd_t = posix.fd_t; pub const timespec = posix.timespec; +pub const utsname = posix.utsname; pub const Stat = posix.Stat; +pub const AT = posix.AT; +pub const CLOCK = posix.CLOCK; +pub const LOCK = posix.LOCK; +pub const NAME_MAX = posix.NAME_MAX; +pub const O = posix.O; pub const PATH_MAX = posix.PATH_MAX; pub const PATH_MAX_WIDE = posix.PATH_MAX_WIDE; -pub const O = posix.O; -pub const AT = posix.AT; pub const S = posix.S; +pub const F_OK = posix.F_OK; +pub const R_OK = posix.R_OK; +pub const W_OK = posix.W_OK; + pub fn cwd() fd_t { if (system_table.boot_services) |boot_services| blk: { const loaded_image = boot_services.openProtocol(handle, protocol.LoadedImage, .{}) catch break :blk; diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig index b371a0337210..67cd5458705c 100644 --- a/lib/std/os/uefi/bits.zig +++ b/lib/std/os/uefi/bits.zig @@ -108,6 +108,49 @@ pub const Time = extern struct { self.second; } + pub fn fromEpochSeconds(epoch_seconds: u64) Time { + var year: u16 = 1900; + var days: u32 = 0; + + while (true) { + const days_in_year = time.epoch.getDaysInYear(year); + if (days + days_in_year > epoch_seconds / time.s_per_day) { + break; + } + + days += days_in_year; + year += 1; + } + + var month: u8 = 1; + while (true) { + const leap_kind: time.epoch.YearLeapKind = if (time.epoch.isLeapYear(year)) .leap else .not_leap; + const days_in_month = time.epoch.getDaysInMonth(leap_kind, @enumFromInt(month)); + + if (days + days_in_month > epoch_seconds / time.s_per_day) { + break; + } + + days += days_in_month; + month += 1; + } + + const day = @as(u8, @intCast(epoch_seconds / time.s_per_day - days)) + 1; + const seconds = epoch_seconds % time.s_per_day; + + return Time{ + .year = year, + .month = month, + .day = day, + .hour = @intCast(seconds / time.s_per_hour), + .minute = @intCast((seconds % time.s_per_hour) / time.s_per_min), + .second = @intCast(seconds % time.s_per_min), + .nanosecond = 0, + .timezone = unspecified_timezone, + .daylight = .{ .in_daylight = false, .adjust_daylight = false }, + }; + } + /// Returns the time in seconds since 1970-01-01 00:00:00. pub fn toUnixEpochSeconds(self: Time) u64 { return self.toEpochSeconds() -| unix_epoch_seconds; @@ -137,6 +180,17 @@ pub const Time = extern struct { pub const unix_epoch_nanoseconds = unix_epoch.toEpochNanoseconds(); pub const unix_epoch_seconds = unix_epoch.toEpochSeconds(); + + test fromEpochSeconds { + const unix = Time.fromEpochSeconds(unix_epoch_seconds); + + try std.testing.expect(unix.year == 1970); + try std.testing.expect(unix.month == 1); + try std.testing.expect(unix.day == 1); + try std.testing.expect(unix.hour == 0); + try std.testing.expect(unix.minute == 0); + try std.testing.expect(unix.second == 0); + } }; /// Capabilities of the clock device @@ -464,3 +518,7 @@ pub const FileSystemVolumeLabel = extern struct { .node = [_]u8{ 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, }; }; + +test { + std.testing.refAllDecls(@This()); +} \ No newline at end of file diff --git a/lib/std/os/uefi/posix.zig b/lib/std/os/uefi/posix.zig index 78363b874f92..2ac0cc5cb99c 100644 --- a/lib/std/os/uefi/posix.zig +++ b/lib/std/os/uefi/posix.zig @@ -19,6 +19,13 @@ pub const fd_t = union(enum) { pub const PATH_MAX_WIDE = 4096; pub const PATH_MAX = PATH_MAX_WIDE * 3 + 1; +pub const NAME_MAX = 255; + +pub const IOV_MAX = 1024; + +pub const F_OK = 0; +pub const R_OK = 1; +pub const W_OK = 2; pub const O = packed struct { ACCMODE: std.posix.ACCMODE = .RDONLY, @@ -26,21 +33,28 @@ pub const O = packed struct { CLOEXEC: bool = false, CREAT: bool = false, TRUNC: bool = false, -}; - -pub const timespec = struct { - tv_sec: u64, - tv_nsec: u64, + EXCL: bool = false, + NOFOLLOW: bool = false, + DIRECTORY: bool = false, }; pub const AT = struct { pub const FDCWD: fd_t = .cwd; + pub const REMOVEDIR: u32 = 0x200; + pub const SYMLINK_NOFOLLOW: u32 = 0x100; }; pub const CLOCK = struct { pub const REALTIME = 0; }; +pub const LOCK = struct { + pub const SH = 1; + pub const EX = 2; + pub const NB = 4; + pub const UN = 8; +}; + pub const S = struct { pub const IFMT = 0o170000; @@ -53,6 +67,19 @@ pub const S = struct { pub const IFSOCK = 0o140000; }; +pub const timespec = struct { + tv_sec: i64, + tv_nsec: i64, +}; + +pub const utsname = struct { + sysname: [8:0]u8, + nodename: [8:0]u8, + release: [32:0]u8, + version: [5:0]u8, + machine: [16:0]u8, +}; + pub const Stat = struct { ino: ino_t, mode: mode_t, @@ -80,7 +107,7 @@ fn unexpectedError(err: anyerror) error{Unexpected} { } pub fn chdir(dir_path: []const u8) std.posix.ChangeCurDirError!void { - var path_buffer: [PATH_MAX]u16 = undefined; + var path_buffer: [PATH_MAX_WIDE]u16 = undefined; const len = try std.unicode.wtf8ToWtf16Le(&path_buffer, dir_path); path_buffer[len] = 0; @@ -94,6 +121,7 @@ pub fn chdir(dir_path: []const u8) std.posix.ChangeCurDirError!void { error.OutOfResources => return error.SystemResources, else => |e| return unexpectedError(e), }; + defer fd.close(); try fchdir(fd); } @@ -134,20 +162,22 @@ pub fn close(fd: fd_t) void { pub fn exit(status: u8) noreturn { if (uefi.system_table.boot_services) |bs| { - bs.exit(uefi.handle, @enumFromInt(status), 0, null) catch {}; + bs.exit(uefi.handle, @enumFromInt(status), null) catch {}; } - uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), 0, null); + uefi.system_table.runtime_services.resetSystem(.cold, @enumFromInt(status), null); } pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) std.posix.AccessError!void { switch (dirfd) { .file => |p| { - var path_buffer: [PATH_MAX]u16 = undefined; + var path_buffer: [PATH_MAX_WIDE]u16 = undefined; const len = try std.unicode.wtf8ToWtf16Le(&path_buffer, path); path_buffer[len] = 0; - const fd = p.open(path_buffer[0..len :0], .{}, .{}) catch |err| switch (err) { + const fd = p.open(path_buffer[0..len :0], .{ + .write = mode & W_OK != 0, + }, .{}) catch |err| switch (err) { error.NotFound => return error.FileNotFound, error.NoMedia => return error.InputOutput, error.MediaChanged => return error.InputOutput, @@ -157,15 +187,14 @@ pub fn faccessat(dirfd: fd_t, path: []const u8, mode: u32, flags: u32) std.posix error.OutOfResources => return error.SystemResources, else => |e| return unexpectedError(e), }; - - fd.close(); + defer fd.close(); }, .cwd => return faccessat(uefi.working_directory, path, mode, flags), else => return error.FileNotFound, } } -pub fn fchdir(fd: fd_t) std.posix.ChangeCurDirError!void { +pub fn fchdir(fd: fd_t) std.posix.FchdirError!void { switch (fd) { .file => { close(uefi.working_directory); @@ -178,6 +207,11 @@ pub fn fchdir(fd: fd_t) std.posix.ChangeCurDirError!void { } } +pub fn flock(fd: fd_t, operation: i32) std.posix.FlockError!void { + _ = fd; + _ = operation; +} + pub fn fstat(fd: fd_t) std.posix.FStatError!Stat { switch (fd) { .file => |p| { @@ -197,9 +231,9 @@ pub fn fstat(fd: fd_t) std.posix.FStatError!Stat { .ino = 0, .mode = if (info.attribute.directory) S.IFDIR else S.IFREG, .size = info.file_size, - .atim = timespec{ .tv_sec = info.last_access_time.toUnixEpochSeconds(), .tv_nsec = info.last_access_time.nanosecond }, - .mtim = timespec{ .tv_sec = info.modification_time.toUnixEpochSeconds(), .tv_nsec = info.modification_time.nanosecond }, - .ctim = timespec{ .tv_sec = info.create_time.toUnixEpochSeconds(), .tv_nsec = info.create_time.nanosecond }, + .atim = timespec{ .tv_sec = @intCast(info.last_access_time.toUnixEpochSeconds()), .tv_nsec = info.last_access_time.nanosecond }, + .mtim = timespec{ .tv_sec = @intCast(info.modification_time.toUnixEpochSeconds()), .tv_nsec = info.modification_time.nanosecond }, + .ctim = timespec{ .tv_sec = @intCast(info.create_time.toUnixEpochSeconds()), .tv_nsec = info.create_time.nanosecond }, }; }, .simple_input, .simple_output => return Stat{ @@ -218,7 +252,7 @@ pub fn fstat(fd: fd_t) std.posix.FStatError!Stat { pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) std.posix.FStatAtError!Stat { _ = flags; - const fd = try openat(dirfd, pathname, .{}, 0); + const fd = openat(dirfd, pathname, .{}, 0) catch return error.FileNotFound; defer close(fd); return try fstat(fd); @@ -244,7 +278,7 @@ pub fn ftruncate(fd: fd_t, length: u64) std.posix.TruncateError!void { u8, @alignOf(std.os.uefi.bits.FileInfo), buffer_size, - ) catch return error.SystemResources; + ) catch return error.Unexpected; defer pool_allocator.allocator().free(buffer); var info = p.getInfo(std.os.uefi.bits.FileInfo, buffer) catch return error.Unexpected; @@ -254,22 +288,49 @@ pub fn ftruncate(fd: fd_t, length: u64) std.posix.TruncateError!void { p.setInfo(std.os.uefi.bits.FileInfo, buffer[0..buffer_size]) catch return error.AccessDenied; } -// TODO: futimens +pub fn futimens(fd: fd_t, times: *const [2]timespec) std.posix.FutimensError!void { + switch (fd) { + .file => |p| { + var pool_allocator = std.os.uefi.PoolAllocator{}; + + const buffer_size = p.getInfoSize(std.os.uefi.bits.FileInfo) catch return error.Unexpected; + const buffer = pool_allocator.allocator().alignedAlloc( + u8, + @alignOf(std.os.uefi.bits.FileInfo), + buffer_size, + ) catch return error.Unexpected; + defer pool_allocator.allocator().free(buffer); + + var info = p.getInfo(std.os.uefi.bits.FileInfo, buffer) catch return error.Unexpected; + + info.last_access_time = uefi.bits.Time.fromEpochSeconds(@as(u64, @intCast(times[0].tv_sec)) -| uefi.bits.Time.unix_epoch_seconds); + info.last_access_time.nanosecond = @intCast(times[0].tv_nsec); + + info.modification_time = uefi.bits.Time.fromEpochSeconds(@as(u64, @intCast(times[1].tv_sec)) -| uefi.bits.Time.unix_epoch_seconds); + info.modification_time.nanosecond = @intCast(times[1].tv_nsec); + + p.setInfo(std.os.uefi.bits.FileInfo, buffer[0..buffer_size]) catch return error.AccessDenied; + }, + .cwd => return futimens(uefi.working_directory, times), + else => return error.AccessDenied, + } +} pub fn getcwd(out_buffer: []u8) std.posix.GetCwdError![]u8 { const fd = uefi.working_directory; if (fd == .none) - return error.NoDevice; + return error.CurrentWorkingDirectoryUnlinked; var buffer: [PATH_MAX]u8 = undefined; - const path = std.os.getFdPath(fd, &buffer); + const path = std.os.getFdPath(fd, &buffer) catch return error.NameTooLong; if (path.len > out_buffer.len) return error.NameTooLong; @memcpy(out_buffer[0..path.len], out_buffer); + return out_buffer[0..path.len]; } -pub fn getrandom(buf: []u8) std.posix.GetRandomError!usize { +pub fn getrandom(buf: []u8) std.posix.GetRandomError!void { if (uefi.system_table.boot_services) |boot_services| { const rng = (boot_services.locateProtocol(uefi.protocol.Rng, .{}) catch return error.NoDevice) orelse return error.NoDevice; @@ -281,17 +342,15 @@ pub fn getrandom(buf: []u8) std.posix.GetRandomError!usize { break; } - - return buf.len; } else { - return 0; + return error.NoDevice; } } -pub fn isatty(fd: fd_t) u8 { +pub fn isatty(fd: fd_t) bool { switch (fd) { - .simple_input, .simple_output => 1, - else => 0, + .simple_input, .simple_output => return true, + else => return false, } } @@ -304,14 +363,14 @@ pub fn lseek_SET(fd: fd_t, pos: u64) std.posix.SeekError!void { } } -pub fn lseek_CUR(fd: fd_t, offset: i64) std.posix.SeekError!u64 { +pub fn lseek_CUR(fd: fd_t, offset: i64) std.posix.SeekError!void { switch (fd) { .file => |p| { const end = p.getEndPosition() catch return error.Unseekable; const pos = p.getPosition() catch return error.Unseekable; const new_pos = @as(i64, @intCast(pos)) + offset; - var abs_pos = 0; + var abs_pos: u64 = 0; if (new_pos > end) abs_pos = uefi.protocol.File.position_end_of_file else if (new_pos > 0) @@ -323,13 +382,13 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) std.posix.SeekError!u64 { } } -pub fn lseek_END(fd: fd_t, offset: i64) std.posix.SeekError!u64 { +pub fn lseek_END(fd: fd_t, offset: i64) std.posix.SeekError!void { switch (fd) { .file => |p| { const end = p.getEndPosition() catch return error.Unseekable; const new_pos = @as(i64, @intCast(end)) + offset; - var abs_pos = 0; + var abs_pos: u64 = 0; if (new_pos > end) abs_pos = uefi.protocol.File.position_end_of_file else if (new_pos > 0) @@ -350,10 +409,47 @@ pub fn lseek_CUR_get(fd: fd_t) std.posix.SeekError!u64 { } } +pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) std.posix.MakeDirError!void { + switch (dir_fd) { + .file => |p| { + var path_buffer: [PATH_MAX_WIDE]u16 = undefined; + const len = try std.unicode.wtf8ToWtf16Le(&path_buffer, sub_dir_path); + path_buffer[len] = 0; + + if (p.open(path_buffer[0..len :0], .{}, .{})) |fd| { + fd.close(); + + return error.PathAlreadyExists; + } else |_| {} + + const fd = p.open(path_buffer[0..len :0], .{ + .write = true, + .create = true, + }, .{ + .directory = true, + }) catch |err| switch (err) { + error.NoMedia => return error.NoDevice, + error.MediaChanged => return error.NoDevice, + error.DeviceError => return error.NoDevice, + error.VolumeCorrupted => return error.NoDevice, + error.WriteProtected => return error.AccessDenied, + error.AccessDenied => return error.AccessDenied, + error.OutOfResources => return error.SystemResources, + else => |e| return unexpectedError(e), + }; + defer fd.close(); + }, + .simple_output => return error.NotDir, + .simple_input => return error.NotDir, + .none => return error.NotDir, + .cwd => return mkdirat(uefi.working_directory, sub_dir_path, mode), + } +} + pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) std.posix.OpenError!fd_t { switch (dir_fd) { .file => |p| { - var path_buffer: [PATH_MAX]u16 = undefined; + var path_buffer: [PATH_MAX_WIDE]u16 = undefined; const len = try std.unicode.wtf8ToWtf16Le(&path_buffer, file_path); path_buffer[len] = 0; @@ -412,13 +508,170 @@ pub fn read(fd: fd_t, buf: []u8) std.posix.ReadError!usize { } } +pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) std.posix.ReadLinkError![]u8 { + const fd = openat(dirfd, file_path, .{}, 0) catch return error.FileNotFound; + + var buffer: [PATH_MAX]u8 = undefined; + const path = std.os.getFdPath(fd, &buffer) catch return error.NameTooLong; + if (path.len > out_buffer.len) + return error.NameTooLong; + + @memcpy(out_buffer[0..path.len], out_buffer); + return path; +} + pub fn realpath(pathname: []const u8, out_buffer: *[PATH_MAX]u8) std.posix.RealPathError![]u8 { - const fd = try openat(.cwd, pathname, .{}, 0); + const fd = openat(.cwd, pathname, .{}, 0) catch |err| switch (err) { + error.WouldBlock => return error.DeviceBusy, + error.InvalidUtf8 => unreachable, + error.FileLocksNotSupported => unreachable, + error.FileBusy => return error.DeviceBusy, + else => |e| return unexpectedError(e), + }; defer close(fd); return std.os.getFdPath(fd, out_buffer); } +pub fn renameat( + old_dir_fd: fd_t, + old_path: []const u8, + new_dir_fd: fd_t, + new_path: []const u8, +) std.posix.RenameError!void { + switch (old_dir_fd) { + .file => |old_dir_p| switch (new_dir_fd) { + .file => |new_dir_p| { + var old_path_buffer: [PATH_MAX_WIDE]u16 = undefined; + const old_len = try std.unicode.wtf8ToWtf16Le(&old_path_buffer, old_path); + old_path_buffer[old_len] = 0; + + var new_path_buffer: [PATH_MAX_WIDE]u16 = undefined; + const new_len = try std.unicode.wtf8ToWtf16Le(&new_path_buffer, new_path); + new_path_buffer[new_len] = 0; + + const old_fd = old_dir_p.open(old_path_buffer[0..old_len :0], .{}, .{}) catch |err| switch (err) { + error.NotFound => return error.FileNotFound, + error.NoMedia => return error.NoDevice, + error.MediaChanged => return error.NoDevice, + error.DeviceError => return error.NoDevice, + error.VolumeCorrupted => return error.NoDevice, + error.WriteProtected => return error.AccessDenied, + error.AccessDenied => return error.AccessDenied, + error.OutOfResources => return error.SystemResources, + else => |e| return unexpectedError(e), + }; + errdefer old_fd.close(); + + const new_fd = new_dir_p.open(new_path_buffer[0..new_len :0], .{ .write = true, .create = true }, .{}) catch |err| switch (err) { + error.NotFound => return error.FileNotFound, + error.NoMedia => return error.NoDevice, + error.MediaChanged => return error.NoDevice, + error.DeviceError => return error.NoDevice, + error.VolumeCorrupted => return error.NoDevice, + error.WriteProtected => return error.AccessDenied, + error.AccessDenied => return error.AccessDenied, + error.OutOfResources => return error.SystemResources, + else => |e| return unexpectedError(e), + }; + defer new_fd.close(); + + var buffer: [8192]u8 = undefined; + while (true) { + const nread = old_fd.read(&buffer) catch |err| switch (err) { + error.NoMedia => return error.NoDevice, + error.DeviceError => return error.NoDevice, + error.VolumeCorrupted => return error.NoDevice, + else => |e| return unexpectedError(e), + }; + if (nread == 0) + break; + + var index: usize = 0; + while (index < nread) { + const written = new_fd.write(buffer[index..nread]) catch |err| switch (err) { + error.NoMedia => return error.NoDevice, + error.DeviceError => return error.NoDevice, + error.VolumeCorrupted => return error.NoDevice, + else => |e| return unexpectedError(e), + }; + index += written; + } + } + + _ = old_fd.delete(); + }, + .simple_output => return error.NotDir, + .simple_input => return error.NotDir, + .none => return error.NotDir, + .cwd => return renameat(old_dir_fd, old_path, uefi.working_directory, new_path), + }, + .simple_output => return error.NotDir, + .simple_input => return error.NotDir, + .none => return error.NotDir, + .cwd => return renameat(uefi.working_directory, old_path, new_dir_fd, new_path), + } +} + +pub fn uname() utsname { + var uts: utsname = undefined; + + @memcpy(&uts.sysname, "zig-uefi"); + uts.sysname[8] = 0; + + @memcpy(&uts.nodename, "zig-uefi"); + uts.nodename[8] = 0; + + const release = builtin.zig_version_string; + @memcpy(uts.release[0..release.len], release); + uts.release[release.len] = 0; + + @memcpy(&uts.version, "2.0.0"); + uts.version[5] = 0; + + const machine = @tagName(builtin.cpu.arch); + @memcpy(uts.machine[0..machine.len], machine); + uts.machine[machine.len] = 0; +} + +pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) std.posix.UnlinkatError!void { + switch (dirfd) { + .file => |p| { + var path_buffer: [PATH_MAX_WIDE]u16 = undefined; + const len = try std.unicode.wtf8ToWtf16Le(&path_buffer, file_path); + path_buffer[len] = 0; + + const fd = p.open(path_buffer[0..len :0], .{ .write = true }, .{}) catch |err| switch (err) { + error.NotFound => return error.FileNotFound, + error.NoMedia => return error.FileSystem, + error.MediaChanged => return error.FileSystem, + error.DeviceError => return error.FileSystem, + error.VolumeCorrupted => return error.FileSystem, + error.WriteProtected => return error.AccessDenied, + error.AccessDenied => return error.AccessDenied, + error.OutOfResources => return error.SystemResources, + else => |e| return unexpectedError(e), + }; + errdefer fd.close(); + + const stat = try fstat(.{ .file = fd }); + + // fd is a directory and AT_REMOVEDIR is not set + if (stat.mode & S.IFDIR != 0 and flags & AT.REMOVEDIR != 0) + return error.IsDir; + + if (!fd.delete()) { + // delete failed, likely because this is a directory and not empty + return error.DirNotEmpty; + } + }, + .simple_output => return error.NotDir, + .simple_input => return error.NotDir, + .none => return error.NotDir, + .cwd => return unlinkat(uefi.working_directory, file_path, flags), + } +} + pub fn write(fd: fd_t, buf: []const u8) std.posix.WriteError!usize { switch (fd) { .file => |p| { diff --git a/lib/std/os/uefi/protocol.zig b/lib/std/os/uefi/protocol.zig index f5fa2f8e849a..7f74fe29f048 100644 --- a/lib/std/os/uefi/protocol.zig +++ b/lib/std/os/uefi/protocol.zig @@ -118,9 +118,9 @@ pub const ManagedNetwork = @import("protocol/managed_network.zig").ManagedNetwor // Ip4 // Ip4Config // Ip4Config2 -pub const Ip6ServiceBinding = @import("protocol/ip6_service_binding.zig").Ip6ServiceBinding; -pub const Ip6 = @import("protocol/ip6.zig").Ip6; -pub const Ip6Config = @import("protocol/ip6_config.zig").Ip6Config; +// pub const Ip6ServiceBinding = @import("protocol/ip6_service_binding.zig").Ip6ServiceBinding; +// pub const Ip6 = @import("protocol/ip6.zig").Ip6; +// pub const Ip6Config = @import("protocol/ip6_config.zig").Ip6Config; // IpsecConfig // Ipsec // Ipsec2 @@ -152,8 +152,8 @@ pub const Ip6Config = @import("protocol/ip6_config.zig").Ip6Config; // 30. UDP, MTFTP // Udp4ServiceBinding // Udp4 -pub const Udp6ServiceBinding = @import("protocol/udp6_service_binding.zig").Udp6ServiceBinding; -pub const Udp6 = @import("protocol/udp6.zig").Udp6; +// pub const Udp6ServiceBinding = @import("protocol/udp6_service_binding.zig").Udp6ServiceBinding; +// pub const Udp6 = @import("protocol/udp6.zig").Udp6; // Mtftp4ServiceBinding // Mtftp4 // Mtftp6ServiceBinding @@ -173,14 +173,14 @@ pub const Udp6 = @import("protocol/udp6.zig").Udp6; // HiiImageEx // HiiImageDecoder // HiiFontGlyphGenerator -pub const HiiDatabase = @import("protocol/hii_database.zig").HiiDatabase; +// pub const HiiDatabase = @import("protocol/hii_database.zig").HiiDatabase; // 35. HII Configuration // ConfigKeywordHandler // HiiConfigRouting // HiiConfigAccess // FormBrowser2 -pub const HiiPopup = @import("protocol/hii_popup.zig").HiiPopup; +// pub const HiiPopup = @import("protocol/hii_popup.zig").HiiPopup; // 36. User Identification // UserManager diff --git a/lib/std/os/uefi/protocol/console/graphics_output.zig b/lib/std/os/uefi/protocol/console/graphics_output.zig index a7b467323b63..547da1f6deb7 100644 --- a/lib/std/os/uefi/protocol/console/graphics_output.zig +++ b/lib/std/os/uefi/protocol/console/graphics_output.zig @@ -84,7 +84,7 @@ pub const GraphicsOutput = extern struct { /// The blue channel of the pixel. blue: u8, ) !void { - const addr = self.frame_buffer_base + (y * self.pixels_per_scan_line + x) * self.info.pixelElementSize(); + const addr = self.frame_buffer_base + (y * self.info.pixels_per_scan_line + x) * self.info.pixelElementSize(); const pixel: *[4]u8 = @ptrFromInt(addr); @@ -100,7 +100,7 @@ pub const GraphicsOutput = extern struct { pixel[2] = red; }, .bitmask => { - const pixel_u32: *u32 = @ptrCast(pixel); + const pixel_u32: *align(1) u32 = @ptrCast(pixel); const red_value = self.info.pixel_bitmask.toValue(.red, red); const green_value = self.info.pixel_bitmask.toValue(.green, green); @@ -144,41 +144,41 @@ pub const GraphicsOutput = extern struct { /// Finds the size in bits of a single pixel. pub fn bitSizeOf(self: *const PixelBitmask) u5 { - const highest_red_bit = 32 - @clz(self.red_mask); - const highest_green_bit = 32 - @clz(self.green_mask); - const highest_blue_bit = 32 - @clz(self.blue_mask); - const highest_reserved_bit = 32 - @clz(self.reserved_mask); + const highest_red_bit = 32 - @clz(self.red); + const highest_green_bit = 32 - @clz(self.green); + const highest_blue_bit = 32 - @clz(self.blue); + const highest_reserved_bit = 32 - @clz(self.reserved); - return @max(@max(highest_red_bit, highest_green_bit), @max(highest_blue_bit, highest_reserved_bit)); + return @intCast(@max(@max(highest_red_bit, highest_green_bit), @max(highest_blue_bit, highest_reserved_bit))); } /// Finds the size in bits of a pixel field. pub inline fn bitSizeOfField(self: *const PixelBitmask, comptime field: PixelField) u5 { switch (field) { - .red => return @popCount(self.red_mask), - .green => return @popCount(self.green_mask), - .blue => return @popCount(self.blue_mask), - .reserved => return @popCount(self.reserved_mask), + .red => return @intCast(@popCount(self.red)), + .green => return @intCast(@popCount(self.green)), + .blue => return @intCast(@popCount(self.blue)), + .reserved => return @intCast(@popCount(self.reserved)), } } /// Finds the offset from zero (ie. a shift) in bits of a pixel field. pub inline fn bitOffsetOfField(self: *const PixelBitmask, comptime field: PixelField) u5 { switch (field) { - .red => return @ctz(self.red_mask), - .green => return @ctz(self.green_mask), - .blue => return @ctz(self.blue_mask), - .reserved => return @ctz(self.reserved_mask), + .red => return @intCast(@ctz(self.red)), + .green => return @intCast(@ctz(self.green)), + .blue => return @intCast(@ctz(self.blue)), + .reserved => return @intCast(@ctz(self.reserved)), } } /// Returns the bit mask of a pixel field. pub inline fn bitMaskOfField(self: *const PixelBitmask, comptime field: PixelField) u32 { switch (field) { - .red => return self.red_mask, - .green => return self.green_mask, - .blue => return self.blue_mask, - .reserved => return self.reserved_mask, + .red => return self.red, + .green => return self.green, + .blue => return self.blue, + .reserved => return self.reserved, } } @@ -191,7 +191,7 @@ pub const GraphicsOutput = extern struct { /// Returns the value of a pixel field shifted and saturated to the correct position for the pixel. pub fn toValue(self: *const PixelBitmask, comptime field: PixelField, value: u8) u32 { - const max_field_value: u32 = 1 << self.bitSizeOfField(field); + const max_field_value: u32 = @as(u32, 1) << self.bitSizeOfField(field); const value_saturated: u32 = @min(value, max_field_value); const value_shifted = value_saturated << self.bitOffsetOfField(field); diff --git a/lib/std/os/uefi/protocol/console/simple_text_input.zig b/lib/std/os/uefi/protocol/console/simple_text_input.zig index 6c766ef1422e..7af3ec79ca1a 100644 --- a/lib/std/os/uefi/protocol/console/simple_text_input.zig +++ b/lib/std/os/uefi/protocol/console/simple_text_input.zig @@ -26,14 +26,12 @@ pub const SimpleTextInput = extern struct { /// Reads the next keystroke from the input device. pub fn readKeyStroke(self: *const SimpleTextInput) !?Key.Input { var input_key: Key.Input = undefined; - switch (self._read_key_stroke(self, &input_key)) { - .success => return input_key, - .not_ready => return null, - else => |s| { - try s.err(); - unreachable; - }, - } + self._read_key_stroke(self, &input_key).err() catch |err| switch (err) { + error.NotReady => return null, + else => |e| return e, + }; + + return input_key; } pub const guid align(8) = Guid{ diff --git a/lib/std/os/uefi/protocol/console/simple_text_input_ex.zig b/lib/std/os/uefi/protocol/console/simple_text_input_ex.zig index 9fdef1cbcb28..caa34179131e 100644 --- a/lib/std/os/uefi/protocol/console/simple_text_input_ex.zig +++ b/lib/std/os/uefi/protocol/console/simple_text_input_ex.zig @@ -13,7 +13,7 @@ pub const SimpleTextInputEx = extern struct { _read_key_stroke_ex: *const fn (*const SimpleTextInputEx, key: *Key) callconv(cc) Status, wait_for_key_ex: Event, _set_state: *const fn (*const SimpleTextInputEx, state: *const Key.State.Toggle) callconv(cc) Status, - _register_key_notify: *const fn (*const SimpleTextInputEx, key: *Key, func: *const fn (*const Key) callconv(cc) usize, handle: *NotifyHandle) callconv(cc) Status, + _register_key_notify: *const fn (*const SimpleTextInputEx, key: *Key, func: NotifyFn, handle: *NotifyHandle) callconv(cc) Status, _unregister_key_notify: *const fn (*const SimpleTextInputEx, handle: NotifyHandle) callconv(cc) Status, /// Resets the input device hardware. diff --git a/lib/std/os/uefi/protocol/device_path.zig b/lib/std/os/uefi/protocol/device_path.zig index 8a2ab6bd2e6c..ac63df18e9d9 100644 --- a/lib/std/os/uefi/protocol/device_path.zig +++ b/lib/std/os/uefi/protocol/device_path.zig @@ -185,7 +185,7 @@ pub const DevicePath = extern struct { @memcpy(new_bytes[original_size..], path_bytes[0..other_size]); // change end entire node to end this instance node - const end_of_existing: *DevicePath = @ptrCast(new_bytes + original_size - 4); + const end_of_existing: *DevicePath = @ptrCast(new_bytes.ptr + original_size - 4); end_of_existing.subtype = @intFromEnum(DevicePathNode.End.Subtype.this_instance); return @ptrCast(new_bytes); diff --git a/lib/std/os/uefi/protocol/managed_network.zig b/lib/std/os/uefi/protocol/managed_network.zig index e0c5dc66f410..7c95cfb75722 100644 --- a/lib/std/os/uefi/protocol/managed_network.zig +++ b/lib/std/os/uefi/protocol/managed_network.zig @@ -1,13 +1,17 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Guid = uefi.Guid; -const Event = uefi.Event; -const Handle = uefi.Handle; -const Status = uefi.Status; -const Time = uefi.Time; -const SimpleNetwork = uefi.protocol.SimpleNetwork; -const MacAddress = uefi.MacAddress; -const cc = uefi.cc; +const bits = @import("../bits.zig"); +const table = @import("../table.zig"); +const protocol = @import("../protocol.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const SimpleNetwork = protocol.SimpleNetwork; + +const Guid = bits.Guid; +const Handle = bits.Handle; +const Event = bits.Event; +const Time = bits.Time; +const MacAddress = bits.MacAddress; pub const ManagedNetwork = extern struct { _get_mode_data: *const fn (*const ManagedNetwork, ?*Config, ?*SimpleNetwork) callconv(cc) Status, diff --git a/lib/std/os/uefi/protocol/media/file.zig b/lib/std/os/uefi/protocol/media/file.zig index 5e9838f38539..ab8055732d86 100644 --- a/lib/std/os/uefi/protocol/media/file.zig +++ b/lib/std/os/uefi/protocol/media/file.zig @@ -76,8 +76,10 @@ pub const File = extern struct { } /// Closes and deletes a file. This can fail, but the descriptor will still be closed. - pub fn delete(self: *const File) void { - _ = self._delete(self); + /// + /// Returns `true` if the file was successfully deleted, `false` otherwise. + pub fn delete(self: *const File) bool { + return self._delete(self) == .success; } /// Reads data from a file. @@ -171,7 +173,7 @@ pub const File = extern struct { comptime Information: type, buffer: []align(@alignOf(Information)) u8, ) !void { - try self._set_info(self, Information.guid, buffer.len, buffer.ptr).err(); + try self._set_info(self, &Information.guid, buffer.len, buffer.ptr).err(); } /// Flushes all modified data associated with a file to a device. diff --git a/lib/std/os/uefi/protocol/media/load_file.zig b/lib/std/os/uefi/protocol/media/load_file.zig index d73b43f8ddd6..d269074bb5f4 100644 --- a/lib/std/os/uefi/protocol/media/load_file.zig +++ b/lib/std/os/uefi/protocol/media/load_file.zig @@ -21,10 +21,12 @@ pub const LoadFile = extern struct { boot_policy: bool, ) !usize { var size: usize = 0; - switch (self._load_file(self, file_path, boot_policy, &size, null)) { - .buffer_too_small => return size, - else => |s| return s.err(), - } + self._load_file(self, file_path, boot_policy, &size, null).err() catch |err| switch (err) { + error.BufferTooSmall => {}, + else => |e| return e, + }; + + return size; } /// Causes the driver to load a specified file. @@ -36,10 +38,11 @@ pub const LoadFile = extern struct { /// file as a boot selection. If false, the file path must match an exact file to be loaded. boot_policy: bool, /// The memory buffer to transfer the file to. - buffer: [*]u8, - ) !void { + buffer: []u8, + ) !usize { var size: usize = buffer.len; - try self._load_file(self, file_path, boot_policy, &size, buffer).err(); + try self._load_file(self, file_path, boot_policy, &size, buffer.ptr).err(); + return size; } pub const guid align(8) = Guid{ diff --git a/lib/std/os/uefi/protocol/media/partition_info.zig b/lib/std/os/uefi/protocol/media/partition_info.zig index 42470f87db95..728063ca2cc1 100644 --- a/lib/std/os/uefi/protocol/media/partition_info.zig +++ b/lib/std/os/uefi/protocol/media/partition_info.zig @@ -59,17 +59,17 @@ pub const PartitionInfo = extern struct { ending_chs: [3]u8, /// Starting LBA of the partition on the disk. This is used by UEFI to find the start of the partition. - starting_lba: u32 align(1), + starting_lba: u32, /// Size of the partiion in LBA units. This is used by UEFI to find the size of the partition. - size_in_lba: u32 align(1), + size_in_lba: u32, }; /// Shall not be used by UEFI firmware. boot_code: [424]u8, /// Used to identify the disk, never written by UEFI firmware. - unique_disk_signature: u32 align(1), + unique_disk_signature: u32, /// Shall not be used by UEFI firmware. unknown: [2]u8, @@ -103,23 +103,23 @@ pub const PartitionInfo = extern struct { /// Unique ID that defines the purpose and type of this Partition. A value of zero defines that this /// partition entry is not being used. - partition_type: Guid align(1), + partition_type: Guid, /// GUID that is unique for every partition entry. Every partition ever created will have a unique GUID. This /// GUID must be assigned when the GPT Partition Entry is created. - unique_partition_guid: Guid align(1), + unique_partition_guid: Guid, /// Starting LBA of the partition defined by this entry. - starting_lba: u64 align(1), + starting_lba: u64, /// Ending LBA of the partition defined by this entry. - ending_lba: u64 align(1), + ending_lba: u64, /// Attribute bits, all bits reserved by UEFI - attributes: Attributes align(1), + attributes: Attributes, /// Null-terminated string containing a human-readable name of the partition. - partition_name: [36]u16 align(1), + partition_name: [36]u16, /// The human readable name of the partition. pub fn getName(self: *const GptEntry) []const u16 { diff --git a/lib/std/os/uefi/protocol/network/simple_network.zig b/lib/std/os/uefi/protocol/network/simple_network.zig index 5de0f1fb3c9a..0763356ac3bc 100644 --- a/lib/std/os/uefi/protocol/network/simple_network.zig +++ b/lib/std/os/uefi/protocol/network/simple_network.zig @@ -110,13 +110,12 @@ pub const SimpleNetwork = extern struct { pub fn collectStatistics(self: *const SimpleNetwork) !Statistics { var stats: Statistics = undefined; var size: usize = @sizeOf(Statistics); - switch (self._statistics(self, false, &size, &stats)) { - .success, .buffer_too_small => return stats, - else => |e| { - try e.err(); - return stats; // not an error? assume the buffer was filled and a warning was issued, this is not specified. - }, - } + self._statistics(self, false, &size, &stats).err() catch |err| switch (err) { + error.BufferTooSmall => {}, + else => |e| return e, + }; + + return stats; } /// Converts a multicast IP address to a multicast HW MAC address. diff --git a/lib/std/os/uefi/protocol/rng.zig b/lib/std/os/uefi/protocol/rng.zig index 3d914e7a9ebe..6dc162db80e6 100644 --- a/lib/std/os/uefi/protocol/rng.zig +++ b/lib/std/os/uefi/protocol/rng.zig @@ -1,8 +1,9 @@ -const std = @import("std"); -const uefi = std.os.uefi; -const Guid = uefi.Guid; -const Status = uefi.Status; -const cc = uefi.cc; +const bits = @import("../bits.zig"); + +const cc = bits.cc; +const Status = @import("../status.zig").Status; + +const Guid = bits.Guid; /// Random Number Generator protocol pub const Rng = extern struct { @@ -10,13 +11,13 @@ pub const Rng = extern struct { _get_rng: *const fn (*const Rng, ?*align(8) const Guid, usize, [*]u8) callconv(cc) Status, /// Returns information about the random number generation implementation. - pub fn getInfo(self: *const Rng, list_size: *usize, list: [*]align(8) Guid) Status { - return self._get_info(self, list_size, list); + pub fn getInfo(self: *const Rng, list_size: *usize, list: [*]align(8) Guid) !void { + return self._get_info(self, list_size, list).err(); } /// Produces and returns an RNG value using either the default or specified RNG algorithm. - pub fn getRNG(self: *const Rng, algo: ?*align(8) const Guid, value_length: usize, value: [*]u8) Status { - return self._get_rng(self, algo, value_length, value); + pub fn getRNG(self: *const Rng, algo: ?*align(8) const Guid, value_length: usize, value: [*]u8) !void { + return self._get_rng(self, algo, value_length, value).err(); } pub const guid align(8) = Guid{ diff --git a/lib/std/os/uefi/protocol/simple_network.zig b/lib/std/os/uefi/protocol/simple_network.zig index f18624f888e0..109745ed7d7d 100644 --- a/lib/std/os/uefi/protocol/simple_network.zig +++ b/lib/std/os/uefi/protocol/simple_network.zig @@ -71,7 +71,7 @@ pub const SimpleNetwork = extern struct { /// /// If statistics_table is null, the statistics are reset. pub fn statistics(self: *const SimpleNetwork, statistics_table: ?*Statistics) !usize { - var statistics_size = @sizeOf(Statistics); + var statistics_size: usize = @sizeOf(Statistics); try self._statistics(self, statistics_table == null, &statistics_size, statistics_table).err(); return statistics_size; } diff --git a/lib/std/os/uefi/status.zig b/lib/std/os/uefi/status.zig index 561a4155149a..a949fae60b77 100644 --- a/lib/std/os/uefi/status.zig +++ b/lib/std/os/uefi/status.zig @@ -249,9 +249,9 @@ pub const Status = enum(usize) { const testing = @import("../../std.zig").testing; test "status" { - var st: Status = .DeviceError; + var st: Status = .device_error; try testing.expectError(error.DeviceError, st.err()); - st = .Success; + st = .success; try st.err(); } diff --git a/lib/std/os/uefi/table/boot_services.zig b/lib/std/os/uefi/table/boot_services.zig index 5e6f32106d19..c5f8e2f9d2cc 100644 --- a/lib/std/os/uefi/table/boot_services.zig +++ b/lib/std/os/uefi/table/boot_services.zig @@ -46,9 +46,9 @@ pub const BootServices = extern struct { _closeEvent: *const fn (event: Event) callconv(cc) Status, _checkEvent: *const fn (event: Event) callconv(cc) Status, - _installProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, interface_type: EfiInterfaceType, interface: ProtocolInterface) callconv(cc) Status, - _reinstallProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, old_interface: ProtocolInterface, new_interface: ProtocolInterface) callconv(cc) Status, - _uninstallProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: ProtocolInterface) callconv(cc) Status, + _installProtocolInterface: *const fn (handle: *?Handle, protocol: *align(8) const Guid, interface_type: EfiInterfaceType, interface: ?ProtocolInterface) callconv(cc) Status, + _reinstallProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, old_interface: ?ProtocolInterface, new_interface: ?ProtocolInterface) callconv(cc) Status, + _uninstallProtocolInterface: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: ?ProtocolInterface) callconv(cc) Status, // this function is deprecated, it will not be bound. _handleProtocol: *const fn (handle: Handle, protocol: *align(8) const Guid, interface: *?ProtocolInterface) callconv(cc) Status, @@ -56,15 +56,15 @@ pub const BootServices = extern struct { reserved: *const anyopaque, _registerProtocolNotify: *const fn (protocol: *align(8) const Guid, event: Event, registration: *RegistrationValue) callconv(cc) Status, - _locateHandle: *const fn (search_type: LocateSearchType.Enum, protocol: ?*align(8) const Guid, search_key: ?RegistrationValue, buffer_size: *usize, buffer: [*]Handle) callconv(cc) Status, - _locateDevicePath: *const fn (protocol: *align(8) const Guid, device_path: **const DevicePathProtocol, device: *Handle) callconv(cc) Status, + _locateHandle: *const fn (search_type: LocateSearchType.Enum, protocol: ?*align(8) const Guid, search_key: ?RegistrationValue, buffer_size: *usize, buffer: ?[*]Handle) callconv(cc) Status, + _locateDevicePath: *const fn (protocol: *align(8) const Guid, device_path: *?*const DevicePathProtocol, device: *?Handle) callconv(cc) Status, _installConfigurationTable: *const fn (guid: *align(8) const Guid, table: ?*const anyopaque) callconv(cc) Status, _loadImage: *const fn (boot_policy: bool, parent_image_handle: Handle, device_path: ?*const DevicePathProtocol, source_buffer: ?[*]const u8, source_size: usize, image_handle: *?Handle) callconv(cc) Status, _startImage: *const fn (image_handle: Handle, exit_data_size: ?*usize, exit_data: ?*[*]const u16) callconv(cc) Status, _exit: *const fn (image_handle: Handle, exit_status: Status, exit_data_size: usize, exit_data: ?[*]const u16) callconv(cc) Status, _unloadImage: *const fn (image_handle: Handle) callconv(cc) Status, - _exitBootServices: *const fn (image_handle: Handle, map_key: usize) callconv(cc) Status, + _exitBootServices: *const fn (image_handle: Handle, map_key: MemoryMap.Key) callconv(cc) Status, _getNextMonotonicCount: *const fn (count: *u64) callconv(cc) Status, _stall: *const fn (microseconds: usize) callconv(cc) Status, @@ -291,12 +291,12 @@ pub const BootServices = extern struct { /// The event to check. event: Event, ) !bool { - var status: Status = self._checkEvent(event); - switch (status) { - .success => return true, - .not_ready => return false, - else => return status.err(), - } + self._checkEvent(event).err() catch |err| switch (err) { + error.NotReady => return false, + else => |e| return e, + }; + + return true; } /// How the timer is to be set. @@ -510,7 +510,7 @@ pub const BootServices = extern struct { .map = @ptrCast(list.ptr), .size = list.len * @sizeOf(bits.MemoryDescriptor), .allocated_size = list.len * @sizeOf(bits.MemoryDescriptor), - .key = 0, + .key = @enumFromInt(0), .descriptor_size = @sizeOf(bits.MemoryDescriptor), .descriptor_version = bits.MemoryDescriptor.revision, }; @@ -558,14 +558,13 @@ pub const BootServices = extern struct { var descriptor_size: usize = 0; var descriptor_version: u32 = 0; - switch (self._getMemoryMap(&mmap_size, null, &mmap_key, &descriptor_size, &descriptor_version)) { - .buffer_too_small => return mmap_size, - .invalid_parameter => return null, - else => |s| { - try s.err(); - unreachable; - }, - } + self._getMemoryMap(&mmap_size, null, &mmap_key, &descriptor_size, &descriptor_version).err() catch |err| switch (err) { + error.BufferTooSmall => {}, + error.InvalidParameter => return null, + else => |e| return e, + }; + + return mmap_size; } /// Fetches the current memory map. @@ -658,7 +657,7 @@ pub const BootServices = extern struct { ) !Handle { var new_handle: ?Handle = handle; try self._installProtocolInterface(&new_handle, protocol_guid, interface_type, interface).err(); - return new_handle; + return new_handle.?; } /// Uninstalls a protocol interface from a device handle. @@ -745,11 +744,13 @@ pub const BootServices = extern struct { .by_protocol => |protocol_guid| self._locateHandle(search_type, protocol_guid, null, &buffer_size, null), }; - switch (status) { - .buffer_too_small => return buffer_size, - .not_found => return null, - else => return status.err(), - } + status.err() catch |err| switch (err) { + error.BufferTooSmall => {}, + error.NotFound => return null, + else => |e| return e, + }; + + return buffer_size; } /// Returns an array of handles that support a specified protocol. @@ -762,22 +763,23 @@ pub const BootServices = extern struct { /// The type of search to perform. search_type: LocateSearchType, /// The buffer in which to return the array of handles. - buffer: [*]u8, + buffer: []align(@alignOf(Handle)) u8, ) !?[]Handle { var handle_buffer: [*]Handle = @ptrCast(buffer.ptr); var buffer_size: usize = buffer.len; const status = switch (search_type) { - .all => self._locateHandle(search_type, null, null, &buffer_size, &handle_buffer), - .by_notify => |search_key| self._locateHandle(search_type, null, search_key, &buffer_size, &handle_buffer), - .by_protocol => |protocol_guid| self._locateHandle(search_type, protocol_guid, null, &buffer_size, &handle_buffer), + .all => self._locateHandle(search_type, null, null, &buffer_size, handle_buffer), + .by_notify => |search_key| self._locateHandle(search_type, null, search_key, &buffer_size, handle_buffer), + .by_protocol => |protocol_guid| self._locateHandle(search_type, protocol_guid, null, &buffer_size, handle_buffer), }; - switch (status) { - .success => return handle_buffer[0..@divExact(buffer_size, @sizeOf(Handle))], - .not_found => return null, - else => return status.err(), - } + status.err() catch |err| switch (err) { + error.NotFound => return null, + else => |e| return e, + }; + + return handle_buffer[0..@divExact(buffer_size, @sizeOf(Handle))]; } /// A tuple of a located device path containing the handle to the device and the remaining device path. @@ -792,12 +794,13 @@ pub const BootServices = extern struct { device_path: *const DevicePathProtocol, ) !LocatedDevicePath { var handle: ?Handle = null; - var path: *const DevicePathProtocol = device_path; - switch (self._locateDevicePath(protocol_guid, &handle, &path)) { - .success => return .{ handle, path }, - .not_found => return .{ null, path }, - else => |status| return status.err(), - } + var path: ?*const DevicePathProtocol = device_path; + self._locateDevicePath(protocol_guid, &path, &handle).err() catch |err| switch (err) { + error.NotFound => {}, + else => |e| return e, + }; + + return .{ handle, path.? }; } pub const OpenProtocolAttributes = packed struct(u32) { @@ -907,11 +910,12 @@ pub const BootServices = extern struct { ) !?[]const ProtocolInformationEntry { var entry_buffer: [*]const ProtocolInformationEntry = undefined; var entry_count: usize = 0; - switch (self._openProtocolInformation(handle, protocol_guid, &entry_buffer, &entry_count)) { - .success => return entry_buffer[0..entry_count], - .not_found => return null, - else => |status| return status.err(), - } + self._openProtocolInformation(handle, protocol_guid, &entry_buffer, &entry_count).err() catch |err| switch (err) { + error.NotFound => return null, + else => |e| return e, + }; + + return entry_buffer[0..entry_count]; } /// Connects one or more drivers to a controller. @@ -919,8 +923,9 @@ pub const BootServices = extern struct { self: *const BootServices, /// The handle of the controller to connect. controller_handle: Handle, - /// The handle of the driver image that is connecting to the controller. - driver_image_handle: ?Handle, + /// A pointer to an ordered list handles that support the DriverBindingProtocol. The list is terminated by a + /// null handle value. + driver_image_handle: ?[*:null]?Handle, /// The remaining device path. /// /// If null, then handles for all children of the controller will be created. @@ -956,7 +961,7 @@ pub const BootServices = extern struct { /// The handle for the protocol interface that is being queried. handle: Handle, ) ![]const *const Guid { - var protocol_buffer: [*]const *const Guid = undefined; + var protocol_buffer: [*]*align(8) const Guid = undefined; var protocol_buffer_count: usize = 0; try self._protocolsPerHandle(handle, &protocol_buffer, &protocol_buffer_count).err(); return protocol_buffer[0..protocol_buffer_count]; @@ -999,14 +1004,12 @@ pub const BootServices = extern struct { options: LocateProtocolOptions, ) !?*const Protocol { var interface: ?ProtocolInterface = undefined; - switch (self._locateProtocol(options.protocol_guid orelse &Protocol.guid, options.registration, &interface)) { - .success => return @ptrCast(@alignCast(interface)), - .not_found => return null, - else => |status| { - try status.err(); - unreachable; - }, - } + self._locateProtocol(options.protocol_guid orelse &Protocol.guid, options.registration, &interface).err() catch |err| switch (err) { + error.NotFound => return null, + else => |e| return e, + }; + + return @ptrCast(@alignCast(interface)); } /// Installs one or more protocol interfaces into the boot services environment. @@ -1060,7 +1063,7 @@ pub const BootServices = extern struct { ) !Handle { var image_handle: ?Handle = null; try self._loadImage(boot_policy, parent_image_handle, device_path, source_buffer, source_size, &image_handle).err(); - return image_handle; + return image_handle.?; } pub const ImageReturn = struct { Status, []const u16 }; @@ -1075,7 +1078,7 @@ pub const BootServices = extern struct { image_handle: Handle, ) ImageReturn { var exit_data_size: usize = 0; - var exit_data: *[*]u16 = null; + var exit_data: [*]const u16 = undefined; const status = self._startImage(image_handle, &exit_data_size, &exit_data); return .{ status, exit_data[0 .. exit_data_size / 2] }; diff --git a/lib/std/os/uefi/table/configuration.zig b/lib/std/os/uefi/table/configuration.zig index da5f7f155357..d1db4591b736 100644 --- a/lib/std/os/uefi/table/configuration.zig +++ b/lib/std/os/uefi/table/configuration.zig @@ -91,7 +91,7 @@ pub const Configuration = extern struct { /// This table should be published by a platform if it no longer supports all EFI runtime services once /// `exitBootServices()` has been called by the OS. Note that this is merely a hint to the OS, which it is free to /// ignore, as any unsupported runtime services will return `error.Unsupported` if called. -pub const RtPropertiesTable = extern struct { +pub const RtProperties = extern struct { pub const Supported = packed struct(u32) { get_time: bool, set_time: bool, @@ -184,7 +184,7 @@ pub const MemoryAttributes = extern struct { const offset = index * self.descriptor_size; - const addr = @intFromPtr(self.table) + @sizeOf(self) + offset; + const addr = @intFromPtr(self) + @sizeOf(MemoryAttributes) + offset; return @ptrFromInt(addr); } diff --git a/lib/std/os/uefi/table/runtime_services.zig b/lib/std/os/uefi/table/runtime_services.zig index c7619bf0964f..6d1c79f52092 100644 --- a/lib/std/os/uefi/table/runtime_services.zig +++ b/lib/std/os/uefi/table/runtime_services.zig @@ -28,17 +28,17 @@ pub const RuntimeServices = extern struct { _setVirtualAddressMap: *const fn (mmap_size: usize, descriptor_size: usize, descriptor_version: u32, virtual_map: *const anyopaque) callconv(cc) Status, _convertPointer: *const fn (debug_disposition: usize, address: *?*anyopaque) callconv(cc) Status, - _getVariable: *const fn (var_name: [*:0]const u16, vendor_guid: *align(8) const Guid, attributes: ?*u32, data_size: *usize, data: ?[*]u8) callconv(cc) Status, + _getVariable: *const fn (var_name: [*:0]const u16, vendor_guid: *align(8) const Guid, attributes: ?*VariableAttributes, data_size: *usize, data: ?[*]u8) callconv(cc) Status, _getNextVariableName: *const fn (var_name_size: *usize, var_name: [*:0]u16, vendor_guid: *align(8) Guid) callconv(cc) Status, - _setVariable: *const fn (var_name: [*:0]const u16, vendor_guid: *align(8) const Guid, attributes: u32, data_size: usize, data: *anyopaque) callconv(cc) Status, + _setVariable: *const fn (var_name: [*:0]const u16, vendor_guid: *align(8) const Guid, attributes: VariableAttributes, data_size: usize, data: *anyopaque) callconv(cc) Status, _getNextHighMonotonicCount: *const fn (high_count: *u32) callconv(cc) Status, _resetSystem: *const fn (reset_type: ResetType, reset_status: Status, data_size: usize, reset_data: ?*const anyopaque) callconv(cc) noreturn, _updateCapsule: *const fn (capsule_header_array: [*]*CapsuleHeader, capsule_count: usize, scatter_gather_list: bits.PhysicalAddress) callconv(cc) Status, - _queryCapsuleCapabilities: *const fn (capsule_header_array: [*]*CapsuleHeader, capsule_count: usize, maximum_capsule_size: *usize, resetType: *ResetType) callconv(cc) Status, + _queryCapsuleCapabilities: *const fn (capsule_header_array: [*]*const CapsuleHeader, capsule_count: usize, maximum_capsule_size: *usize, resetType: *ResetType) callconv(cc) Status, - _queryVariableInfo: *const fn (attributes: *u32, maximum_variable_storage_size: *u64, remaining_variable_storage_size: *u64, maximum_variable_size: *u64) callconv(cc) Status, + _queryVariableInfo: *const fn (attributes: *VariableAttributes, maximum_variable_storage_size: *u64, remaining_variable_storage_size: *u64, maximum_variable_size: *u64) callconv(cc) Status, /// A tuple of EFI variable data and its attributes. pub const Variable = struct { []u8, VariableAttributes }; @@ -53,11 +53,12 @@ pub const RuntimeServices = extern struct { ) !?usize { var data_size: usize = 0; - switch (self._getVariable(variable_name, vendor_guid, null, &data_size, null)) { - .success => return data_size, - .not_found => return null, - else => |status| return status.err(), - } + self._getVariable(variable_name, vendor_guid, null, &data_size, null).err() catch |err| switch (err) { + error.NotFound => return null, + else => |e| return e, + }; + + return data_size; } /// Returns the value of a variable. @@ -69,16 +70,19 @@ pub const RuntimeServices = extern struct { variable_name: [*:0]const u16, /// A unique identifier for the vendor. vendor_guid: *align(8) const Guid, + /// The buffer to store the variable data. + buffer: []u8, ) !?Variable { - var attributes: u32 = 0; - var data_size: usize = 0; - var data: ?[*]u8 = null; + var attributes: VariableAttributes = @bitCast(@as(u32, 0)); + var data_size: usize = buffer.len; + var data: [*]u8 = buffer.ptr; - switch (self._getVariable(variable_name, vendor_guid, &attributes, &data_size, data)) { - .success => return .{ data[0..data_size], attributes }, - .not_found => return null, - else => |status| return status.err(), - } + self._getVariable(variable_name, vendor_guid, &attributes, &data_size, data).err() catch |err| switch (err) { + error.NotFound => return null, + else => |e| return e, + }; + + return .{ data[0..data_size], attributes }; } /// Enumerates the current variable names. @@ -98,11 +102,12 @@ pub const RuntimeServices = extern struct { /// On output, returns the GUID of the next vendor. vendor_guid: *align(8) Guid, ) !bool { - switch (self._getNextVariableName(variable_name_size, variable_name, vendor_guid)) { - .success => return true, - .not_found => return false, - else => |status| return status.err(), - } + self._getNextVariableName(variable_name_size, variable_name, vendor_guid).err() catch |err| switch (err) { + error.NotFound => return false, + else => |e| return e, + }; + + return true; } /// The attributes of a EFI variable. @@ -150,10 +155,10 @@ pub const RuntimeServices = extern struct { /// The data to set. data: []u8, ) !void { - switch (self._setVariable(variable_name, vendor_guid, attributes, data.len, data.ptr)) { - .success, .not_found => return, - else => |status| return status.err(), - } + self._setVariable(variable_name, vendor_guid, attributes, data.len, data.ptr).err() catch |err| switch (err) { + error.NotFound => return, + else => |e| return e, + }; } /// Returns the current time and date information. @@ -161,10 +166,8 @@ pub const RuntimeServices = extern struct { self: *const RuntimeServices, ) !bits.Time { var time: bits.Time = undefined; - switch (self._getTime(&time, null)) { - .success => return time, - else => |status| return status.err(), - } + try self._getTime(&time, null).err(); + return time; } /// Returns the time-keeping capabilities of the hardware platform. @@ -173,10 +176,8 @@ pub const RuntimeServices = extern struct { ) !bits.TimeCapabilities { var time: bits.Time = undefined; var capabilities: bits.TimeCapabilities = undefined; - switch (self._getTime(&time, &capabilities)) { - .success => return capabilities, - else => |status| return status.err(), - } + try self._getTime(&time, &capabilities).err(); + return capabilities; } /// The state of the wakeup alarm clock. @@ -198,10 +199,8 @@ pub const RuntimeServices = extern struct { var enabled: bool = false; var pending: bool = false; var time: bits.Time = undefined; - switch (self._getWakeupTime(&enabled, &pending, &time)) { - .success => return .{ .enabled = enabled, .pending = pending, .time = time }, - else => |status| return status.err(), - } + try self._getWakeupTime(&enabled, &pending, &time).err(); + return .{ .enabled = enabled, .pending = pending, .time = time }; } /// Sets the current wakeup alarm clock setting. @@ -212,10 +211,7 @@ pub const RuntimeServices = extern struct { time: ?bits.Time, ) !void { const enabled: bool = time != null; - switch (self._setWakeupTime(enabled, if (time) |*t| &t else null)) { - .success => return, - else => |status| return status.err(), - } + try self._setWakeupTime(enabled, if (time) |*t| t else null).err(); } /// Changes the runtime addresssing mode of EFI firmware from physical to virtual. @@ -226,10 +222,7 @@ pub const RuntimeServices = extern struct { /// The memory map of the address space. map: MemoryMap, ) !void { - switch (self._setVirtualAddressMap(map.size, map.descriptor_size, map.descriptor_version, map.map)) { - .success => return, - else => |status| return status.err(), - } + try self._setVirtualAddressMap(map.size, map.descriptor_size, map.descriptor_version, map.map).err(); } /// Converts a pointer from physical to virtual addressing mode. @@ -251,12 +244,10 @@ pub const RuntimeServices = extern struct { // This pointer needs to be the least qualified form because that's how we need to pass it to UEFI. // This is completely safe, because UEFI never dereferences the pointer. var address: ?*anyopaque = @volatileCast(@constCast(@ptrCast(pointer))); + try self._convertPointer(disposition, &address).err(); - switch (self._convertPointer(disposition, &address)) { - // Requalify the pointer and cast it back to the type we originally had. - .success => return @alignCast(@ptrCast(address)), - else => |status| return status.err(), - } + // Requalify the pointer and cast it back to the type we originally had. + return @alignCast(@ptrCast(address)); } pub const ResetType = enum(u32) { @@ -302,10 +293,9 @@ pub const RuntimeServices = extern struct { self: *const RuntimeServices, ) !u32 { var high_count: u32 = undefined; - switch (self._getNextHighMonotonicCount(&high_count)) { - .success => return high_count, - else => |status| return status.err(), - } + try self._getNextHighMonotonicCount(&high_count).err(); + + return high_count; } pub const CapsuleHeader = extern struct { @@ -372,7 +362,7 @@ pub const RuntimeServices = extern struct { pub fn updateCapsule( self: *const RuntimeServices, /// An array of pointers to capsule headers. - capsule_headers: []*const CapsuleHeader, + capsule_headers: []*CapsuleHeader, /// A physical pointer to a list of `CapsuleBlockDescriptor` structures that describe the location in physical /// memory of a set of capsules. This list must be in the same order as the capsules pointed to by /// `capsule_headers`. @@ -392,10 +382,9 @@ pub const RuntimeServices = extern struct { ) !CapsuleCapabilities { var maximum_capsule_size: usize = undefined; var reset_type: ResetType = undefined; - switch (self._queryCapsuleCapabilities(capsule_headers.ptr, capsule_headers.len, &maximum_capsule_size, &reset_type)) { - .success => return .{ maximum_capsule_size, reset_type }, - else => |status| return status.err(), - } + try self._queryCapsuleCapabilities(capsule_headers.ptr, capsule_headers.len, &maximum_capsule_size, &reset_type).err(); + + return .{ maximum_capsule_size, reset_type }; } pub const signature: u64 = 0x56524553544e5552; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index a64fdcb1af3e..543f0bc8a895 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -2008,6 +2008,8 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { const result = out_buffer[0..path.len]; @memcpy(result, path); return result; + } else if (native_os == .uefi) { + return uefi.posix.getcwd(out_buffer); } const err: E = if (builtin.link_libc) err: { @@ -2062,8 +2064,10 @@ pub const SymLinkError = error{ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError!void { if (native_os == .windows) { @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); + } else if (native_os == .uefi) { + @compileError("symlink is not supported on UEFI"); } else if (native_os == .wasi and !builtin.link_libc) { - return symlinkat(target_path, wasi.AT.FDCWD, sym_link_path); + return symlinkat(target_path, AT.FDCWD, sym_link_path); } const target_path_c = try toPosixPath(target_path); const sym_link_path_c = try toPosixPath(sym_link_path); @@ -2075,8 +2079,10 @@ pub fn symlink(target_path: []const u8, sym_link_path: []const u8) SymLinkError! pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLinkError!void { if (native_os == .windows) { @compileError("symlink is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); + } else if (native_os == .uefi) { + @compileError("symlink is not supported on UEFI"); } else if (native_os == .wasi and !builtin.link_libc) { - return symlinkatZ(target_path, fs.cwd().fd, sym_link_path); + return symlinkatZ(target_path, AT.FDCWD, sym_link_path); } switch (errno(system.symlink(target_path, sym_link_path))) { .SUCCESS => return, @@ -2114,6 +2120,8 @@ pub fn symlinkZ(target_path: [*:0]const u8, sym_link_path: [*:0]const u8) SymLin pub fn symlinkat(target_path: []const u8, newdirfd: fd_t, sym_link_path: []const u8) SymLinkError!void { if (native_os == .windows) { @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); + } else if (native_os == .uefi) { + @compileError("symlinkat is not supported on UEFI"); } else if (native_os == .wasi and !builtin.link_libc) { return symlinkatWasi(target_path, newdirfd, sym_link_path); } @@ -2153,6 +2161,8 @@ pub fn symlinkatWasi(target_path: []const u8, newdirfd: fd_t, sym_link_path: []c pub fn symlinkatZ(target_path: [*:0]const u8, newdirfd: fd_t, sym_link_path: [*:0]const u8) SymLinkError!void { if (native_os == .windows) { @compileError("symlinkat is not supported on Windows; use std.os.windows.CreateSymbolicLink instead"); + } else if (native_os == .uefi) { + @compileError("symlinkat is not supported on UEFI"); } else if (native_os == .wasi and !builtin.link_libc) { return symlinkat(mem.sliceTo(target_path, 0), newdirfd, mem.sliceTo(sym_link_path, 0)); } @@ -2203,6 +2213,8 @@ pub const LinkError = UnexpectedError || error{ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkError!void { if (native_os == .wasi and !builtin.link_libc) { return link(mem.sliceTo(oldpath, 0), mem.sliceTo(newpath, 0), flags); + } else if (native_os == .uefi) { + @compileError("link is not supported on UEFI"); } switch (errno(system.link(oldpath, newpath, flags))) { .SUCCESS => return, @@ -2232,8 +2244,11 @@ pub fn linkZ(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: i32) LinkErr /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn link(oldpath: []const u8, newpath: []const u8, flags: i32) LinkError!void { + if (native_os == .uefi) { + @compileError("link is not supported on UEFI"); + } if (native_os == .wasi and !builtin.link_libc) { - return linkat(wasi.AT.FDCWD, oldpath, wasi.AT.FDCWD, newpath, flags) catch |err| switch (err) { + return linkat(AT.FDCWD, oldpath, AT.FDCWD, newpath, flags) catch |err| switch (err) { error.NotDir => unreachable, // link() does not support directories else => |e| return e, }; @@ -2254,6 +2269,9 @@ pub fn linkatZ( newpath: [*:0]const u8, flags: i32, ) LinkatError!void { + if (native_os == .uefi) { + @compileError("linkat is not supported on UEFI"); + } if (native_os == .wasi and !builtin.link_libc) { return linkat(olddir, mem.sliceTo(oldpath, 0), newdir, mem.sliceTo(newpath, 0), flags); } @@ -2292,6 +2310,9 @@ pub fn linkat( newpath: []const u8, flags: i32, ) LinkatError!void { + if (native_os == .uefi) { + @compileError("linkat is not supported on UEFI"); + } if (native_os == .wasi and !builtin.link_libc) { const old: RelativePathWasi = .{ .dir_fd = olddir, .relative_path = oldpath }; const new: RelativePathWasi = .{ .dir_fd = newdir, .relative_path = newpath }; @@ -2369,8 +2390,8 @@ pub const UnlinkError = error{ /// On other platforms, `file_path` is an opaque sequence of bytes with no particular encoding. /// See also `unlinkZ`. pub fn unlink(file_path: []const u8) UnlinkError!void { - if (native_os == .wasi and !builtin.link_libc) { - return unlinkat(wasi.AT.FDCWD, file_path, 0) catch |err| switch (err) { + if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { + return unlinkat(AT.FDCWD, file_path, 0) catch |err| switch (err) { error.DirNotEmpty => unreachable, // only occurs when targeting directories else => |e| return e, }; @@ -2388,7 +2409,7 @@ pub fn unlinkZ(file_path: [*:0]const u8) UnlinkError!void { if (native_os == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); return unlinkW(file_path_w.span()); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return unlink(mem.sliceTo(file_path, 0)); } switch (errno(system.unlink(file_path))) { @@ -2438,6 +2459,8 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo return unlinkatW(dirfd, file_path_w.span(), flags); } else if (native_os == .wasi and !builtin.link_libc) { return unlinkatWasi(dirfd, file_path, flags); + } else if (native_os == .uefi) { + return uefi.posix.unlinkat(dirfd, file_path, flags); } else { const file_path_c = try toPosixPath(file_path); return unlinkatZ(dirfd, &file_path_c, flags); @@ -2482,7 +2505,7 @@ pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatEr if (native_os == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path_c); return unlinkatW(dirfd, file_path_w.span(), flags); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return unlinkat(dirfd, mem.sliceTo(file_path_c, 0), flags); } switch (errno(system.unlinkat(dirfd, file_path_c, flags))) { @@ -2563,8 +2586,8 @@ pub const RenameError = error{ /// On WASI, both paths should be encoded as valid UTF-8. /// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn rename(old_path: []const u8, new_path: []const u8) RenameError!void { - if (native_os == .wasi and !builtin.link_libc) { - return renameat(wasi.AT.FDCWD, old_path, wasi.AT.FDCWD, new_path); + if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { + return renameat(AT.FDCWD, old_path, AT.FDCWD, new_path); } else if (native_os == .windows) { const old_path_w = try windows.sliceToPrefixedFileW(null, old_path); const new_path_w = try windows.sliceToPrefixedFileW(null, new_path); @@ -2582,7 +2605,7 @@ pub fn renameZ(old_path: [*:0]const u8, new_path: [*:0]const u8) RenameError!voi const old_path_w = try windows.cStrToPrefixedFileW(null, old_path); const new_path_w = try windows.cStrToPrefixedFileW(null, new_path); return renameW(old_path_w.span().ptr, new_path_w.span().ptr); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return rename(mem.sliceTo(old_path, 0), mem.sliceTo(new_path, 0)); } switch (errno(system.rename(old_path, new_path))) { @@ -2638,6 +2661,8 @@ pub fn renameat( const old: RelativePathWasi = .{ .dir_fd = old_dir_fd, .relative_path = old_path }; const new: RelativePathWasi = .{ .dir_fd = new_dir_fd, .relative_path = new_path }; return renameatWasi(old, new); + } else if (native_os == .uefi) { + return uefi.posix.renameat(old_dir_fd, old_path, new_dir_fd, new_path); } else { const old_path_c = try toPosixPath(old_path); const new_path_c = try toPosixPath(new_path); @@ -2696,7 +2721,7 @@ pub fn renameatZ( const old_path_w = try windows.cStrToPrefixedFileW(old_dir_fd, old_path); const new_path_w = try windows.cStrToPrefixedFileW(new_dir_fd, new_path); return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return renameat(old_dir_fd, mem.sliceTo(old_path, 0), new_dir_fd, mem.sliceTo(new_path, 0)); } @@ -2844,6 +2869,8 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); } else if (native_os == .wasi and !builtin.link_libc) { return mkdiratWasi(dir_fd, sub_dir_path, mode); + } else if (native_os == .uefi) { + return uefi.posix.mkdirat(dir_fd, sub_dir_path, mode); } else { const sub_dir_path_c = try toPosixPath(sub_dir_path); return mkdiratZ(dir_fd, &sub_dir_path_c, mode); @@ -2879,7 +2906,7 @@ pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirErr if (native_os == .windows) { const sub_dir_path_w = try windows.cStrToPrefixedFileW(dir_fd, sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span(), mode); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return mkdirat(dir_fd, mem.sliceTo(sub_dir_path, 0), mode); } switch (errno(system.mkdirat(dir_fd, sub_dir_path, mode))) { @@ -2957,8 +2984,8 @@ pub const MakeDirError = error{ /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn mkdir(dir_path: []const u8, mode: u32) MakeDirError!void { - if (native_os == .wasi and !builtin.link_libc) { - return mkdirat(wasi.AT.FDCWD, dir_path, mode); + if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { + return mkdirat(AT.FDCWD, dir_path, mode); } else if (native_os == .windows) { const dir_path_w = try windows.sliceToPrefixedFileW(null, dir_path); return mkdirW(dir_path_w.span(), mode); @@ -2976,7 +3003,7 @@ pub fn mkdirZ(dir_path: [*:0]const u8, mode: u32) MakeDirError!void { if (native_os == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); return mkdirW(dir_path_w.span(), mode); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return mkdir(mem.sliceTo(dir_path, 0), mode); } switch (errno(system.mkdir(dir_path, mode))) { @@ -3045,8 +3072,8 @@ pub const DeleteDirError = error{ /// On WASI, `dir_path` should be encoded as valid UTF-8. /// On other platforms, `dir_path` is an opaque sequence of bytes with no particular encoding. pub fn rmdir(dir_path: []const u8) DeleteDirError!void { - if (native_os == .wasi and !builtin.link_libc) { - return unlinkat(wasi.AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { + if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { + return unlinkat(AT.FDCWD, dir_path, AT.REMOVEDIR) catch |err| switch (err) { error.FileSystem => unreachable, // only occurs when targeting files error.IsDir => unreachable, // only occurs when targeting files else => |e| return e, @@ -3068,7 +3095,7 @@ pub fn rmdirZ(dir_path: [*:0]const u8) DeleteDirError!void { if (native_os == .windows) { const dir_path_w = try windows.cStrToPrefixedFileW(null, dir_path); return rmdirW(dir_path_w.span()); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return rmdir(mem.sliceTo(dir_path, 0)); } switch (errno(system.rmdir(dir_path))) { @@ -3185,6 +3212,10 @@ pub const FchdirError = error{ pub fn fchdir(dirfd: fd_t) FchdirError!void { if (dirfd == AT.FDCWD) return; + if (native_os == .uefi) { + return uefi.posix.fchdir(dirfd); + } + while (true) { switch (errno(system.fchdir(dirfd))) { .SUCCESS => return, @@ -3231,8 +3262,8 @@ pub const ReadLinkError = error{ /// On WASI, the result is encoded as UTF-8. /// On other platforms, the result is an opaque sequence of bytes with no particular encoding. pub fn readlink(file_path: []const u8, out_buffer: []u8) ReadLinkError![]u8 { - if (native_os == .wasi and !builtin.link_libc) { - return readlinkat(wasi.AT.FDCWD, file_path, out_buffer); + if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { + return readlinkat(AT.FDCWD, file_path, out_buffer); } else if (native_os == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(null, file_path); return readlinkW(file_path_w.span(), out_buffer); @@ -3254,7 +3285,7 @@ pub fn readlinkZ(file_path: [*:0]const u8, out_buffer: []u8) ReadLinkError![]u8 if (native_os == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(null, file_path); return readlinkW(file_path_w.span(), out_buffer); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return readlink(mem.sliceTo(file_path, 0), out_buffer); } const rc = system.readlink(file_path, out_buffer.ptr, out_buffer.len); @@ -3294,6 +3325,9 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) ReadLink const file_path_w = try windows.sliceToPrefixedFileW(dirfd, file_path); return readlinkatW(dirfd, file_path_w.span(), out_buffer); } + if (native_os == .uefi) { + return uefi.posix.readlinkat(dirfd, file_path, out_buffer); + } const file_path_c = try toPosixPath(file_path); return readlinkatZ(dirfd, &file_path_c, out_buffer); } @@ -3332,7 +3366,7 @@ pub fn readlinkatZ(dirfd: fd_t, file_path: [*:0]const u8, out_buffer: []u8) Read if (native_os == .windows) { const file_path_w = try windows.cStrToPrefixedFileW(dirfd, file_path); return readlinkatW(dirfd, file_path_w.span(), out_buffer); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return readlinkat(dirfd, mem.sliceTo(file_path, 0), out_buffer); } const rc = system.readlinkat(dirfd, file_path, out_buffer.ptr, out_buffer.len); @@ -3446,6 +3480,9 @@ pub fn isatty(handle: fd_t) bool { return true; } + if (native_os == .uefi) { + return uefi.posix.isatty(handle); + } if (native_os == .linux) { while (true) { var wsz: linux.winsize = undefined; @@ -4346,6 +4383,8 @@ pub fn fstatat(dirfd: fd_t, pathname: []const u8, flags: u32) FStatAtError!Stat return Stat.fromFilestat(filestat); } else if (native_os == .windows) { @compileError("fstatat is not yet implemented on Windows"); + } else if (native_os == .uefi) { + return uefi.posix.fstatat(dirfd, pathname, flags); } else { const pathname_c = try toPosixPath(pathname); return fstatatZ(dirfd, &pathname_c, flags); @@ -4360,6 +4399,8 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S .SYMLINK_FOLLOW = (flags & AT.SYMLINK_NOFOLLOW) == 0, }); return Stat.fromFilestat(filestat); + } else if (native_os == .uefi) { + return fstatat(dirfd, mem.sliceTo(pathname, 0), flags); } const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat; @@ -4758,7 +4799,7 @@ pub fn access(path: []const u8, mode: u32) AccessError!void { _ = try windows.GetFileAttributesW(path_w.span().ptr); return; } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { - return faccessat(wasi.AT.FDCWD, path, mode, 0); + return faccessat(AT.FDCWD, path, mode, 0); } const path_c = try toPosixPath(path); return accessZ(&path_c, mode); @@ -4860,7 +4901,7 @@ pub fn faccessatZ(dirfd: fd_t, path: [*:0]const u8, mode: u32, flags: u32) Acces if (native_os == .windows) { const path_w = try windows.cStrToPrefixedFileW(dirfd, path); return faccessatW(dirfd, path_w.span().ptr); - } else if (native_os == .wasi and !builtin.link_libc) { + } else if (native_os == .wasi and !builtin.link_libc or native_os == .uefi) { return faccessat(dirfd, mem.sliceTo(path, 0), mode, flags); } switch (errno(system.faccessat(dirfd, path, mode, flags))) { @@ -5222,7 +5263,7 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { return windows.SetFilePointerEx_CURRENT_get(fd); } if (native_os == .uefi) { - return uefi.lseek_CUR_get(fd); + return uefi.posix.lseek_CUR_get(fd); } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; @@ -5292,6 +5333,10 @@ pub const FlockError = error{ /// Depending on the operating system `flock` may or may not interact with /// `fcntl` locks made by other processes. pub fn flock(fd: fd_t, operation: i32) FlockError!void { + if (native_os == .uefi) { + return uefi.posix.flock(fd, operation); + } + while (true) { const rc = system.flock(fd, operation); switch (errno(rc)) { @@ -5747,6 +5792,9 @@ pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void { else => |err| return unexpectedErrno(err), } } + if (native_os == .uefi) { + return uefi.posix.futimens(fd, times); + } switch (errno(system.futimens(fd, times))) { .SUCCESS => return, @@ -5784,6 +5832,9 @@ pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { } pub fn uname() utsname { + if (native_os == .uefi) + return uefi.posix.uname(); + var uts: utsname = undefined; switch (errno(system.uname(&uts))) { .SUCCESS => return uts, diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 1020bef4b7b0..befc31333503 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -362,6 +362,9 @@ test "fstatat" { } test "readlinkat" { + if (native_os == .uefi) + return error.SkipZigTest; + var tmp = tmpDir(.{}); defer tmp.cleanup(); @@ -431,7 +434,7 @@ fn start2(ctx: *i32) u8 { } test "cpu count" { - if (native_os == .wasi) return error.SkipZigTest; + if (native_os == .wasi or native_os == .uefi) return error.SkipZigTest; const cpu_count = try Thread.getCpuCount(); try expect(cpu_count >= 1); @@ -471,7 +474,7 @@ test "getcwd" { } test "sigaltstack" { - if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var st: posix.stack_t = undefined; try posix.sigaltstack(null, &st); @@ -533,7 +536,7 @@ test "dl_iterate_phdr" { } test "gethostname" { - if (native_os == .windows or native_os == .wasi) + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var buf: [posix.HOST_NAME_MAX]u8 = undefined; @@ -542,7 +545,7 @@ test "gethostname" { } test "pipe" { - if (native_os == .windows or native_os == .wasi) + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; const fds = try posix.pipe(); @@ -586,7 +589,7 @@ test "memfd_create" { } test "mmap" { - if (native_os == .windows or native_os == .wasi) + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -696,7 +699,7 @@ test "getenv" { } test "fcntl" { - if (native_os == .windows or native_os == .wasi) + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -805,7 +808,7 @@ test "getrlimit and setrlimit" { } test "shutdown socket" { - if (native_os == .wasi) + if (native_os == .wasi or native_os == .uefi) return error.SkipZigTest; if (native_os == .windows) { _ = try std.os.windows.WSAStartup(2, 2); @@ -824,7 +827,7 @@ test "shutdown socket" { } test "sigaction" { - if (native_os == .wasi or native_os == .windows) + if (native_os == .wasi or native_os == .windows or native_os == .uefi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/7427 @@ -931,7 +934,7 @@ test "dup & dup2" { } test "writev longer than IOV_MAX" { - if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; + if (native_os == .windows or native_os == .wasi or native_os == .uefi) return error.SkipZigTest; var tmp = tmpDir(.{}); defer tmp.cleanup(); diff --git a/lib/std/time.zig b/lib/std/time.zig index f65d298d6728..04b25ff5be9c 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -163,7 +163,7 @@ pub const Instant = struct { // true if we should use clock_gettime() const is_posix = switch (builtin.os.tag) { - .windows, .uefi, .wasi => false, + .windows, .wasi => false, else => true, }; From 4a0f619ce270e3d7d09209ac4d54c0f252d22c36 Mon Sep 17 00:00:00 2001 From: Nameless Date: Fri, 19 Apr 2024 14:08:31 -0500 Subject: [PATCH 22/23] fix memcpy typo --- lib/std/os/uefi/posix.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/os/uefi/posix.zig b/lib/std/os/uefi/posix.zig index 2ac0cc5cb99c..e11cb4186de4 100644 --- a/lib/std/os/uefi/posix.zig +++ b/lib/std/os/uefi/posix.zig @@ -326,7 +326,7 @@ pub fn getcwd(out_buffer: []u8) std.posix.GetCwdError![]u8 { if (path.len > out_buffer.len) return error.NameTooLong; - @memcpy(out_buffer[0..path.len], out_buffer); + @memcpy(out_buffer[0..path.len], path); return out_buffer[0..path.len]; } @@ -516,7 +516,7 @@ pub fn readlinkat(dirfd: fd_t, file_path: []const u8, out_buffer: []u8) std.posi if (path.len > out_buffer.len) return error.NameTooLong; - @memcpy(out_buffer[0..path.len], out_buffer); + @memcpy(out_buffer[0..path.len], path); return path; } From ab302cb513904992ef01796f896f26bdae13f95d Mon Sep 17 00:00:00 2001 From: Tristan Ross Date: Fri, 19 Apr 2024 11:52:10 -0700 Subject: [PATCH 23/23] Make the compiler build for UEFI --- lib/std/Build/Cache.zig | 4 +- lib/std/child_process.zig | 22 +++-- lib/std/fs/Dir.zig | 19 +++++ lib/std/fs/get_app_data_dir.zig | 1 + lib/std/fs/path.zig | 6 +- lib/std/net.zig | 1 + lib/std/os/uefi.zig | 11 +++ lib/std/os/uefi/bits.zig | 2 +- lib/std/os/uefi/posix.zig | 2 +- lib/std/os/uefi/protocol.zig | 2 +- lib/std/os/uefi/protocol/media/file.zig | 2 +- lib/std/os/uefi/protocol/shell.zig | 70 ++++++++++++++++ lib/std/os/uefi/ucontext.zig | 107 ++++++++++++++++++++++++ lib/std/posix.zig | 2 +- lib/std/process.zig | 77 ++++++++++++++++- src/introspect.zig | 2 +- src/main.zig | 21 ++--- 17 files changed, 322 insertions(+), 29 deletions(-) create mode 100644 lib/std/os/uefi/protocol/shell.zig create mode 100644 lib/std/os/uefi/ucontext.zig diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 463fb911acea..8b211a33b45b 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -930,7 +930,7 @@ pub const Manifest = struct { // WASI does not currently support flock, so we bypass it here. // TODO: If/when flock is supported on WASI, this check should be removed. // See https://github.com/WebAssembly/wasi-filesystem/issues/2 - if (builtin.os.tag != .wasi or std.process.can_spawn or !builtin.single_threaded) { + if (builtin.os.tag != .wasi or builtin.os.tag != .uefi or std.process.can_spawn or !builtin.single_threaded) { const manifest_file = self.manifest_file.?; try manifest_file.downgradeLock(); } @@ -945,7 +945,7 @@ pub const Manifest = struct { // WASI does not currently support flock, so we bypass it here. // TODO: If/when flock is supported on WASI, this check should be removed. // See https://github.com/WebAssembly/wasi-filesystem/issues/2 - if (builtin.os.tag != .wasi or std.process.can_spawn or !builtin.single_threaded) { + if (builtin.os.tag != .wasi or builtin.os.tag != .uefi or std.process.can_spawn or !builtin.single_threaded) { const manifest_file = self.manifest_file.?; // Here we intentionally have a period where the lock is released, in case there are // other processes holding a shared lock. diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig index dcc00b77d5b8..06bd2bfe2ee1 100644 --- a/lib/std/child_process.zig +++ b/lib/std/child_process.zig @@ -19,7 +19,7 @@ const native_os = builtin.os.tag; pub const ChildProcess = struct { pub const Id = switch (native_os) { .windows => windows.HANDLE, - .wasi => void, + .wasi, .uefi => void, else => posix.pid_t, }; @@ -47,10 +47,16 @@ pub const ChildProcess = struct { stderr_behavior: StdIo, /// Set to change the user id when spawning the child process. - uid: if (native_os == .windows or native_os == .wasi) void else ?posix.uid_t, + uid: switch (native_os) { + .windows, .wasi, .uefi => void, + else => ?posix.uid_t, + }, /// Set to change the group id when spawning the child process. - gid: if (native_os == .windows or native_os == .wasi) void else ?posix.gid_t, + gid: switch (native_os) { + .windows, .wasi, .uefi => void, + else => ?posix.gid_t, + }, /// Set to change the current working directory when spawning the child process. cwd: ?[]const u8, @@ -169,8 +175,14 @@ pub const ChildProcess = struct { .term = null, .env_map = null, .cwd = null, - .uid = if (native_os == .windows or native_os == .wasi) {} else null, - .gid = if (native_os == .windows or native_os == .wasi) {} else null, + .uid = switch (native_os) { + .windows, .wasi, .uefi => {}, + else => null, + }, + .gid = switch (native_os) { + .windows, .wasi, .uefi => {}, + else => null, + }, .stdin = null, .stdout = null, .stderr = null, diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index 89b1d18b9cd5..da558ae67349 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -579,6 +579,22 @@ pub const Iterator = switch (native_os) { self.cookie = std.os.wasi.DIRCOOKIE_START; } }, + .uefi => struct { + dir: Dir, + + const Self = @This(); + + pub const Error = IteratorError; + + pub fn next(self: *Self) Error!?Entry { + _ = self; + return null; + } + + pub fn reset(self: *Self) void { + _ = self; + } + }, else => @compileError("unimplemented"), }; @@ -641,6 +657,9 @@ fn iterateImpl(self: Dir, first_iter_start_value: bool) Iterator { .end_index = 0, .buf = undefined, }, + .uefi => return Iterator{ + .dir = self, + }, else => @compileError("unimplemented"), } } diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index 1437a5fc0881..b52b88d4ed3b 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -51,6 +51,7 @@ pub fn getAppDataDir(allocator: mem.Allocator, appname: []const u8) GetAppDataDi else => return error.AppDataDirUnavailable, } }, + .uefi => return std.process.getCwdAlloc(allocator) catch error.AppDataDirUnavailable, else => @compileError("Unsupported OS"), } } diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index daa7355f0c36..cb0e521f4acf 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -505,6 +505,8 @@ fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8 pub fn resolve(allocator: Allocator, paths: []const []const u8) ![]u8 { if (native_os == .windows) { return resolveWindows(allocator, paths); + } else if (native_os == .uefi) { + return resolveUefi(allocator, paths); } else { return resolvePosix(allocator, paths); } @@ -916,7 +918,7 @@ test resolveUefi { // Keep relative paths relative. try testResolveUefi(&[_][]const u8{"a\\b"}, "a\\b"); try testResolveUefi(&[_][]const u8{"."}, "."); - try testResolveUefi(&[_][]const u8{".", "src\\test.zig", "..", "..\\test\\cases.zig"}, "test\\cases.zig"); + try testResolveUefi(&[_][]const u8{ ".", "src\\test.zig", "..", "..\\test\\cases.zig" }, "test\\cases.zig"); } fn testResolveWindows(paths: []const []const u8, expected: []const u8) !void { @@ -1487,7 +1489,7 @@ test relative { try testRelativePosix("/baz", "/baz-quux", "../baz-quux"); try testRelativeUefi("\\var\\lib", "\\var", ".."); - try testRelativeUefi("\\var\\lib", "\\bin", "..\\..\\bin"); + try testRelativeUefi("\\var\\lib", "\\bin", "..\\..\\bin"); try testRelativeUefi("\\var\\lib", "\\var\\lib", ""); try testRelativeUefi("\\var\\lib", "\\var\\apache", "..\\apache"); try testRelativeUefi("\\var\\", "\\var\\lib", "lib"); diff --git a/lib/std/net.zig b/lib/std/net.zig index 3126bfa93b69..b117499c113b 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -16,6 +16,7 @@ const windows = std.os.windows; // first release to support them. pub const has_unix_sockets = switch (native_os) { .windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false, + .uefi => true, else => true, }; diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 1d07da7bdef9..674f0bb81378 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -16,6 +16,7 @@ pub const PoolAllocator = allocator.PoolAllocator; pub const RawPoolAllocator = allocator.RawPoolAllocator; pub var global_page_allocator = PageAllocator{}; +pub var global_pool_allocator = PoolAllocator{}; /// The EFI image's handle that is passed to its entry point. pub var handle: bits.Handle = undefined; @@ -78,6 +79,16 @@ pub fn cwd() fd_t { return .none; } +pub const ListEntry = struct { + forward_link: ?*ListEntry, + backward_link: ?*ListEntry, +}; + +const uctx = @import("uefi/ucontext.zig"); +pub const getcontext = uctx.getcontext; +pub const ucontext_t = uctx.ucontext_t; +pub const REG = uctx.REG; + test { _ = table; _ = protocol; diff --git a/lib/std/os/uefi/bits.zig b/lib/std/os/uefi/bits.zig index 67cd5458705c..32f55af9d269 100644 --- a/lib/std/os/uefi/bits.zig +++ b/lib/std/os/uefi/bits.zig @@ -521,4 +521,4 @@ pub const FileSystemVolumeLabel = extern struct { test { std.testing.refAllDecls(@This()); -} \ No newline at end of file +} diff --git a/lib/std/os/uefi/posix.zig b/lib/std/os/uefi/posix.zig index e11cb4186de4..dc8442364107 100644 --- a/lib/std/os/uefi/posix.zig +++ b/lib/std/os/uefi/posix.zig @@ -693,7 +693,7 @@ pub fn write(fd: fd_t, buf: []const u8) std.posix.WriteError!usize { var index: usize = 0; var utf16: [256]u16 = undefined; while (iter.nextCodepoint()) |rune| { - if (index + 1 >= utf16.len) { + if (index + 2 >= utf16.len) { utf16[index] = 0; p.outputString(utf16[0..index :0]) catch |err| switch (err) { error.DeviceError => return error.InputOutput, diff --git a/lib/std/os/uefi/protocol.zig b/lib/std/os/uefi/protocol.zig index 7f74fe29f048..dfc5cdce3875 100644 --- a/lib/std/os/uefi/protocol.zig +++ b/lib/std/os/uefi/protocol.zig @@ -208,7 +208,7 @@ pub const Rng = @import("protocol/rng.zig").Rng; // ** EFI Shell Specification Version 2.2, January 26, 2016 -// Shell +pub const Shell = @import("protocol/shell.zig").Shell; pub const ShellParameters = @import("protocol/shell_parameters.zig").ShellParameters; // ShellDynamicCommand diff --git a/lib/std/os/uefi/protocol/media/file.zig b/lib/std/os/uefi/protocol/media/file.zig index ab8055732d86..485c351147be 100644 --- a/lib/std/os/uefi/protocol/media/file.zig +++ b/lib/std/os/uefi/protocol/media/file.zig @@ -76,7 +76,7 @@ pub const File = extern struct { } /// Closes and deletes a file. This can fail, but the descriptor will still be closed. - /// + /// /// Returns `true` if the file was successfully deleted, `false` otherwise. pub fn delete(self: *const File) bool { return self._delete(self) == .success; diff --git a/lib/std/os/uefi/protocol/shell.zig b/lib/std/os/uefi/protocol/shell.zig new file mode 100644 index 000000000000..41793cfc797d --- /dev/null +++ b/lib/std/os/uefi/protocol/shell.zig @@ -0,0 +1,70 @@ +const uefi = @import("std").os.uefi; +const Status = uefi.Status; +const Handle = uefi.bits.Handle; +const Guid = uefi.bits.Guid; +const cc = uefi.bits.cc; + +pub const Shell = extern struct { + execute: *const fn (parent_img_hndl: Handle, cmdline: ?[*:0]const u16, env: ?[*:null]const ?[*:0]const u16, status: *Status) callconv(cc) Status, + getEnv: *const fn (name: ?[*:0]const u16) callconv(cc) ?[*:0]const u16, + setEnv: *const fn (name: [*:0]const u16, value: [*:0]const u16, voltlie: bool) callconv(cc) Status, + getAlias: *const fn (alias: [*:0]const u16, voltlie: *bool) callconv(cc) ?[*:0]const u16, + setAlias: *const fn (cmd: [*:0]const u16, alias: [*:0]const u16, replace: bool, voltlie: bool) callconv(cc) Status, + getHelpText: *const fn (cmd: [*:0]const u16, sections: ?[*:0]const u16, helpText: *[*:0]u16) callconv(cc) Status, + getDevicePathFromMap: *const fn (mapping: [*:0]const u16) callconv(cc) ?*uefi.protocol.DevicePath, + getMapFromDevicePath: *const fn (devicePath: **const uefi.protocol.DevicePath) callconv(cc) ?[*:0]const u16, + getDevicePathFromFilePath: *const fn (path: [*:0]const u16) callconv(cc) *uefi.protocol.DevicePath, + getFilePathFromDevicePath: *const fn (devicePath: *uefi.protocol.DevicePath) callconv(cc) [*:0]const u16, + setMap: *const fn (devicePath: *uefi.protocol.DevicePath, mapping: [*:0]const u16) callconv(cc) Status, + getCurDir: *const fn (fsmap: ?[*:0]const u16) callconv(cc) ?[*:0]const u16, + setCurDir: *const fn (fs: ?[*:0]const u16, dir: [*:0]const u16) callconv(cc) Status, + openFileList: *const fn (path: [*:0]const u16, openMode: u64, fileList: **FileInfo) callconv(cc) Status, + freeFileList: *const fn (fileList: **FileInfo) callconv(cc) Status, + removeDupInFileList: *const fn (fileList: **FileInfo) callconv(cc) Status, + batchIsActive: *const fn () callconv(cc) bool, + isRootShell: *const fn () callconv(cc) bool, + enablePageBreak: *const fn () callconv(cc) void, + disablePageBreak: *const fn () callconv(cc) void, + getPageBreak: *const fn () callconv(cc) bool, + getDeviceName: *const fn (handle: Handle, flags: u32, lang: [*:0]const u8, bestDeviceName: *?[*:0]u16) callconv(cc) Status, + getFileInfo: *const fn (handle: FileHandle) callconv(cc) ?*uefi.bits.FileInfo, + setFileInfo: *const fn (handle: FileHandle, info: *const uefi.bits.FileInfo) callconv(cc) Status, + openFileByName: *const fn (filename: [*:0]const u16, handle: *FileHandle, mode: u64) callconv(cc) Status, + closeFile: *const fn (handle: FileHandle) callconv(cc) Status, + createFile: *const fn (filename: [*:0]const u16, attribs: u64, handle: *FileHandle) callconv(cc) Status, + readFile: *const fn (handle: FileHandle, size: *usize, buff: [*]u8) callconv(cc) Status, + writeFile: *const fn (handle: FileHandle, size: *usize, buff: [*]const u8) callconv(cc) Status, + deleteFile: *const fn (handle: FileHandle) callconv(cc) Status, + deleteFileByName: *const fn (filename: [*:0]const u16) callconv(cc) Status, + getFilePosition: *const fn (handle: FileHandle, pos: *u64) callconv(cc) Status, + setFilePosition: *const fn (handle: FileHandle, pos: u64) callconv(cc) Status, + flushFile: *const fn (handle: FileHandle) callconv(cc) Status, + findFiles: *const fn (pattern: [*:0]const u16, fileList: *?*FileInfo) callconv(cc) Status, + findFilesInDir: *const fn (handle: FileHandle, fileList: *?*FileInfo) callconv(cc) Status, + getFileSize: *const fn (handle: FileHandle, size: *u64) callconv(cc) Status, + openRoot: *const fn (devicePath: *uefi.protocol.DevicePath, handle: *FileHandle) callconv(cc) Status, + openRootByHandle: *const fn (deviceHandle: *Handle, fileHandle: *FileHandle) callconv(cc) Status, + executionBreak: uefi.bits.Event, + majorVersion: u32, + minorVersion: u32, + + pub const FileHandle = *opaque {}; + + pub const FileInfo = struct { + link: uefi.ListEntry, + status: Status, + fullname: [*:0]const u16, + filename: [*:0]const u16, + handle: FileHandle, + info: *uefi.bits.FileInfo, + }; + + pub const guid align(8) = Guid{ + .time_low = 0x6302d008, + .time_mid = 0x7f9b, + .time_high_and_version = 0x4f30, + .clock_seq_high_and_reserved = 0x87, + .clock_seq_low = 0xac, + .node = [_]u8{ 0x60, 0xc9, 0xfe, 0xf5, 0xda, 0x4e }, + }; +}; diff --git a/lib/std/os/uefi/ucontext.zig b/lib/std/os/uefi/ucontext.zig new file mode 100644 index 000000000000..a17c188a9b35 --- /dev/null +++ b/lib/std/os/uefi/ucontext.zig @@ -0,0 +1,107 @@ +const builtin = @import("builtin"); +const std = @import("../../std.zig"); + +pub const REG = switch (builtin.cpu.arch) { + .x86_64 => struct { + pub const RAX = 0; + pub const RBX = 1; + pub const RCX = 2; + pub const RDX = 3; + pub const RSI = 4; + pub const RDI = 5; + pub const RBP = 6; + pub const RSP = 7; + pub const R8 = 8; + pub const R9 = 9; + pub const R10 = 10; + pub const R11 = 11; + pub const R12 = 12; + pub const R13 = 13; + pub const R14 = 14; + pub const R15 = 15; + pub const RIP = 16; + pub const EFL = 17; + }, + else => @compileError("arch not supported"), +}; + +pub const fpregset = struct { + fcw: u16, + fsw: u16, + ftw: u8, + reserved1: u8, + fop: u16, + fip: u64, + fdp: u64, + mxcsr: u32, + mxcsr_mask: u32, + st: [8]u128, + xmm: [16]u128, + reserved2: [96]u8, +}; + +pub const mcontext_t = struct { + gregs: [18]usize, + fpregs: fpregset, +}; + +pub const ucontext_t = struct { + mcontext: mcontext_t, +}; + +fn gpRegisterOffset(comptime reg_index: comptime_int) usize { + return @offsetOf(ucontext_t, "mcontext") + @offsetOf(mcontext_t, "gregs") + @sizeOf(usize) * reg_index; +} + +pub inline fn getcontext(context: *ucontext_t) usize { + switch (builtin.cpu.arch) { + .x86_64 => { + asm volatile ( + \\ movq %%r8, %[r8_offset:c](%[context]) + \\ movq %%r9, %[r9_offset:c](%[context]) + \\ movq %%r10, %[r10_offset:c](%[context]) + \\ movq %%r11, %[r11_offset:c](%[context]) + \\ movq %%r12, %[r12_offset:c](%[context]) + \\ movq %%r13, %[r13_offset:c](%[context]) + \\ movq %%r14, %[r14_offset:c](%[context]) + \\ movq %%r15, %[r15_offset:c](%[context]) + \\ movq %%rdi, %[rdi_offset:c](%[context]) + \\ movq %%rsi, %[rsi_offset:c](%[context]) + \\ movq %%rbx, %[rbx_offset:c](%[context]) + \\ movq %%rdx, %[rdx_offset:c](%[context]) + \\ movq %%rax, %[rax_offset:c](%[context]) + \\ movq %%rcx, %[rcx_offset:c](%[context]) + \\ movq %%rbp, %[rbp_offset:c](%[context]) + \\ movq %%rsp, %[rsp_offset:c](%[context]) + \\ leaq (%%rip), %%rcx + \\ movq %%rcx, %[rip_offset:c](%[context]) + \\ pushfq + \\ popq %[efl_offset:c](%[context]) + : + : [context] "{rdi}" (context), + [r8_offset] "i" (comptime gpRegisterOffset(REG.R8)), + [r9_offset] "i" (comptime gpRegisterOffset(REG.R9)), + [r10_offset] "i" (comptime gpRegisterOffset(REG.R10)), + [r11_offset] "i" (comptime gpRegisterOffset(REG.R11)), + [r12_offset] "i" (comptime gpRegisterOffset(REG.R12)), + [r13_offset] "i" (comptime gpRegisterOffset(REG.R13)), + [r14_offset] "i" (comptime gpRegisterOffset(REG.R14)), + [r15_offset] "i" (comptime gpRegisterOffset(REG.R15)), + [rax_offset] "i" (comptime gpRegisterOffset(REG.RAX)), + [rbx_offset] "i" (comptime gpRegisterOffset(REG.RBX)), + [rcx_offset] "i" (comptime gpRegisterOffset(REG.RCX)), + [rdx_offset] "i" (comptime gpRegisterOffset(REG.RDX)), + [rsi_offset] "i" (comptime gpRegisterOffset(REG.RSI)), + [rdi_offset] "i" (comptime gpRegisterOffset(REG.RDI)), + [rsp_offset] "i" (comptime gpRegisterOffset(REG.RSP)), + [rbp_offset] "i" (comptime gpRegisterOffset(REG.RBP)), + [rip_offset] "i" (comptime gpRegisterOffset(REG.RIP)), + [efl_offset] "i" (comptime gpRegisterOffset(REG.EFL)), + : "cc", "memory", "rcx" + ); + }, + else => {}, + } + + return 0; +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index efd4949eebc3..f7fcc88a24ed 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -5842,7 +5842,7 @@ pub fn gethostname(name_buffer: *[HOST_NAME_MAX]u8) GetHostNameError![]u8 { pub fn uname() utsname { if (native_os == .uefi) return uefi.posix.uname(); - + var uts: utsname = undefined; switch (errno(system.uname(&uts))) { .SUCCESS => return uts, diff --git a/lib/std/process.zig b/lib/std/process.zig index cf5837a002f2..211d69d33a01 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -876,6 +876,52 @@ pub const ArgIteratorWindows = struct { } }; +/// Iterator which uses the UEFI shell parameters protocol. +pub const ArgIteratorUefi = struct { + allocator: Allocator, + argv: []const [:0]const u8, + index: usize = 0, + + pub const InitError = std.os.uefi.Status.EfiError || Allocator.Error || std.unicode.Utf16LeToUtf8AllocError || error{MissingProtocol}; + + pub fn init(allocator: Allocator) InitError!ArgIteratorUefi { + if (std.os.uefi.system_table.boot_services) |boot_services| { + const proto = try boot_services.openProtocol(std.os.uefi.handle, std.os.uefi.protocol.ShellParameters, .{}); + + var argv = try std.ArrayList([:0]const u8).initCapacity(allocator, proto.argc); + errdefer for (argv.items) |arg| allocator.free(arg); + defer argv.deinit(); + + for (proto.argv[0..proto.argc]) |arg| { + const utf8_arg = try std.unicode.utf16LeToUtf8AllocZ(allocator, arg[0..std.mem.len(arg)]); + errdefer allocator.free(utf8_arg); + argv.appendAssumeCapacity(utf8_arg); + } + + return .{ .allocator = allocator, .argv = try argv.toOwnedSlice() }; + } + return error.ProtocolError; + } + + pub fn next(self: *ArgIteratorUefi) ?[:0]const u8 { + if (self.index >= self.argv.len) return null; + const arg = self.argv[self.index]; + self.index += 1; + return arg; + } + + pub fn skip(self: *ArgIteratorUefi) bool { + if (self.index >= self.argv.len) return false; + self.index += 1; + return true; + } + + pub fn deinit(self: *ArgIteratorUefi) void { + for (self.argv) |arg| self.allocator.free(arg); + self.allocator.free(self.argv); + } +}; + /// Optional parameters for `ArgIteratorGeneral` pub const ArgIteratorGeneralOptions = struct { comments: bool = false, @@ -1084,6 +1130,7 @@ pub fn ArgIteratorGeneral(comptime options: ArgIteratorGeneralOptions) type { pub const ArgIterator = struct { const InnerType = switch (native_os) { .windows => ArgIteratorWindows, + .uefi => ArgIteratorUefi, .wasi => if (builtin.link_libc) ArgIteratorPosix else ArgIteratorWasi, else => ArgIteratorPosix, }; @@ -1099,6 +1146,9 @@ pub const ArgIterator = struct { if (native_os == .windows) { @compileError("In Windows, use initWithAllocator instead."); } + if (native_os == .uefi) { + @compileError("In UEFI, use initWithAllocator instead."); + } return ArgIterator{ .inner = InnerType.init() }; } @@ -1107,7 +1157,7 @@ pub const ArgIterator = struct { /// You must deinitialize iterator's internal buffers by calling `deinit` when done. pub fn initWithAllocator(allocator: Allocator) InitError!ArgIterator { - if (native_os == .wasi and !builtin.link_libc) { + if ((native_os == .wasi and !builtin.link_libc) or native_os == .uefi) { return ArgIterator{ .inner = try InnerType.init(allocator) }; } if (native_os == .windows) { @@ -1140,7 +1190,7 @@ pub const ArgIterator = struct { self.inner.deinit(); } - if (native_os == .windows) { + if (native_os == .windows or native_os == .uefi) { self.inner.deinit(); } } @@ -1593,13 +1643,13 @@ pub fn getBaseAddress() usize { /// Tells whether calling the `execv` or `execve` functions will be a compile error. pub const can_execv = switch (native_os) { - .windows, .haiku, .wasi => false, + .windows, .haiku, .wasi, .uefi => false, else => true, }; /// Tells whether spawning child processes is supported (e.g. via ChildProcess) pub const can_spawn = switch (native_os) { - .wasi, .watchos, .tvos => false, + .wasi, .watchos, .tvos, .uefi => false, else => true, }; @@ -1710,6 +1760,7 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { } return @as(u64, sbi.NumberOfPhysicalPages) * sbi.PageSize; }, + .uefi => return totalSystemMemoryUefi(), else => return error.UnknownTotalSystemMemory, } } @@ -1730,6 +1781,24 @@ fn totalSystemMemoryLinux() !u64 { return kilobytes * 1024; } +fn totalSystemMemoryUefi() !u64 { + if (std.os.uefi.system_table.boot_services) |boot_services| { + const alloc = std.os.uefi.global_pool_allocator.allocator(); + const map = try boot_services.getMemoryMap(alloc); + defer map.deinit(alloc); + + var pages: u64 = 0; + var it = map.iterator(); + while (it.next()) |entry| { + if (entry.type == .ConventionalMemory) { + pages += entry.number_of_pages; + } + } + return pages * std.mem.page_size; + } + return error.UnknownTotalSystemMemory; +} + /// Indicate that we are now terminating with a successful exit code. /// In debug builds, this is a no-op, so that the calling code's /// cleanup mechanisms are tested and so that external tools that diff --git a/src/introspect.zig b/src/introspect.zig index 341b1cddeb82..f0c558a9323f 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -88,7 +88,7 @@ pub fn resolveGlobalCacheDir(allocator: mem.Allocator) ![]u8 { const appname = "zig"; - if (builtin.os.tag != .windows) { + if (builtin.os.tag != .windows and builtin.os.tag != .uefi) { if (std.zig.EnvVar.XDG_CACHE_HOME.getPosix()) |cache_root| { return fs.path.join(allocator, &[_][]const u8{ cache_root, appname }); } else if (std.zig.EnvVar.HOME.getPosix()) |home| { diff --git a/src/main.zig b/src/main.zig index 5b0e211647cf..3657ed789ffc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -310,7 +310,7 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { .cmd_name = "objcopy", .root_src_path = "objcopy.zig", }); - } else if (mem.eql(u8, cmd, "fetch")) { + } else if (mem.eql(u8, cmd, "fetch") and native_os != .uefi) { return cmdFetch(gpa, arena, cmd_args); } else if (mem.eql(u8, cmd, "libc")) { return jitCmd(gpa, arena, cmd_args, .{ @@ -719,7 +719,7 @@ const ArgMode = union(enum) { /// Avoid dragging networking into zig2.c because it adds dependencies on some /// linker symbols that are annoying to satisfy while bootstrapping. -const Ip4Address = if (build_options.only_core_functionality) void else std.net.Ip4Address; +const Ip4Address = if (build_options.only_core_functionality or native_os == .uefi) void else std.net.Ip4Address; const Listen = union(enum) { none, @@ -1314,7 +1314,7 @@ fn buildOutputType( if (mem.eql(u8, next_arg, "-")) { listen = .stdio; } else { - if (build_options.only_core_functionality) unreachable; + if (build_options.only_core_functionality or native_os == .uefi) unreachable; // example: --listen 127.0.0.1:9000 var it = std.mem.splitScalar(u8, next_arg, ':'); const host = it.next().?; @@ -3358,7 +3358,8 @@ fn buildOutputType( switch (listen) { .none => {}, .stdio => { - if (build_options.only_c) unreachable; + // TODO: use UEFI shell to run child processes + if (build_options.only_c or native_os == .uefi) unreachable; try serve( comp, std.io.getStdIn(), @@ -3372,7 +3373,7 @@ fn buildOutputType( return cleanExit(); }, .ip4 => |ip4_addr| { - if (build_options.only_core_functionality) unreachable; + if (build_options.only_core_functionality or native_os == .uefi) unreachable; const addr: std.net.Address = .{ .in = ip4_addr }; @@ -3459,7 +3460,7 @@ fn buildOutputType( gpa, arena, test_exec_args.items, - self_exe_path.?, + self_exe_path orelse @panic("Cannot locate Zig"), arg_mode, &target, &comp_destroyed, @@ -4961,7 +4962,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { // Dummy http client that is not actually used when only_core_functionality is enabled. // Prevents bootstrap from depending on a bunch of unnecessary stuff. - const HttpClient = if (build_options.only_core_functionality) struct { + const HttpClient = if (build_options.only_core_functionality or native_os == .uefi) struct { allocator: Allocator, fn deinit(self: *@This()) void { _ = self; @@ -5035,7 +5036,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { var cleanup_build_dir: ?fs.Dir = null; defer if (cleanup_build_dir) |*dir| dir.close(); - if (build_options.only_core_functionality) { + if (build_options.only_core_functionality or native_os == .uefi) { try createEmptyDependenciesModule( arena, root_mod, @@ -5251,7 +5252,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void { if (code == 2) process.exit(2); if (code == 3) { - if (build_options.only_core_functionality) process.exit(3); + if (build_options.only_core_functionality or native_os == .uefi) process.exit(3); // Indicates the configure phase failed due to missing lazy // dependencies and stdout contains the hashes of the ones // that are missing. @@ -5980,7 +5981,7 @@ fn parseCodeModel(arg: []const u8) std.builtin.CodeModel { /// zig processes to run concurrently with each other, without clobbering each other. fn gimmeMoreOfThoseSweetSweetFileDescriptors() void { const have_rlimit = switch (native_os) { - .windows, .wasi => false, + .windows, .wasi, .uefi => false, else => true, }; if (!have_rlimit) return;