const std = @import("std"); const uefi = std.os.uefi; const elf = std.elf; const boot_handoff = @import("boot-handoff"); const build_options = @import("build_options"); const BootInformation = boot_handoff.BootInformation; const GraphicsOutput = uefi.protocol.GraphicsOutput; const EdidActive = uefi.protocol.edid.Active; const MemoryMapSlice = uefi.tables.MemoryMapSlice; // The boot volume is the FHS-shaped zig-out (see build.zig / docs/README.md), so the // loader reads each artifact from its addressed FHS path. UEFI paths use backslashes; // the FAT driver walks the components itself, so no per-directory dance is needed. /// The kernel image: /system/kernel. const kernel_file_name = std.unicode.utf8ToUtf16LeStringLiteral("system\\kernel"); /// The init program: /system/services/init. const init_file_name = std.unicode.utf8ToUtf16LeStringLiteral("system\\services\\init"); /// The initial-ramdisk (the VFS server + drivers), in /boot. const initial_ramdisk_file_name = std.unicode.utf8ToUtf16LeStringLiteral("boot\\initial-ramdisk.img"); /// Physical page size, and the sentinel UEFI uses to seek to end-of-file. const page_size = 4096; const seek_end = 0xffff_ffff_ffff_ffff; pub fn main() uefi.Status { // `boot` never returns on success — it jumps into the kernel. If it fails, // report the reason (boot services are still up) and park the machine so the // message stays on screen. boot() catch |err| { log("\r\nEFI: boot failed: "); logBytes(@errorName(err)); log("\r\n"); while (true) asm volatile ("hlt"); }; unreachable; } fn boot() !noreturn { const bs = uefi.system_table.boot_services orelse return error.NoBootServices; // Everything the kernel needs must be gathered *before* we exit boot // services, since afterwards none of these calls are usable. var boot_information: BootInformation = .{ // A missing GOP (a headless machine) is not fatal — hand the kernel a // "no framebuffer" descriptor (base 0) and let it log to serial instead. .framebuffer = queryFramebuffer(bs) catch boot_handoff.Framebuffer{ .base = 0, .width = 0, .height = 0, .pitch = 0, .format = .bgrx, }, .memory_map = undefined, // filled by exitBootServices, just below .kernel_segments = undefined, // filled by loadKernel .kernel_segment_count = 0, // Read the ACPI RSDP from the UEFI configuration table now, while boot // services are still up. The pointer lives in ACPI reclaim memory, which // the kernel identity-maps, so the physical address stays valid afterward. .acpi_rsdp = if (acpiRootSystemDescriptorPointer()) |p| @intFromPtr(p) else 0, }; const entry = try loadKernel(bs, &boot_information); // Best effort: a volume without /system/services/init still boots (kernel-only). loadInit(bs, &boot_information) catch |err| { log("EFI: no /system/services/init ("); logBytes(@errorName(err)); log(") - booting without user space\r\n"); }; // Best effort: the initial_ramdisk (VFS server + drivers) is optional too. loadInitialRamdisk(bs, &boot_information) catch |err| { log("EFI: no initial_ramdisk ("); logBytes(@errorName(err)); log(")\r\n"); }; // Build the page tables the kernel starts life on: identity + a physmap of // low RAM, plus the higher-half kernel image once it links high. Allocated // now, while boot services (and the memory map) are still stable — nothing // is allocatable after ExitBootServices, and any allocation between fetching // the map and exiting would invalidate the map key. const cr3 = try buildBootstrapTables(bs, &boot_information); progress("EFI: kernel loaded, exiting boot services\r\n"); boot_information.memory_map = try exitBootServices(bs); // Switch onto our tables and jump to the kernel in one uninterruptible step. // We load RDI explicitly (SystemV first arg) rather than trusting this UEFI // binary's Microsoft-x64 default, and jump straight to the (possibly // higher-half) entry — the bootstrap tables map both the low loader code // executing this and the kernel's link address. handoff(cr3, entry, &boot_information); } /// A display resolution in pixels. const Resolution = struct { width: u32, height: u32 }; /// Switch the GPU to the monitor's native resolution (when we can determine it) /// and read the resulting graphics mode into our own framebuffer description. fn queryFramebuffer(bs: *uefi.tables.BootServices) !boot_handoff.Framebuffer { // Enumerate the handles carrying the Graphics Output Protocol. We go through // handles (rather than locateProtocol) so we can also ask them for their EDID, // which is what tells us the panel's native resolution. const handles = (try bs.locateHandleBuffer(.{ .by_protocol = &GraphicsOutput.guid })) orelse return error.NoGraphicsOutput; defer _ = bs.freePool(@ptrCast(handles.ptr)) catch {}; const gop = (try bs.handleProtocol(GraphicsOutput, handles[0])) orelse return error.NoGraphicsOutput; // Best effort: the monitor's preferred (native) timing from its EDID. const native = nativeResolution(bs, handles); // Select the mode and switch to it if it isn't already current. setMode // updates gop.mode (info and frame_buffer_base) to describe the new mode. const target = pickMode(gop, native); if (target != gop.mode.mode) try gop.setMode(target); const info = gop.mode.info; return .{ .base = @intCast(gop.mode.frame_buffer_base), .width = info.horizontal_resolution, .height = info.vertical_resolution, // Each pixel is 32 bits, so the byte pitch is 4 * pixels-per-row. .pitch = info.pixels_per_scan_line * 4, .format = try pixelFormat(info.pixel_format), }; } /// Map a GOP pixel format to ours. bit_mask / blt_only have no linear 32bpp /// layout we can paint into, so they're rejected. fn pixelFormat(fmt: GraphicsOutput.PixelFormat) !boot_handoff.PixelFormat { return switch (fmt) { .red_green_blue_reserved_8_bit_per_color => .rgbx, .blue_green_red_reserved_8_bit_per_color => .bgrx, else => error.UnsupportedPixelFormat, }; } /// Choose the graphics mode to boot with. If we learned the monitor's native /// resolution from EDID and a mode offers it (with a layout we can paint into), /// use that. Otherwise keep whatever mode the firmware already selected: with a /// valid EDID present the firmware normally defaults to the native mode itself, /// so its default is a far safer bet than second-guessing it with, say, the /// largest advertised mode (which is often a huge non-native surface). fn pickMode(gop: *GraphicsOutput, native: ?Resolution) u32 { const n = native orelse return gop.mode.mode; var id: u32 = 0; while (id < gop.mode.max_mode) : (id += 1) { const info = gop.queryMode(id) catch continue; _ = pixelFormat(info.pixel_format) catch continue; // must be paintable if (info.horizontal_resolution == n.width and info.vertical_resolution == n.height) return id; } return gop.mode.mode; // native not on offer; trust the firmware's default } /// The monitor's native resolution, read from an EDID's preferred timing. We try /// every GOP handle and both EDID protocols (Active first, then Discovered), /// since firmware installs them inconsistently — and many, including OVMF with /// QEMU's stdvga, don't expose them at all. Returns null when none is found, in /// which case pickMode keeps the firmware's default mode. fn nativeResolution(bs: *uefi.tables.BootServices, handles: []uefi.Handle) ?Resolution { for (handles) |h| { if (bs.handleProtocol(EdidActive, h) catch null) |e| { if (e.edid) |p| if (edidNative(p[0..e.size_of_edid])) |r| return r; } if (bs.handleProtocol(uefi.protocol.edid.Discovered, h) catch null) |e| { if (e.edid) |p| if (edidNative(p[0..e.size_of_edid])) |r| return r; } } return null; } /// Parse the native resolution from a raw EDID block. The first Detailed Timing /// Descriptor (at byte 54) is the preferred — i.e. native — mode by convention; /// its active pixel counts are split across low bytes and the high nibbles of /// later bytes. fn edidNative(edid: []const u8) ?Resolution { if (edid.len < 128) return null; // Every EDID begins with this fixed 8-byte header. const header = [_]u8{ 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 }; if (!std.mem.eql(u8, edid[0..8], &header)) return null; const dtd = edid[54..][0..18]; // A zero pixel clock marks a display (not timing) descriptor: no resolution. if (dtd[0] == 0 and dtd[1] == 0) return null; const w = @as(u32, dtd[2]) | (@as(u32, dtd[4] & 0xf0) << 4); const h = @as(u32, dtd[5]) | (@as(u32, dtd[7] & 0xf0) << 4); if (w == 0 or h == 0) return null; return .{ .width = w, .height = h }; } /// Open the kernel on the volume we booted from, read it into a pool buffer, /// load its segments, and return the physical entry-point address. fn loadKernel(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !usize { const loaded = (try bs.handleProtocol(uefi.protocol.LoadedImage, uefi.handle)) orelse return error.NoLoadedImage; const device = loaded.device_handle orelse return error.NoBootDevice; const fs = (try bs.handleProtocol(uefi.protocol.SimpleFileSystem, device)) orelse return error.NoFileSystem; const root = try fs.openVolume(); defer _ = root.close() catch {}; const file = try root.open(kernel_file_name, .read, .{}); defer _ = file.close() catch {}; // Seek to the end to learn the size, then rewind. try file.setPosition(seek_end); const size: usize = @intCast(try file.getPosition()); try file.setPosition(0); const image = try bs.allocatePool(.loader_data, size); defer _ = bs.freePool(image.ptr) catch {}; // `read` may return short; loop until the whole file is in memory. var read_total: usize = 0; while (read_total < size) { const n = try file.read(image[read_total..]); if (n == 0) return error.UnexpectedEof; read_total += n; } return loadElf(bs, image, boot_information); } // --- bootstrap page tables ------------------------------------------------- // The kernel is (or will be) linked in the higher half but loaded low; the // firmware's identity map doesn't cover the higher half, so the loader builds // the first set of real page tables and switches CR3 before jumping in. They // carry: an identity map of low RAM (so the loader's own code/stack executing // the switch stays valid, and the low-linked kernel keeps working during the // staged move), a physmap at boot_handoff.physmap_base (the kernel's permanent way to // reach physical memory), and 4 KiB mappings of any higher-half kernel segment. // The kernel later builds its own precise tables (paging.init) and abandons // these; they leak as reserved LoaderData (~a handful of frames). const pte_present: u64 = 1 << 0; const pte_write: u64 = 1 << 1; const pte_ps: u64 = 1 << 7; // page-size: a 2 MiB leaf at the PD level const pte_address: u64 = 0x000F_FFFF_FFFF_F000; const gib: u64 = 1 << 30; /// A bump allocator over a pre-reserved block of zeroed frames, for page tables. const TablePool = struct { base: usize, next: usize, cap: usize, fn alloc(self: *TablePool) !u64 { if (self.next >= self.cap) return error.OutOfBootstrapFrames; const frame = self.base + self.next * page_size; self.next += 1; @memset(@as(*[512]u64, @ptrFromInt(frame)), 0); return frame; } fn table(physical: u64) *[512]u64 { return @ptrFromInt(physical); } /// Return the next-level table an entry points at, creating it if absent. fn descend(self: *TablePool, entry: *u64) !u64 { if (entry.* & pte_present != 0) return entry.* & pte_address; const frame = try self.alloc(); entry.* = frame | pte_present | pte_write; return frame; } fn map2M(self: *TablePool, pml4: u64, virtual: u64, physical: u64) !void { const pml4e = &table(pml4)[(virtual >> 39) & 0x1FF]; const pdpt = try self.descend(pml4e); const pdpte = &table(pdpt)[(virtual >> 30) & 0x1FF]; const pd = try self.descend(pdpte); table(pd)[(virtual >> 21) & 0x1FF] = (physical & ~@as(u64, 0x1F_FFFF)) | pte_present | pte_write | pte_ps; } fn map4K(self: *TablePool, pml4: u64, virtual: u64, physical: u64) !void { const pml4e = &table(pml4)[(virtual >> 39) & 0x1FF]; const pdpt = try self.descend(pml4e); const pdpte = &table(pdpt)[(virtual >> 30) & 0x1FF]; const pd = try self.descend(pdpte); const pde = &table(pd)[(virtual >> 21) & 0x1FF]; const pt = try self.descend(pde); table(pt)[(virtual >> 12) & 0x1FF] = (physical & pte_address) | pte_present | pte_write; } }; /// Build the bootstrap tables and return the physical PML4 address (for CR3). /// No NX bits are set anywhere, so EFER.NXE (still off here) is irrelevant. fn buildBootstrapTables(bs: *uefi.tables.BootServices, boot_information: *const BootInformation) !u64 { // 64 frames (256 KiB) — comfortably covers a PML4, two PDPTs, eight PDs for // the 4 GiB identity+physmap ranges, plus the kernel image's PTs. const pool_pages = 64; const block = try bs.allocatePages(.any, .loader_data, pool_pages); var pool = TablePool{ .base = @intFromPtr(block.ptr), .next = 0, .cap = pool_pages }; const pml4 = try pool.alloc(); // Identity + physmap for low RAM. 4 GiB covers all of QEMU's RAM and MMIO // (LAPIC/IOAPIC/HPET/ECAM/framebuffer under q35); a machine with RAM or a // framebuffer above 4 GiB would extend this — see the fb window below. var address: u64 = 0; while (address < 4 * gib) : (address += 2 << 20) { try pool.map2M(pml4, address, address); // identity try pool.map2M(pml4, boot_handoff.physicalToVirtual(address), address); // physmap } // A framebuffer above the 4 GiB window needs its own identity + physmap // pages (the kernel touches fb.base before it builds its own tables). const fb = boot_information.framebuffer; if (fb.present() and fb.base + @as(u64, fb.pitch) * fb.height > 4 * gib) { var p: u64 = fb.base & ~@as(u64, 0x1F_FFFF); const fb_end = fb.base + @as(u64, fb.pitch) * fb.height; while (p < fb_end) : (p += 2 << 20) { try pool.map2M(pml4, p, p); try pool.map2M(pml4, boot_handoff.physicalToVirtual(p), p); } } // Higher-half kernel segments (virtual != physical). While the kernel still links // low its segments sit in the identity range and need no separate mapping // (and 4 KiB-mapping them would collide with the 2 MiB identity leaves), so // only map segments that actually live in the higher half. for (boot_information.kernel_segments[0..boot_information.kernel_segment_count]) |seg| { if (seg.virtual < boot_handoff.kernel_virt_base) continue; var off: u64 = 0; while (off < seg.pages * page_size) : (off += page_size) { try pool.map4K(pml4, seg.virtual + off, seg.physical + off); } } return pml4; } /// Switch onto `cr3` and jump to the kernel `entry` with `boot_information` in RDI, /// interrupts off, in one block so nothing runs between the CR3 load and the /// jump. The identity mapping keeps this low loader code valid across the CR3 /// load; the jump target is mapped (identity while low, higher-half once high). fn handoff(cr3: u64, entry: usize, boot_information: *const BootInformation) noreturn { asm volatile ( \\cli \\movq %[cr3], %%cr3 \\movq %[bi], %%rdi \\callq *%[entry] : : [cr3] "r" (cr3), [bi] "r" (boot_information), [entry] "r" (entry), : .{ .memory = true }); unreachable; } /// Read a whole file off the boot volume into a pool buffer that outlives the /// loader. The buffer is deliberately NOT freed: it's LoaderData, which the /// memory-map conversion classifies as reserved, so the kernel identity-maps it /// and reads from there. Returns the buffer (pointer + length). fn loadFile(bs: *uefi.tables.BootServices, name: [*:0]const u16) ![]u8 { const loaded = (try bs.handleProtocol(uefi.protocol.LoadedImage, uefi.handle)) orelse return error.NoLoadedImage; const device = loaded.device_handle orelse return error.NoBootDevice; const fs = (try bs.handleProtocol(uefi.protocol.SimpleFileSystem, device)) orelse return error.NoFileSystem; const root = try fs.openVolume(); defer _ = root.close() catch {}; const file = try root.open(name, .read, .{}); defer _ = file.close() catch {}; try file.setPosition(seek_end); const size: usize = @intCast(try file.getPosition()); try file.setPosition(0); if (size == 0) return error.EmptyFile; const image = try bs.allocatePool(.loader_data, size); // survives the handoff var read_total: usize = 0; while (read_total < size) { const n = try file.read(image[read_total..]); if (n == 0) return error.UnexpectedEof; read_total += n; } return image[0..size]; } /// Ferry the init program (/system/services/init) to the kernel. The kernel does the ELF /// loading itself (into ring-3 mappings) — the loader just carries the bytes. fn loadInit(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void { const image = try loadFile(bs, init_file_name); boot_information.init_base = @intFromPtr(image.ptr); boot_information.init_len = image.len; progress("EFI: /system/services/init loaded\r\n"); } /// Ferry the initial_ramdisk (the VFS server + drivers) to the kernel, same as init. fn loadInitialRamdisk(bs: *uefi.tables.BootServices, boot_information: *BootInformation) !void { const image = try loadFile(bs, initial_ramdisk_file_name); boot_information.initial_ramdisk_base = @intFromPtr(image.ptr); boot_information.initial_ramdisk_len = image.len; progress("EFI: initial_ramdisk loaded\r\n"); } /// Validate the ELF, copy every PT_LOAD segment to its physical address, and /// record each segment's layout so the kernel can re-map itself with the right /// permissions. fn loadElf(bs: *uefi.tables.BootServices, image: []u8, boot_information: *BootInformation) !usize { if (image.len < @sizeOf(elf.Elf64_Ehdr)) return error.NotElf; const ehdr: *const elf.Elf64_Ehdr = @ptrCast(@alignCast(image.ptr)); if (ehdr.e_ident[0] != 0x7f or ehdr.e_ident[1] != 'E' or ehdr.e_ident[2] != 'L' or ehdr.e_ident[3] != 'F') return error.NotElf; if (ehdr.e_machine != .X86_64) return error.WrongArchitecture; var i: usize = 0; while (i < ehdr.e_phnum) : (i += 1) { const phdr: *const elf.Elf64_Phdr = @ptrCast(@alignCast( image.ptr + ehdr.e_phoff + i * ehdr.e_phentsize, )); if (phdr.p_type != elf.PT_LOAD) continue; // Reserve the exact physical pages this segment is linked at. This // requires the segment's p_paddr to be free in the firmware memory map; // if it collides, adjust `image_base` in build.zig. (Once the kernel // links high — M2 step 4 — p_paddr becomes a separate low load address // via the linker's AT(), and this stays a valid physical allocation.) const mem_sz: usize = @intCast(phdr.p_memsz); const pages = (mem_sz + page_size - 1) / page_size; const dest: [*]align(page_size) uefi.Page = @ptrFromInt(phdr.p_paddr); _ = try bs.allocatePages(.{ .address = dest }, .loader_data, pages); // Copy the file-backed part, then zero the .bss tail (memsz > filesz). const bytes: [*]u8 = @ptrFromInt(phdr.p_paddr); const file_sz: usize = @intCast(phdr.p_filesz); const off: usize = @intCast(phdr.p_offset); @memcpy(bytes[0..file_sz], image[off..][0..file_sz]); @memset(bytes[file_sz..mem_sz], 0); // Record the virtual link address and the physical load address so the // kernel can map itself with the right permissions post-switch. They're // equal while the kernel links low; they diverge once it links high. const n = boot_information.kernel_segment_count; if (n < boot_information.kernel_segments.len) { boot_information.kernel_segments[n] = .{ .virtual = phdr.p_vaddr, .physical = phdr.p_paddr, .pages = pages, .flags = phdr.p_flags, }; boot_information.kernel_segment_count = n + 1; } } return @intCast(ehdr.e_entry); } /// Fetch the memory map, exit boot services, and hand back the map in danos's /// neutral form. Allocating the buffers can itself change the map (invalidating /// the key), so retry until it takes. Both buffers are LoaderData, which survives /// the exit, so the returned map stays valid for the kernel. fn exitBootServices(bs: *uefi.tables.BootServices) !boot_handoff.MemoryMap { var attempts: usize = 0; while (attempts < 8) : (attempts += 1) { const info = try bs.getMemoryMapInfo(); // Spare descriptors to absorb the growth from the allocations below. const cap = info.len + 8; const map_buffer = try bs.allocatePool(.loader_data, cap * info.descriptor_size); const regions_buffer = try bs.allocatePool(.loader_data, cap * @sizeOf(boot_handoff.MemoryRegion)); const map = bs.getMemoryMap(map_buffer) catch { _ = bs.freePool(map_buffer.ptr) catch {}; _ = bs.freePool(regions_buffer.ptr) catch {}; continue; }; bs.exitBootServices(uefi.handle, map.info.key) catch { _ = bs.freePool(map_buffer.ptr) catch {}; _ = bs.freePool(regions_buffer.ptr) catch {}; continue; }; // Boot services are gone; do not touch `bs` again. Converting the map is // pure computation on memory we already hold, so it's safe here. return convertMemoryMap(map, regions_buffer); } return error.ExitBootServicesFailed; } /// Translate UEFI's memory map into danos's neutral `MemoryRegion` array, written /// into `out` (sized for at least `map.info.len` regions). Adjacent regions of /// the same kind are coalesced. This is the loader's job precisely so the kernel /// never sees UEFI's vocabulary — the same seam the framebuffer already uses. fn convertMemoryMap(map: MemoryMapSlice, out: []u8) boot_handoff.MemoryMap { const regions: [*]boot_handoff.MemoryRegion = @ptrCast(@alignCast(out.ptr)); // We're about to call boot-services memory `usable`, but our own stack lives // in it and the kernel starts out running on it. Keep the region holding the // current stack pointer reserved so it's never handed out. const rsp = asm volatile ("mov %%rsp, %[out]" : [out] "=r" (-> usize), ); var count: usize = 0; var i: usize = 0; while (i < map.info.len) : (i += 1) { // Stride by descriptor_size, NOT @sizeOf — firmware descriptors may be // larger than the struct. const d: *const uefi.tables.MemoryDescriptor = @ptrCast(@alignCast(map.ptr + i * map.info.descriptor_size)); if (d.number_of_pages == 0) continue; var kind = classify(d); // The descriptor we're executing on stays reserved (see rsp above). const region_end = d.physical_start + d.number_of_pages * page_size; if (kind == .usable and rsp >= d.physical_start and rsp < region_end) kind = .reserved; // Coalesce with the previous region if it's the same kind and contiguous. if (count > 0) { const previous = ®ions[count - 1]; if (previous.kind == kind and previous.base + previous.pages * page_size == d.physical_start) { previous.pages += d.number_of_pages; continue; } } regions[count] = .{ .base = d.physical_start, .pages = d.number_of_pages, .kind = kind, }; count += 1; } return .{ .regions = @intFromPtr(regions), .len = count }; } /// Map a UEFI descriptor to danos's neutral kind. A region that isn't /// writeback-cacheable (`wb`) isn't backed by real RAM — it's device registers or /// a reserved address-space window (e.g. PCIe configuration space) — so it's `mmio` /// regardless of type. UEFI overloads `reserved_memory_type` for both reserved RAM /// and such holes, and the cache attribute is what actually tells them apart. /// /// Boot-services memory is folded straight into `usable`: we've already called /// ExitBootServices, so it's free RAM now — the kernel never needs to know it was /// ever the firmware's (the one live piece, our stack, is reserved by the caller). /// Anything unrecognised is `reserved` — the safe default; our own LoaderData (the /// kernel image and these buffers) lands there and stays reserved. fn classify(d: *const uefi.tables.MemoryDescriptor) boot_handoff.MemoryKind { if (!d.attribute.wb) return .mmio; return switch (d.type) { .conventional_memory, .boot_services_code, .boot_services_data => .usable, .acpi_reclaim_memory => .acpi_tables, .acpi_memory_nvs => .acpi_nvs, .memory_mapped_io, .memory_mapped_io_port_space => .mmio, else => .reserved, }; } /// Write a compile-time string to the console (best effort). fn log(comptime message: []const u8) void { const out = uefi.system_table.con_out orelse return; _ = out.outputString(std.unicode.utf8ToUtf16LeStringLiteral(message)) catch {}; } /// A boot-progress breadcrumb: like `log`, but compiled out unless `-Dserial` /// (off by default), so a real-hardware boot stays silent. Fatal errors use /// `log` directly and always show, so a failed boot still explains itself. fn progress(comptime message: []const u8) void { if (!build_options.serial) return; log(message); } /// Write a runtime ASCII byte string (e.g. an @errorName) by widening to UTF-16. fn logBytes(bytes: []const u8) void { const out = uefi.system_table.con_out orelse return; var buffer: [128]u16 = undefined; var i: usize = 0; for (bytes) |b| { if (i + 1 >= buffer.len) break; buffer[i] = b; i += 1; } buffer[i] = 0; _ = out.outputString(buffer[0..i :0].ptr) catch {}; } fn acpiRootSystemDescriptorPointer() ?*const anyopaque { const table_entries = uefi.system_table.number_of_table_entries; const configuration_tables = uefi.system_table.configuration_table; const acpi2 = uefi.tables.ConfigurationTable.acpi_20_table_guid; const acpi1 = uefi.tables.ConfigurationTable.acpi_10_table_guid; for (0..table_entries) |i| { const entry = configuration_tables[i]; if (entry.vendor_guid.eql(acpi2) or entry.vendor_guid.eql(acpi1)) { return entry.vendor_table; } } return null; }