diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index aa21a8a0ea6e..f020f0b2714c 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -108,6 +108,8 @@ else if (native_os == .linux) LinuxThreadImpl else if (native_os == .wasi) WasiThreadImpl +else if (native_os == .uefi) + UefiThreadImpl else UnsupportedImpl; @@ -1124,6 +1126,12 @@ const WasiThreadImpl = struct { } }; +const UefiThreadImpl = struct { + fn getCurrentId() Id { + return 1; + } +}; + const LinuxThreadImpl = struct { const linux = std.os.linux; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index e8855f5d1ad3..409d7713cae7 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -505,7 +505,7 @@ pub fn defaultPanic( if (uefi.system_table.boot_services) |bs| { // ExitData buffer must be allocated using boot_services.allocatePool (spec: page 220) - const exit_data: []u16 = uefi.raw_pool_allocator.alloc(u16, exit_msg.len + 1) catch @trap(); + const exit_data: []u16 = uefi.global_pool_allocator.allocator().alloc(u16, exit_msg.len + 1) catch @trap(); @memcpy(exit_data, exit_msg[0..exit_data.len]); // Includes null terminator. _ = bs.exit(uefi.handle, .Aborted, exit_data.len, exit_data.ptr); } diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig index 544cf0ac6ff4..c391235d290c 100644 --- a/lib/std/debug/SelfInfo.zig +++ b/lib/std/debug/SelfInfo.zig @@ -129,6 +129,8 @@ pub fn getModuleForAddress(self: *SelfInfo, address: usize) !*Module { 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); } @@ -146,6 +148,8 @@ pub fn getModuleNameForAddress(self: *SelfInfo, address: usize) ?[]const u8 { return null; } else if (comptime builtin.target.isWasm()) { return null; + } else if (native_os == .uefi) { + return null; } else { return self.lookupModuleNameDl(address); } @@ -500,6 +504,12 @@ fn lookupModuleWasm(self: *SelfInfo, address: usize) !*Module { @panic("TODO implement lookup module for Wasm"); } +fn lookupModuleUefi(self: *SelfInfo, address: usize) !*Module { + _ = self; + _ = address; + @panic("TODO implement lookup module for UEFI"); +} + pub const Module = switch (native_os) { .macos, .ios, .watchos, .tvos, .visionos => struct { base_address: usize, diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 99936e9abd76..e0d74408b30f 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, }; @@ -52,7 +52,7 @@ pub const MAX_PATH_BYTES = @compileError("deprecated; renamed to 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. diff --git a/lib/std/fs/Dir.zig b/lib/std/fs/Dir.zig index fab9679838b3..2b94e36427fb 100644 --- a/lib/std/fs/Dir.zig +++ b/lib/std/fs/Dir.zig @@ -2787,4 +2787,4 @@ const assert = std.debug.assert; const linux = std.os.linux; const windows = std.os.windows; const native_os = builtin.os.tag; -const have_flock = @TypeOf(posix.system.flock) != void; +const have_flock = @hasDecl(posix.system, "flock") and @TypeOf(posix.system.flock) != void; diff --git a/lib/std/fs/File.zig b/lib/std/fs/File.zig index 69c3553ac3c4..fa85e292fd20 100644 --- a/lib/std/fs/File.zig +++ b/lib/std/fs/File.zig @@ -303,6 +303,9 @@ pub fn supportsAnsiEscapeCodes(self: File) bool { // stderr is always sanitized. return false; } + if (builtin.os.tag == .uefi) { + return false; + } if (self.isTty()) { if (self.handle == posix.STDOUT_FILENO or self.handle == posix.STDERR_FILENO) { if (posix.getenvZ("TERM")) |term| { diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 3d19d8daa6b2..2ec9f201f04c 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -239,6 +239,8 @@ else if (builtin.target.os.tag == .plan9) .ptr = undefined, .vtable = &SbrkAllocator(std.os.plan9.sbrk).vtable, } +else if (builtin.target.os.tag == .uefi) + std.os.uefi.global_page_allocator.allocator() else Allocator{ .ptr = undefined, diff --git a/lib/std/io.zig b/lib/std/io.zig index 640f575654e2..1cd46b56cec0 100644 --- a/lib/std/io.zig +++ b/lib/std/io.zig @@ -3,6 +3,7 @@ const builtin = @import("builtin"); const root = @import("root"); const c = std.c; const is_windows = builtin.os.tag == .windows; +const is_uefi = builtin.os.tag == .uefi; const windows = std.os.windows; const posix = std.posix; @@ -23,6 +24,10 @@ fn getStdOutHandle() posix.fd_t { return windows.peb().ProcessParameters.hStdOutput; } + if (is_uefi) { + return .{ .simple_output = std.os.uefi.system_table.con_out.? }; + } + if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdOutHandle")) { return root.os.io.getStdOutHandle(); } @@ -43,6 +48,10 @@ fn getStdErrHandle() posix.fd_t { return windows.peb().ProcessParameters.hStdError; } + if (is_uefi) { + return .{ .simple_output = std.os.uefi.system_table.std_err.? }; + } + if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdErrHandle")) { return root.os.io.getStdErrHandle(); } @@ -63,6 +72,10 @@ fn getStdInHandle() posix.fd_t { return windows.peb().ProcessParameters.hStdInput; } + if (is_uefi) { + return .{ .simple_output = std.os.uefi.system_table.con_in.? }; + } + if (@hasDecl(root, "os") and @hasDecl(root.os, "io") and @hasDecl(root.os.io, "getStdInHandle")) { return root.os.io.getStdInHandle(); } diff --git a/lib/std/os/uefi.zig b/lib/std/os/uefi.zig index 91f3678b267c..66ac2667b539 100644 --- a/lib/std/os/uefi.zig +++ b/lib/std/os/uefi.zig @@ -1,5 +1,7 @@ const std = @import("../std.zig"); +pub const posix = @import("uefi/posix.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; @@ -13,8 +15,31 @@ pub const tables = @import("uefi/tables.zig"); /// 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 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.Page; +pub const PoolAllocator = allocator.Pool; +pub const RawPoolAllocator = allocator.RawPool; + +pub var global_page_allocator = PageAllocator{}; +pub var global_pool_allocator = PoolAllocator{}; + +pub var working_directory: fd_t = .none; + +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 S = posix.S; + +pub const utsname = posix.utsname; +pub const Stat = posix.Stat; +pub const fd_t = posix.fd_t; +pub const ino_t = posix.ino_t; +pub const mode_t = posix.mode_t; /// The EFI image's handle that is passed to its entry point. pub var handle: Handle = undefined; diff --git a/lib/std/os/uefi/allocator.zig b/lib/std/os/uefi/allocator.zig new file mode 100644 index 000000000000..a5babf127a0e --- /dev/null +++ b/lib/std/os/uefi/allocator.zig @@ -0,0 +1,236 @@ +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 Page = struct { + memory_type: uefi.tables.MemoryType = .LoaderData, + + pub fn allocator(self: *Page) 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: *Page = @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: *Page = @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 Pool = struct { + memory_type: uefi.tables.MemoryType = .LoaderData, + + pub fn allocator(self: *Pool) 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) *align(1) Header { + return @ptrCast(ptr - @sizeOf(Header)); + } + + fn alloc( + ctx: *anyopaque, + len: usize, + log2_ptr_align: u8, + ret_addr: usize, + ) ?[*]u8 { + _ = ret_addr; + const self: *Pool = @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 }; + const header = getHeader(buf.ptr); + + uefi.system_table.boot_services.?.freePool(header.ptr[0..header.len]); + } +}; + +/// 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 RawPool = struct { + memory_type: uefi.tables.MemoryType = .LoaderData, + + pub fn allocator(self: *RawPool) 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: *RawPool = @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 f7962f22aa60..000000000000 --- a/lib/std/os/uefi/pool_allocator.zig +++ /dev/null @@ -1,134 +0,0 @@ -const std = @import("std"); - -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; - _ = log2_old_ptr_align; - - if (new_len > buf.len) return false; - 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; - 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/posix.zig b/lib/std/os/uefi/posix.zig new file mode 100644 index 000000000000..40a6031b1d30 --- /dev/null +++ b/lib/std/os/uefi/posix.zig @@ -0,0 +1,748 @@ +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 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, + NONBLOCK: bool = false, + CLOEXEC: bool = false, + CREAT: bool = false, + TRUNC: bool = false, + 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; + + 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 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, + 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_WIDE]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), + }; + defer fd.close(); + + 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().err() catch {}, + .simple_output => |p| p.reset(true).err() catch {}, + .simple_input => |p| p.reset(true).err() catch {}, + .none => {}, + .cwd => {}, + } +} + +pub fn exit(status: u8) noreturn { + if (uefi.system_table.boot_services) |bs| { + bs.exit(uefi.handle, @enumFromInt(status), null) catch {}; + } + + 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_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], .{ + .write = mode & W_OK != 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), + }; + defer fd.close(); + }, + .cwd => return faccessat(uefi.working_directory, path, mode, flags), + else => return error.FileNotFound, + } +} + +pub fn fchdir(fd: fd_t) std.posix.FchdirError!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 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| { + 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 = @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{ + .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 = openat(dirfd, pathname, .{}, 0) catch return error.FileNotFound; + 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.Unexpected; + 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; +} + +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.CurrentWorkingDirectoryUnlinked; + + 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], path); + return out_buffer[0..path.len]; +} + +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; + + while (true) { + rng.getRNG(null, buf.len, buf.ptr) catch |err| switch (err) { + error.NotReady => continue, + else => return error.FileNotFound, + }; + + break; + } + } else { + return error.NoDevice; + } +} + +pub fn isatty(fd: fd_t) bool { + switch (fd) { + .simple_input, .simple_output => return true, + else => return false, + } +} + +pub fn lseek_SET(fd: fd_t, pos: u64) std.posix.SeekError!void { + switch (fd) { + .file => |p| { + return p.setPosition(pos).err() catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +pub fn lseek_CUR(fd: fd_t, offset: i64) std.posix.SeekError!void { + switch (fd) { + .file => |p| { + const end = p.getEndPosition().err() catch return error.Unseekable; + const pos = p.getPosition().err() catch return error.Unseekable; + const new_pos = @as(i64, @intCast(pos)) + offset; + + var abs_pos: u64 = 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!void { + switch (fd) { + .file => |p| { + const end = p.getEndPosition().err() catch return error.Unseekable; + const new_pos = @as(i64, @intCast(end)) + offset; + + var abs_pos: u64 = 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().err() catch return error.Unseekable; + }, + else => return error.Unseekable, // cannot read + } +} + +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_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], .{ + .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| { + var size: usize = buf.len; + p.read(&size, buf.ptr).err() catch |err| switch (err) { + error.NoMedia => return error.InputOutput, + error.DeviceError => return error.InputOutput, + error.VolumeCorrupted => return error.InputOutput, + else => |e| return unexpectedError(e), + }; + return size; + }, + .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 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], path); + return path; +} + +pub fn realpath(pathname: []const u8, out_buffer: *[PATH_MAX]u8) std.posix.RealPathError![]u8 { + 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| { + var size: usize = buf.len; + p.write(&size, buf.ptr).err() 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), + }; + return size; + }, + .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 + 2 >= utf16.len) { + utf16[index] = 0; + p.outputString(utf16[0..index :0]).err() 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]).err() 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/file.zig b/lib/std/os/uefi/protocol/file.zig index 9c801b2f4c28..d75de18f1d83 100644 --- a/lib/std/os/uefi/protocol/file.zig +++ b/lib/std/os/uefi/protocol/file.zig @@ -8,7 +8,7 @@ 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, + _open: *const fn (*const File, **const File, [*:0]const u16, OpenMode, 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, *usize, [*]u8) callconv(cc) Status, @@ -40,8 +40,43 @@ pub const File = extern struct { 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); + /// The attributes of a file. + pub const Attributes = packed struct(u64) { + read_only: bool = false, + hidden: bool = false, + system: bool = false, + reserved: bool = false, + directory: bool = false, + archive: bool = false, + + _pad: u58 = 0, + }; + + 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, + }; + + 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: 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; } pub fn close(self: *const File) Status { diff --git a/lib/std/os/uefi/protocol/simple_text_input.zig b/lib/std/os/uefi/protocol/simple_text_input.zig index e6091b93b762..e1a66320d317 100644 --- a/lib/std/os/uefi/protocol/simple_text_input.zig +++ b/lib/std/os/uefi/protocol/simple_text_input.zig @@ -17,8 +17,14 @@ pub const SimpleTextInput = extern struct { } /// 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; + 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/tables.zig b/lib/std/os/uefi/tables.zig index e4f651104c7a..85ca7fc3478a 100644 --- a/lib/std/os/uefi/tables.zig +++ b/lib/std/os/uefi/tables.zig @@ -86,10 +86,27 @@ pub const EfiInterfaceType = enum(u32) { EfiNativeInterface, }; -pub const AllocateType = enum(u32) { - AllocateAnyPages, - AllocateMaxAddress, - AllocateAddress, +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: EfiPhysicalAddress, + + /// Allocate pages at a specified address. + at_address: EfiPhysicalAddress, }; pub const EfiPhysicalAddress = u64; diff --git a/lib/std/os/uefi/tables/boot_services.zig b/lib/std/os/uefi/tables/boot_services.zig index 04d912753b16..be36adc3cdb3 100644 --- a/lib/std/os/uefi/tables/boot_services.zig +++ b/lib/std/os/uefi/tables/boot_services.zig @@ -15,6 +15,7 @@ const LocateSearchType = uefi.tables.LocateSearchType; const OpenProtocolAttributes = uefi.tables.OpenProtocolAttributes; const ProtocolInformationEntry = uefi.tables.ProtocolInformationEntry; const EfiEventNotify = uefi.tables.EfiEventNotify; +const EfiPhysicalAddress = uefi.tables.EfiPhysicalAddress; const cc = uefi.cc; /// Boot services are services provided by the system's firmware until the operating system takes @@ -38,19 +39,19 @@ pub const BootServices = extern struct { restoreTpl: *const fn (old_tpl: usize) callconv(cc) void, /// Allocates memory pages from the system. - allocatePages: *const fn (alloc_type: AllocateType, mem_type: MemoryType, pages: usize, memory: *[*]align(4096) u8) callconv(cc) Status, + _allocatePages: *const fn (alloc_type: AllocateType.Enum, mem_type: MemoryType, pages: usize, memory: EfiPhysicalAddress) callconv(cc) Status, /// Frees memory pages. - freePages: *const fn (memory: [*]align(4096) u8, pages: usize) callconv(cc) Status, + _freePages: *const fn (memory: [*]align(4096) u8, pages: usize) callconv(cc) Status, /// Returns the current memory map. getMemoryMap: *const fn (mmap_size: *usize, mmap: ?[*]MemoryDescriptor, mapKey: *usize, descriptor_size: *usize, descriptor_version: *u32) callconv(cc) Status, /// Allocates pool memory. - allocatePool: *const fn (pool_type: MemoryType, size: usize, buffer: *[*]align(8) u8) callconv(cc) Status, + _allocatePool: *const fn (pool_type: MemoryType, size: usize, buffer: *[*]align(8) u8) callconv(cc) Status, /// Returns pool memory to the system. - freePool: *const fn (buffer: [*]align(8) u8) callconv(cc) Status, + _freePool: *const fn (buffer: [*]align(8) u8) callconv(cc) Status, /// 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, @@ -188,6 +189,74 @@ pub const BootServices = extern struct { return ptr.?; } + /// Allocates memory pages from the system. + /// + /// 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_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: EfiPhysicalAddress = @intFromPtr(&buffer_addr); + + try self._allocatePages(alloc_type, mem_type, pages, pointer).err(); + const addr: [*]align(4096) u8 = @ptrFromInt(buffer_addr); + + return addr[0 .. pages * 4096]; + } + + /// Frees memory pages. + /// + /// 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)); + } + + /// Allocates pool memory. + /// + /// 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. + /// + /// 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); + } + pub const signature: u64 = 0x56524553544f4f42; pub const event_timer: u32 = 0x80000000; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 100500bec40c..be9a39a2534d 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) @@ -45,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 { pub const ucontext_t = void; pub const pid_t = void; @@ -264,6 +266,9 @@ pub fn close(fd: fd_t) void { if (native_os == .windows) { return windows.CloseHandle(fd); } + if (native_os == .uefi) { + return uefi.posix.close(fd); + } if (native_os == .wasi and !builtin.link_libc) { _ = std.os.wasi.fd_close(fd); return; @@ -764,7 +769,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| { @@ -822,6 +826,9 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { if (native_os == .windows) { return windows.ReadFile(fd, buf, null); } + if (native_os == .uefi) { + return uefi.posix.read(fd, buf); + } if (native_os == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ .base = buf.ptr, @@ -1224,6 +1231,10 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { return windows.WriteFile(fd, bytes, null); } + if (native_os == .uefi) { + return uefi.posix.write(fd, bytes); + } + if (native_os == .wasi and !builtin.link_libc) { const ciovs = [_]iovec_const{iovec_const{ .base = bytes.ptr, @@ -1692,6 +1703,8 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: O, mode: mode_t) OpenE } return fd; + } else if (native_os == .uefi) { + 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); @@ -1794,7 +1807,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); } @@ -5150,6 +5163,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.posix.lseek_SET(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)) { @@ -5193,6 +5209,9 @@ 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) { + return uefi.posix.lseek_CUR(fd, offset); + } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, offset, .CUR, &new_offset)) { @@ -5235,6 +5254,9 @@ 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) { + return uefi.posix.lseek_END(fd, offset); + } if (native_os == .wasi and !builtin.link_libc) { var new_offset: wasi.filesize_t = undefined; switch (wasi.fd_seek(fd, offset, .END, &new_offset)) { @@ -5277,6 +5299,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.posix.lseek_CUR_get(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)) { diff --git a/lib/std/start.zig b/lib/std/start.zig index 9da0cb2ec61b..6fa78099bab7 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -199,6 +199,8 @@ fn wasi_start() callconv(.c) void { } } +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 EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv(.c) usize { uefi.handle = handle; uefi.system_table = system_table; @@ -217,7 +219,29 @@ fn EfiMain(handle: uefi.Handle, system_table: *uefi.tables.SystemTable) callconv 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) == .error_union) { + const result = root.main() catch |err| { + std.log.err("{s}", .{@errorName(err)}); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace.*); + } + std.time.sleep(5 * std.time.ns_per_s); + 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); + }, } } diff --git a/src/target.zig b/src/target.zig index 37f2cd50da50..d3bdbfeb0e41 100644 --- a/src/target.zig +++ b/src/target.zig @@ -74,7 +74,7 @@ pub fn defaultSingleThreaded(target: std.Target) bool { else => {}, } switch (target.os.tag) { - .haiku => return true, + .haiku, .uefi => return true, else => {}, } return false;