From 297c66b480a238dad5ce7f03405fe6f5b9123701 Mon Sep 17 00:00:00 2001 From: Bobby Date: Tue, 24 Feb 2026 06:45:06 +0530 Subject: Implement Hikari Custom Boot Loader --- hikari/asm/asm.zig | 146 ++++++++++++ hikari/boot/boot.zig | 15 ++ hikari/boot/params.zig | 109 +++++++++ hikari/build.zig | 33 +++ hikari/disk/disk.zig | 3 + hikari/disk/gpt/constants.zig | 16 ++ hikari/disk/gpt/gpt.zig | 15 ++ hikari/disk/gpt/parser.zig | 195 +++++++++++++++ hikari/disk/gpt/types.zig | 77 ++++++ hikari/display/display.zig | 11 + hikari/display/font.zig | 80 +++++++ hikari/display/framebuffer.zig | 156 ++++++++++++ hikari/display/text.zig | 198 ++++++++++++++++ hikari/efi/constants/constants.zig | 9 + hikari/efi/constants/graphics.zig | 13 + hikari/efi/constants/guids.zig | 130 ++++++++++ hikari/efi/constants/keyboard.zig | 54 +++++ hikari/efi/constants/memory.zig | 39 +++ hikari/efi/constants/status.zig | 39 +++ hikari/efi/constants/tables.zig | 23 ++ hikari/efi/constants/unit.zig | 19 ++ hikari/efi/efi.zig | 6 + hikari/efi/protocols/block_io.zig | 38 +++ hikari/efi/protocols/device_location.zig | 49 ++++ hikari/efi/protocols/disk_io.zig | 25 ++ hikari/efi/protocols/graphics_output.zig | 33 +++ hikari/efi/protocols/loaded_image.zig | 22 ++ hikari/efi/protocols/protocols.zig | 21 ++ hikari/efi/protocols/simple_text_input.zig | 18 ++ hikari/efi/protocols/simple_text_output.zig | 90 +++++++ hikari/efi/protocols/simple_unit_system.zig | 13 + hikari/efi/protocols/unit.zig | 96 ++++++++ hikari/efi/services/boot.zig | 295 +++++++++++++++++++++++ hikari/efi/services/runtime.zig | 116 +++++++++ hikari/efi/services/services.zig | 9 + hikari/efi/services/system.zig | 24 ++ hikari/efi/types/base.zig | 33 +++ hikari/efi/types/block.zig | 18 ++ hikari/efi/types/graphics.zig | 49 ++++ hikari/efi/types/input.zig | 8 + hikari/efi/types/memory.zig | 42 ++++ hikari/efi/types/reset.zig | 8 + hikari/efi/types/table.zig | 16 ++ hikari/efi/types/time.zig | 26 ++ hikari/efi/types/types.zig | 23 ++ hikari/efi/types/unit.zig | 24 ++ hikari/fs/afs/afs.zig | 30 +++ hikari/fs/afs/btree.zig | 354 ++++++++++++++++++++++++++++ hikari/fs/afs/constants.zig | 66 ++++++ hikari/fs/afs/reader.zig | 333 ++++++++++++++++++++++++++ hikari/fs/afs/types.zig | 336 ++++++++++++++++++++++++++ hikari/fs/fat32/constants.zig | 43 ++++ hikari/fs/fat32/fat32.zig | 15 ++ hikari/fs/fat32/reader.zig | 303 ++++++++++++++++++++++++ hikari/fs/fat32/types.zig | 214 +++++++++++++++++ hikari/fs/fs.zig | 4 + hikari/hikari.zig | 322 +++++++++++++++++++++++++ hikari/loader/elf/constants.zig | 85 +++++++ hikari/loader/elf/elf.zig | 17 ++ hikari/loader/elf/loader.zig | 201 ++++++++++++++++ hikari/loader/elf/types.zig | 182 ++++++++++++++ hikari/loader/loader.zig | 3 + hikari/menu/input.zig | 82 +++++++ hikari/menu/menu.zig | 125 ++++++++++ hikari/menu/renderer.zig | 199 ++++++++++++++++ hikari/menu/theme.zig | 55 +++++ hikari/paging/constants.zig | 35 +++ hikari/paging/paging.zig | 21 ++ hikari/paging/setup.zig | 208 ++++++++++++++++ hikari/paging/types.zig | 88 +++++++ 70 files changed, 5803 insertions(+) create mode 100644 hikari/asm/asm.zig create mode 100644 hikari/boot/boot.zig create mode 100644 hikari/boot/params.zig create mode 100644 hikari/build.zig create mode 100644 hikari/disk/disk.zig create mode 100644 hikari/disk/gpt/constants.zig create mode 100644 hikari/disk/gpt/gpt.zig create mode 100644 hikari/disk/gpt/parser.zig create mode 100644 hikari/disk/gpt/types.zig create mode 100644 hikari/display/display.zig create mode 100644 hikari/display/font.zig create mode 100644 hikari/display/framebuffer.zig create mode 100644 hikari/display/text.zig create mode 100644 hikari/efi/constants/constants.zig create mode 100644 hikari/efi/constants/graphics.zig create mode 100644 hikari/efi/constants/guids.zig create mode 100644 hikari/efi/constants/keyboard.zig create mode 100644 hikari/efi/constants/memory.zig create mode 100644 hikari/efi/constants/status.zig create mode 100644 hikari/efi/constants/tables.zig create mode 100644 hikari/efi/constants/unit.zig create mode 100644 hikari/efi/efi.zig create mode 100644 hikari/efi/protocols/block_io.zig create mode 100644 hikari/efi/protocols/device_location.zig create mode 100644 hikari/efi/protocols/disk_io.zig create mode 100644 hikari/efi/protocols/graphics_output.zig create mode 100644 hikari/efi/protocols/loaded_image.zig create mode 100644 hikari/efi/protocols/protocols.zig create mode 100644 hikari/efi/protocols/simple_text_input.zig create mode 100644 hikari/efi/protocols/simple_text_output.zig create mode 100644 hikari/efi/protocols/simple_unit_system.zig create mode 100644 hikari/efi/protocols/unit.zig create mode 100644 hikari/efi/services/boot.zig create mode 100644 hikari/efi/services/runtime.zig create mode 100644 hikari/efi/services/services.zig create mode 100644 hikari/efi/services/system.zig create mode 100644 hikari/efi/types/base.zig create mode 100644 hikari/efi/types/block.zig create mode 100644 hikari/efi/types/graphics.zig create mode 100644 hikari/efi/types/input.zig create mode 100644 hikari/efi/types/memory.zig create mode 100644 hikari/efi/types/reset.zig create mode 100644 hikari/efi/types/table.zig create mode 100644 hikari/efi/types/time.zig create mode 100644 hikari/efi/types/types.zig create mode 100644 hikari/efi/types/unit.zig create mode 100644 hikari/fs/afs/afs.zig create mode 100644 hikari/fs/afs/btree.zig create mode 100644 hikari/fs/afs/constants.zig create mode 100644 hikari/fs/afs/reader.zig create mode 100644 hikari/fs/afs/types.zig create mode 100644 hikari/fs/fat32/constants.zig create mode 100644 hikari/fs/fat32/fat32.zig create mode 100644 hikari/fs/fat32/reader.zig create mode 100644 hikari/fs/fat32/types.zig create mode 100644 hikari/fs/fs.zig create mode 100644 hikari/hikari.zig create mode 100644 hikari/loader/elf/constants.zig create mode 100644 hikari/loader/elf/elf.zig create mode 100644 hikari/loader/elf/loader.zig create mode 100644 hikari/loader/elf/types.zig create mode 100644 hikari/loader/loader.zig create mode 100644 hikari/menu/input.zig create mode 100644 hikari/menu/menu.zig create mode 100644 hikari/menu/renderer.zig create mode 100644 hikari/menu/theme.zig create mode 100644 hikari/paging/constants.zig create mode 100644 hikari/paging/paging.zig create mode 100644 hikari/paging/setup.zig create mode 100644 hikari/paging/types.zig diff --git a/hikari/asm/asm.zig b/hikari/asm/asm.zig new file mode 100644 index 0000000..962fe60 --- /dev/null +++ b/hikari/asm/asm.zig @@ -0,0 +1,146 @@ +//! Hikari Assembly Operations + +pub inline fn read_cr3() u64 { + return asm volatile ("mov %cr3, %[ret]" + : [ret] "=r" (-> u64), + ); +} + +pub inline fn write_cr3(value: u64) void { + asm volatile ("mov %[value], %cr3" + : + : [value] "r" (value), + : .{ .memory = true }); +} + +pub inline fn read_cr0() u64 { + return asm volatile ("mov %cr0, %[ret]" + : [ret] "=r" (-> u64), + ); +} + +pub inline fn write_cr0(value: u64) void { + asm volatile ("mov %[value], %cr0" + : + : [value] "r" (value), + ); +} + +pub inline fn read_cr4() u64 { + return asm volatile ("mov %cr4, %[ret]" + : [ret] "=r" (-> u64), + ); +} + +pub inline fn write_cr4(value: u64) void { + asm volatile ("mov %[value], %cr4" + : + : [value] "r" (value), + ); +} + +pub inline fn read_msr(msr: u32) u64 { + var low: u32 = undefined; + var high: u32 = undefined; + asm volatile ("rdmsr" + : [low] "={eax}" (low), + [high] "={edx}" (high), + : [msr] "{ecx}" (msr), + ); + return (@as(u64, high) << 32) | low; +} + +pub inline fn write_msr(msr: u32, value: u64) void { + const low: u32 = @truncate(value); + const high: u32 = @truncate(value >> 32); + asm volatile ("wrmsr" + : + : [msr] "{ecx}" (msr), + [low] "{eax}" (low), + [high] "{edx}" (high), + ); +} + +pub inline fn halt() noreturn { + while (true) { + asm volatile ("hlt"); + } +} + +pub inline fn disable_interrupts() void { + asm volatile ("cli"); +} + +pub inline fn enable_interrupts() void { + asm volatile ("sti"); +} + +pub inline fn invlpg(address: u64) void { + asm volatile ("invlpg (%[addr])" + : + : [addr] "r" (address), + : .{ .memory = true }); +} + +pub inline fn flush_tlb() void { + const cr3 = read_cr3(); + write_cr3(cr3); +} + +pub const msr_efer: u32 = 0xC0000080; +pub const efer_sce: u64 = 1 << 0; +pub const efer_lme: u64 = 1 << 8; +pub const efer_lma: u64 = 1 << 10; +pub const efer_nxe: u64 = 1 << 11; + +pub const cr0_pe: u64 = 1 << 0; +pub const cr0_mp: u64 = 1 << 1; +pub const cr0_em: u64 = 1 << 2; +pub const cr0_ts: u64 = 1 << 3; +pub const cr0_et: u64 = 1 << 4; +pub const cr0_ne: u64 = 1 << 5; +pub const cr0_wp: u64 = 1 << 16; +pub const cr0_am: u64 = 1 << 18; +pub const cr0_nw: u64 = 1 << 29; +pub const cr0_cd: u64 = 1 << 30; +pub const cr0_pg: u64 = 1 << 31; + +pub const cr4_vme: u64 = 1 << 0; +pub const cr4_pvi: u64 = 1 << 1; +pub const cr4_tsd: u64 = 1 << 2; +pub const cr4_de: u64 = 1 << 3; +pub const cr4_pse: u64 = 1 << 4; +pub const cr4_pae: u64 = 1 << 5; +pub const cr4_mce: u64 = 1 << 6; +pub const cr4_pge: u64 = 1 << 7; +pub const cr4_pce: u64 = 1 << 8; +pub const cr4_osfxsr: u64 = 1 << 9; +pub const cr4_osxmmexcpt: u64 = 1 << 10; +pub const cr4_fsgsbase: u64 = 1 << 16; +pub const cr4_osxsave: u64 = 1 << 18; +pub const cr4_smep: u64 = 1 << 20; +pub const cr4_smap: u64 = 1 << 21; + +pub fn jump_to_kernel( + entry_point: u64, + stack_top: u64, + boot_params: u64, + pml4_address: u64, +) noreturn { + disable_interrupts(); + + write_cr3(pml4_address); + + asm volatile ( + \\mov %[stack], %%rsp + \\mov %[params], %%rdi + \\xor %%rbp, %%rbp + \\jmp *%[entry] + : + : [stack] "r" (stack_top), + [params] "r" (boot_params), + [entry] "r" (entry_point), + : .{ .memory = true }); + + unreachable; +} diff --git a/hikari/boot/boot.zig b/hikari/boot/boot.zig new file mode 100644 index 0000000..979dc94 --- /dev/null +++ b/hikari/boot/boot.zig @@ -0,0 +1,15 @@ +//! Hikari Boot Subsystem + +pub const params = @import("params.zig"); + +pub const BootParams = params.BootParams; +pub const FramebufferInfo = params.FramebufferInfo; +pub const MemoryMapInfo = params.MemoryMapInfo; +pub const MemoryRegion = params.MemoryRegion; +pub const MemoryType = params.MemoryType; +pub const KernelInfo = params.KernelInfo; +pub const AcpiInfo = params.AcpiInfo; +pub const PixelFormat = params.PixelFormat; + +pub const boot_params_magic = params.boot_params_magic; +pub const boot_params_version = params.boot_params_version; diff --git a/hikari/boot/params.zig b/hikari/boot/params.zig new file mode 100644 index 0000000..4494cd0 --- /dev/null +++ b/hikari/boot/params.zig @@ -0,0 +1,109 @@ +//! Hikari Boot Parameters +//! +//! This structure is passed to the Mirai kernel at boot time. + +const paging = @import("../paging/paging.zig"); + +pub const boot_params_magic: u64 = 0x4152494D41424B41; // "AKBAMIRA" +pub const boot_params_version: u32 = 1; + +pub const BootParams = extern struct { + magic: u64, + version: u32, + size: u32, + + framebuffer: FramebufferInfo, + memory_map: MemoryMapInfo, + kernel: KernelInfo, + acpi: AcpiInfo, + boot_time: u64, + + reserved: [256]u8, + + pub fn initialize() BootParams { + var params: BootParams = undefined; + params.magic = boot_params_magic; + params.version = boot_params_version; + params.size = @sizeOf(BootParams); + + var i: usize = 0; + while (i < 256) : (i += 1) { + params.reserved[i] = 0; + } + + return params; + } + + pub fn is_valid(self: *const BootParams) bool { + return self.magic == boot_params_magic and + self.version == boot_params_version and + self.size == @sizeOf(BootParams); + } +}; + +pub const FramebufferInfo = extern struct { + base: u64, + size: u64, + width: u32, + height: u32, + stride: u32, + pixel_format: PixelFormat, + red_mask_size: u8, + red_mask_shift: u8, + green_mask_size: u8, + green_mask_shift: u8, + blue_mask_size: u8, + blue_mask_shift: u8, + reserved: [2]u8, +}; + +pub const PixelFormat = enum(u32) { + rgb = 0, + bgr = 1, + bitmask = 2, + unknown = 255, +}; + +pub const MemoryMapInfo = extern struct { + entries: u64, + entry_count: u32, + entry_size: u32, + descriptor_version: u32, + reserved: u32, +}; + +pub const MemoryRegion = extern struct { + base: u64, + size: u64, + region_type: MemoryType, + attributes: u64, +}; + +pub const MemoryType = enum(u32) { + usable = 0, + reserved = 1, + acpi_reclaimable = 2, + acpi_nvs = 3, + bad_memory = 4, + bootloader_reclaimable = 5, + kernel = 6, + framebuffer = 7, +}; + +pub const KernelInfo = extern struct { + physical_base: u64, + virtual_base: u64, + size: u64, + entry_point: u64, + pml4_address: u64, + physmap_base: u64, + physmap_size: u64, + stack_top: u64, + stack_size: u64, +}; + +pub const AcpiInfo = extern struct { + rsdp_address: u64, + rsdp_version: u32, + reserved: u32, +}; diff --git a/hikari/build.zig b/hikari/build.zig new file mode 100644 index 0000000..57eab76 --- /dev/null +++ b/hikari/build.zig @@ -0,0 +1,33 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const hikari_module = b.createModule(.{ + .root_source_file = b.path("hikari.zig"), + .target = b.resolveTargetQuery(.{ + .cpu_arch = .x86_64, + .os_tag = .uefi, + .abi = .msvc, + }), + .optimize = .ReleaseSafe, + }); + + const hikari = b.addExecutable(.{ + .name = "hikari", + .root_module = hikari_module, + }); + + hikari.entry = .{ .symbol_name = "hikari" }; + + b.installArtifact(hikari); + + const copy_step = b.addInstallFile( + hikari.getEmittedBin(), + "BOOTX64.EFI", + ); + copy_step.step.dependOn(&hikari.step); + + const install_step = b.step("install", "Build and install Hikari"); + install_step.dependOn(©_step.step); + + b.default_step = install_step; +} diff --git a/hikari/disk/disk.zig b/hikari/disk/disk.zig new file mode 100644 index 0000000..66f4603 --- /dev/null +++ b/hikari/disk/disk.zig @@ -0,0 +1,3 @@ +//! Hikari Disk Subsystem + +pub const gpt = @import("gpt/gpt.zig"); diff --git a/hikari/disk/gpt/constants.zig b/hikari/disk/gpt/constants.zig new file mode 100644 index 0000000..5bc2c91 --- /dev/null +++ b/hikari/disk/gpt/constants.zig @@ -0,0 +1,16 @@ +//! Hikari GPT Constants + +pub const signature: u64 = 0x5452415020494645; // "EFI PART" +pub const revision_1_0: u32 = 0x00010000; + +pub const header_size_minimum: u32 = 92; +pub const header_lba: u64 = 1; + +pub const partition_entry_size_minimum: u32 = 128; +pub const partition_entries_start_lba: u64 = 2; +pub const partition_entries_count_maximum: u32 = 128; + +pub const partition_identity_length: usize = 36; + +pub const protective_mbr_signature: u16 = 0xAA55; +pub const protective_mbr_partition_type: u8 = 0xEE; diff --git a/hikari/disk/gpt/gpt.zig b/hikari/disk/gpt/gpt.zig new file mode 100644 index 0000000..e59e07c --- /dev/null +++ b/hikari/disk/gpt/gpt.zig @@ -0,0 +1,15 @@ +//! Hikari GPT (GUID Partition Table) + +pub const constants = @import("constants.zig"); +pub const types = @import("types.zig"); +pub const parser = @import("parser.zig"); + +pub const Header = types.Header; +pub const PartitionEntry = types.PartitionEntry; +pub const PartitionAttributes = types.PartitionAttributes; +pub const ProtectiveMbr = types.ProtectiveMbr; +pub const MbrPartitionRecord = types.MbrPartitionRecord; + +pub const Parser = parser.Parser; +pub const ParseError = parser.ParseError; +pub const calculate_crc32 = parser.calculate_crc32; diff --git a/hikari/disk/gpt/parser.zig b/hikari/disk/gpt/parser.zig new file mode 100644 index 0000000..364a27f --- /dev/null +++ b/hikari/disk/gpt/parser.zig @@ -0,0 +1,195 @@ +//! Hikari GPT Parser + +const efi = @import("../../efi/efi.zig"); +const constants = @import("constants.zig"); +const types = @import("types.zig"); + +pub const ParseError = error{ + read_failed, + invalid_protective_mbr, + invalid_signature, + invalid_revision, + invalid_header_size, + invalid_header_crc, + invalid_entries_crc, + no_partitions, + partition_not_found, +}; + +pub const Parser = struct { + block_io: *efi.protocols.BlockIoProtocol, + block_size: u32, + header: types.Header, + entries_buffer: [*]u8, + entries_count: u32, + entry_size: u32, + + pub fn initialize(block_io: *efi.protocols.BlockIoProtocol, boot_services: *efi.services.BootServices) ParseError!Parser { + const block_size = block_io.media.block_size; + + var header_buffer: [512]u8 align(8) = undefined; + const header_read_status = block_io.read_blocks( + block_io, + block_io.media.media_id, + constants.header_lba, + block_size, + &header_buffer, + ); + + if (efi.types.is_error(header_read_status)) { + return ParseError.read_failed; + } + + const header: *const types.Header = @ptrCast(@alignCast(&header_buffer)); + + if (header.signature != constants.signature) { + return ParseError.invalid_signature; + } + + if (header.revision < constants.revision_1_0) { + return ParseError.invalid_revision; + } + + if (header.header_size < constants.header_size_minimum) { + return ParseError.invalid_header_size; + } + + const stored_header_crc = header.header_crc32; + var header_for_crc = header_buffer; + const header_ptr: *types.Header = @ptrCast(@alignCast(&header_for_crc)); + header_ptr.header_crc32 = 0; + + const calculated_header_crc = calculate_crc32(header_for_crc[0..header.header_size]); + if (calculated_header_crc != stored_header_crc) { + return ParseError.invalid_header_crc; + } + + if (header.partition_entries_count == 0) { + return ParseError.no_partitions; + } + + const entries_total_size = header.partition_entries_count * header.partition_entry_size; + const entries_blocks = (entries_total_size + block_size - 1) / block_size; + const entries_buffer_size = entries_blocks * block_size; + + var entries_buffer: [*]align(8) u8 = undefined; + const alloc_status = boot_services.allocate_pool( + .loader_data, + entries_buffer_size, + &entries_buffer, + ); + + if (efi.types.is_error(alloc_status)) { + return ParseError.read_failed; + } + + const entries_read_status = block_io.read_blocks( + block_io, + block_io.media.media_id, + header.partition_entries_lba, + entries_buffer_size, + entries_buffer, + ); + + if (efi.types.is_error(entries_read_status)) { + return ParseError.read_failed; + } + + const calculated_entries_crc = calculate_crc32(entries_buffer[0..entries_total_size]); + if (calculated_entries_crc != header.partition_entries_crc32) { + return ParseError.invalid_entries_crc; + } + + return Parser{ + .block_io = block_io, + .block_size = block_size, + .header = header.*, + .entries_buffer = entries_buffer, + .entries_count = header.partition_entries_count, + .entry_size = header.partition_entry_size, + }; + } + + pub fn find_partition_by_type(self: *const Parser, type_guid: efi.types.Guid) ?*const types.PartitionEntry { + var index: u32 = 0; + while (index < self.entries_count) : (index += 1) { + const entry = self.get_partition_entry(index); + if (entry.is_empty()) { + continue; + } + if (entry.is_type(type_guid)) { + return entry; + } + } + return null; + } + + pub fn find_partition_by_index(self: *const Parser, index: u32) ?*const types.PartitionEntry { + if (index >= self.entries_count) { + return null; + } + const entry = self.get_partition_entry(index); + if (entry.is_empty()) { + return null; + } + return entry; + } + + pub fn get_partition_entry(self: *const Parser, index: u32) *const types.PartitionEntry { + const offset = index * self.entry_size; + return @ptrCast(@alignCast(self.entries_buffer + offset)); + } + + pub fn count_valid_partitions(self: *const Parser) u32 { + var count: u32 = 0; + var index: u32 = 0; + while (index < self.entries_count) : (index += 1) { + const entry = self.get_partition_entry(index); + if (!entry.is_empty()) { + count += 1; + } + } + return count; + } + + pub fn get_disk_guid(self: *const Parser) efi.types.Guid { + return self.header.disk_guid; + } + + pub fn get_first_usable_lba(self: *const Parser) u64 { + return self.header.first_usable_lba; + } + + pub fn get_last_usable_lba(self: *const Parser) u64 { + return self.header.last_usable_lba; + } +}; + +const crc32_table: [256]u32 = generate_crc32_table(); + +fn generate_crc32_table() [256]u32 { + var table: [256]u32 = undefined; + var index: u32 = 0; + while (index < 256) : (index += 1) { + var crc: u32 = index; + var bit: u32 = 0; + while (bit < 8) : (bit += 1) { + if ((crc & 1) != 0) { + crc = (crc >> 1) ^ 0xEDB88320; + } else { + crc = crc >> 1; + } + } + table[index] = crc; + } + return table; +} + +pub fn calculate_crc32(data: []const u8) u32 { + var crc: u32 = 0xFFFFFFFF; + for (data) |byte| { + const table_index = (crc ^ byte) & 0xFF; + crc = (crc >> 8) ^ crc32_table[table_index]; + } + return crc ^ 0xFFFFFFFF; +} diff --git a/hikari/disk/gpt/types.zig b/hikari/disk/gpt/types.zig new file mode 100644 index 0000000..cc6246c --- /dev/null +++ b/hikari/disk/gpt/types.zig @@ -0,0 +1,77 @@ +//! Hikari GPT Types + +const efi = @import("../../efi/efi.zig"); +const constants = @import("constants.zig"); + +pub const Header = extern struct { + signature: u64, + revision: u32, + header_size: u32, + header_crc32: u32, + reserved: u32, + current_lba: u64, + backup_lba: u64, + first_usable_lba: u64, + last_usable_lba: u64, + disk_guid: efi.types.Guid, + partition_entries_lba: u64, + partition_entries_count: u32, + partition_entry_size: u32, + partition_entries_crc32: u32, +}; + +pub const PartitionEntry = extern struct { + partition_type_guid: efi.types.Guid, + unique_partition_guid: efi.types.Guid, + starting_lba: u64, + ending_lba: u64, + attributes: PartitionAttributes, + partition_identity: [constants.partition_identity_length]u16, + + pub fn is_empty(self: *const PartitionEntry) bool { + return self.partition_type_guid.time_low == 0 and + self.partition_type_guid.time_mid == 0 and + self.partition_type_guid.time_high_and_version == 0 and + @as(u64, @bitCast(self.partition_type_guid.clock_sequence_and_node)) == 0; + } + + pub fn is_type(self: *const PartitionEntry, type_guid: efi.types.Guid) bool { + return self.partition_type_guid.equals(type_guid); + } + + pub fn size_in_blocks(self: *const PartitionEntry) u64 { + if (self.ending_lba < self.starting_lba) { + return 0; + } + return self.ending_lba - self.starting_lba + 1; + } + + pub fn size_in_bytes(self: *const PartitionEntry, block_size: u32) u64 { + return self.size_in_blocks() * block_size; + } +}; + +pub const PartitionAttributes = packed struct(u64) { + required_for_platform: bool, + efi_firmware_ignore: bool, + legacy_bios_bootable: bool, + reserved_bits_3_47: u45, + guid_specific_bits_48_63: u16, +}; + +pub const ProtectiveMbr = extern struct { + bootstrap_code: [440]u8, + disk_signature: u32, + reserved: u16, + partitions: [4]MbrPartitionRecord, + boot_signature: u16, +}; + +pub const MbrPartitionRecord = extern struct { + boot_indicator: u8, + starting_chs: [3]u8, + os_type: u8, + ending_chs: [3]u8, + starting_lba: u32, + size_in_lba: u32, +}; diff --git a/hikari/display/display.zig b/hikari/display/display.zig new file mode 100644 index 0000000..de6e303 --- /dev/null +++ b/hikari/display/display.zig @@ -0,0 +1,11 @@ +//! Hikari Display Subsystem + +pub const framebuffer = @import("framebuffer.zig"); +pub const font = @import("font.zig"); +pub const text = @import("text.zig"); + +pub const Framebuffer = framebuffer.Framebuffer; +pub const Color = framebuffer.Color; +pub const Font = font.Font; +pub const Psf2Header = font.Psf2Header; +pub const TextRenderer = text.TextRenderer; diff --git a/hikari/display/font.zig b/hikari/display/font.zig new file mode 100644 index 0000000..8abb94c --- /dev/null +++ b/hikari/display/font.zig @@ -0,0 +1,80 @@ +//! Hikari PSF2 Font + +pub const psf2_magic: u32 = 0x864AB572; + +pub const Psf2Header = extern struct { + magic: u32, + version: u32, + header_size: u32, + flags: u32, + glyph_count: u32, + glyph_size: u32, + height: u32, + width: u32, + + pub fn is_valid(self: *const Psf2Header) bool { + return self.magic == psf2_magic; + } + + pub fn has_unicode_table(self: *const Psf2Header) bool { + return (self.flags & 0x01) != 0; + } + + pub fn bytes_per_row(self: *const Psf2Header) u32 { + return (self.width + 7) / 8; + } +}; + +pub const Font = struct { + header: *const Psf2Header, + glyphs: [*]const u8, + glyph_count: u32, + glyph_size: u32, + width: u32, + height: u32, + bytes_per_row: u32, + + pub fn load(data: [*]const u8, size: u64) ?Font { + if (size < @sizeOf(Psf2Header)) { + return null; + } + + const header: *const Psf2Header = @ptrCast(@alignCast(data)); + + if (!header.is_valid()) { + return null; + } + + const glyphs = data + header.header_size; + + return Font{ + .header = header, + .glyphs = glyphs, + .glyph_count = header.glyph_count, + .glyph_size = header.glyph_size, + .width = header.width, + .height = header.height, + .bytes_per_row = header.bytes_per_row(), + }; + } + + pub fn get_glyph(self: *const Font, codepoint: u32) ?[*]const u8 { + if (codepoint >= self.glyph_count) { + return null; + } + + return self.glyphs + (codepoint * self.glyph_size); + } + + pub fn get_glyph_pixel(self: *const Font, glyph: [*]const u8, x: u32, y: u32) bool { + if (x >= self.width or y >= self.height) { + return false; + } + + const row = glyph + (y * self.bytes_per_row); + const byte_index = x / 8; + const bit_index: u3 = @truncate(7 - (x % 8)); + + return ((row[byte_index] >> bit_index) & 1) != 0; + } +}; diff --git a/hikari/display/framebuffer.zig b/hikari/display/framebuffer.zig new file mode 100644 index 0000000..cf7c0ab --- /dev/null +++ b/hikari/display/framebuffer.zig @@ -0,0 +1,156 @@ +//! Hikari Framebuffer + +const efi = @import("../efi/efi.zig"); + +pub const Framebuffer = struct { + base: [*]u32, + width: u32, + height: u32, + stride: u32, + pixel_format: efi.types.graphics.PixelFormat, + + pub fn initialize(gop: *efi.protocols.GraphicsOutputProtocol) Framebuffer { + const mode = gop.mode; + const info = mode.info; + + return Framebuffer{ + .base = @ptrFromInt(mode.framebuffer_base), + .width = info.horizontal_resolution, + .height = info.vertical_resolution, + .stride = info.pixels_per_scan_line, + .pixel_format = info.pixel_format, + }; + } + + pub fn put_pixel(self: *Framebuffer, x: u32, y: u32, color: Color) void { + if (x >= self.width or y >= self.height) { + return; + } + + const offset = y * self.stride + x; + self.base[offset] = color.to_pixel(self.pixel_format); + } + + pub fn fill_rect(self: *Framebuffer, x: u32, y: u32, w: u32, h: u32, color: Color) void { + const pixel = color.to_pixel(self.pixel_format); + const x_end = if (x + w > self.width) self.width else x + w; + const y_end = if (y + h > self.height) self.height else y + h; + + var py = y; + while (py < y_end) : (py += 1) { + var px = x; + while (px < x_end) : (px += 1) { + const offset = py * self.stride + px; + self.base[offset] = pixel; + } + } + } + + pub fn clear(self: *Framebuffer, color: Color) void { + self.fill_rect(0, 0, self.width, self.height, color); + } + + pub fn draw_horizontal_line(self: *Framebuffer, x: u32, y: u32, length: u32, color: Color) void { + if (y >= self.height) { + return; + } + + const pixel = color.to_pixel(self.pixel_format); + const x_end = if (x + length > self.width) self.width else x + length; + const row_offset = y * self.stride; + + var px = x; + while (px < x_end) : (px += 1) { + self.base[row_offset + px] = pixel; + } + } + + pub fn draw_vertical_line(self: *Framebuffer, x: u32, y: u32, length: u32, color: Color) void { + if (x >= self.width) { + return; + } + + const pixel = color.to_pixel(self.pixel_format); + const y_end = if (y + length > self.height) self.height else y + length; + + var py = y; + while (py < y_end) : (py += 1) { + self.base[py * self.stride + x] = pixel; + } + } + + pub fn draw_rect(self: *Framebuffer, x: u32, y: u32, w: u32, h: u32, color: Color) void { + self.draw_horizontal_line(x, y, w, color); + self.draw_horizontal_line(x, y + h - 1, w, color); + self.draw_vertical_line(x, y, h, color); + self.draw_vertical_line(x + w - 1, y, h, color); + } + + pub fn copy_rect(self: *Framebuffer, src_x: u32, src_y: u32, dst_x: u32, dst_y: u32, w: u32, h: u32) void { + if (src_y < dst_y) { + var row: u32 = h; + while (row > 0) { + row -= 1; + self.copy_row(src_x, src_y + row, dst_x, dst_y + row, w); + } + } else { + var row: u32 = 0; + while (row < h) : (row += 1) { + self.copy_row(src_x, src_y + row, dst_x, dst_y + row, w); + } + } + } + + fn copy_row(self: *Framebuffer, src_x: u32, src_y: u32, dst_x: u32, dst_y: u32, w: u32) void { + const src_offset = src_y * self.stride + src_x; + const dst_offset = dst_y * self.stride + dst_x; + + if (src_x < dst_x) { + var i: u32 = w; + while (i > 0) { + i -= 1; + self.base[dst_offset + i] = self.base[src_offset + i]; + } + } else { + var i: u32 = 0; + while (i < w) : (i += 1) { + self.base[dst_offset + i] = self.base[src_offset + i]; + } + } + } +}; + +pub const Color = struct { + r: u8, + g: u8, + b: u8, + a: u8, + + pub fn rgb(r: u8, g: u8, b: u8) Color { + return Color{ .r = r, .g = g, .b = b, .a = 255 }; + } + + pub fn rgba(r: u8, g: u8, b: u8, a: u8) Color { + return Color{ .r = r, .g = g, .b = b, .a = a }; + } + + pub fn to_pixel(self: Color, format: efi.types.graphics.PixelFormat) u32 { + return switch (format) { + .rgb => (@as(u32, self.r) << 16) | (@as(u32, self.g) << 8) | self.b, + .bgr => (@as(u32, self.b) << 16) | (@as(u32, self.g) << 8) | self.r, + else => (@as(u32, self.r) << 16) | (@as(u32, self.g) << 8) | self.b, + }; + } + + pub const black = Color.rgb(0, 0, 0); + pub const white = Color.rgb(255, 255, 255); + pub const red = Color.rgb(255, 0, 0); + pub const green = Color.rgb(0, 255, 0); + pub const blue = Color.rgb(0, 0, 255); + pub const cyan = Color.rgb(0, 255, 255); + pub const magenta = Color.rgb(255, 0, 255); + pub const yellow = Color.rgb(255, 255, 0); + pub const gray = Color.rgb(128, 128, 128); + pub const dark_gray = Color.rgb(64, 64, 64); + pub const light_gray = Color.rgb(192, 192, 192); +}; diff --git a/hikari/display/text.zig b/hikari/display/text.zig new file mode 100644 index 0000000..806b122 --- /dev/null +++ b/hikari/display/text.zig @@ -0,0 +1,198 @@ +//! Hikari Text Renderer + +const framebuffer = @import("framebuffer.zig"); +const font_mod = @import("font.zig"); + +pub const TextRenderer = struct { + fb: *framebuffer.Framebuffer, + font: *const font_mod.Font, + cursor_x: u32, + cursor_y: u32, + fg_color: framebuffer.Color, + bg_color: framebuffer.Color, + columns: u32, + rows: u32, + + pub fn initialize( + fb: *framebuffer.Framebuffer, + font: *const font_mod.Font, + ) TextRenderer { + return TextRenderer{ + .fb = fb, + .font = font, + .cursor_x = 0, + .cursor_y = 0, + .fg_color = framebuffer.Color.white, + .bg_color = framebuffer.Color.black, + .columns = fb.width / font.width, + .rows = fb.height / font.height, + }; + } + + pub fn set_colors(self: *TextRenderer, fg: framebuffer.Color, bg: framebuffer.Color) void { + self.fg_color = fg; + self.bg_color = bg; + } + + pub fn set_cursor(self: *TextRenderer, col: u32, row: u32) void { + self.cursor_x = col; + self.cursor_y = row; + } + + pub fn draw_char(self: *TextRenderer, codepoint: u32) void { + const glyph = self.font.get_glyph(codepoint) orelse self.font.get_glyph('?') orelse return; + + const screen_x = self.cursor_x * self.font.width; + const screen_y = self.cursor_y * self.font.height; + + var y: u32 = 0; + while (y < self.font.height) : (y += 1) { + var x: u32 = 0; + while (x < self.font.width) : (x += 1) { + const pixel_on = self.font.get_glyph_pixel(glyph, x, y); + const color = if (pixel_on) self.fg_color else self.bg_color; + self.fb.put_pixel(screen_x + x, screen_y + y, color); + } + } + } + + pub fn draw_char_at(self: *TextRenderer, codepoint: u32, col: u32, row: u32) void { + const old_x = self.cursor_x; + const old_y = self.cursor_y; + self.cursor_x = col; + self.cursor_y = row; + self.draw_char(codepoint); + self.cursor_x = old_x; + self.cursor_y = old_y; + } + + pub fn put_char(self: *TextRenderer, c: u8) void { + switch (c) { + '\n' => { + self.cursor_x = 0; + self.cursor_y += 1; + if (self.cursor_y >= self.rows) { + self.scroll_up(); + self.cursor_y = self.rows - 1; + } + }, + '\r' => { + self.cursor_x = 0; + }, + '\t' => { + const tab_stop = 8; + self.cursor_x = ((self.cursor_x / tab_stop) + 1) * tab_stop; + if (self.cursor_x >= self.columns) { + self.cursor_x = 0; + self.cursor_y += 1; + if (self.cursor_y >= self.rows) { + self.scroll_up(); + self.cursor_y = self.rows - 1; + } + } + }, + 0x08 => { + if (self.cursor_x > 0) { + self.cursor_x -= 1; + } + }, + else => { + self.draw_char(c); + self.cursor_x += 1; + if (self.cursor_x >= self.columns) { + self.cursor_x = 0; + self.cursor_y += 1; + if (self.cursor_y >= self.rows) { + self.scroll_up(); + self.cursor_y = self.rows - 1; + } + } + }, + } + } + + pub fn print(self: *TextRenderer, str: []const u8) void { + for (str) |c| { + self.put_char(c); + } + } + + pub fn print_line(self: *TextRenderer, str: []const u8) void { + self.print(str); + self.put_char('\n'); + } + + pub fn scroll_up(self: *TextRenderer) void { + const line_height = self.font.height; + const scroll_pixels = (self.rows - 1) * line_height; + + self.fb.copy_rect(0, line_height, 0, 0, self.fb.width, scroll_pixels); + + self.fb.fill_rect( + 0, + scroll_pixels, + self.fb.width, + line_height, + self.bg_color, + ); + } + + pub fn clear_screen(self: *TextRenderer) void { + self.fb.clear(self.bg_color); + self.cursor_x = 0; + self.cursor_y = 0; + } + + pub fn clear_line(self: *TextRenderer, row: u32) void { + if (row >= self.rows) { + return; + } + + self.fb.fill_rect( + 0, + row * self.font.height, + self.fb.width, + self.font.height, + self.bg_color, + ); + } + + pub fn print_u64(self: *TextRenderer, value: u64) void { + var buf: [20]u8 = undefined; + var i: usize = 0; + var v = value; + + if (v == 0) { + self.put_char('0'); + return; + } + + while (v > 0) { + buf[i] = @truncate((v % 10) + '0'); + v /= 10; + i += 1; + } + + while (i > 0) { + i -= 1; + self.put_char(buf[i]); + } + } + + pub fn print_hex(self: *TextRenderer, value: u64) void { + const hex_chars = "0123456789ABCDEF"; + self.print("0x"); + + var started = false; + var shift: u6 = 60; + while (true) { + const nibble: u4 = @truncate((value >> shift) & 0xF); + if (nibble != 0 or started or shift == 0) { + self.put_char(hex_chars[nibble]); + started = true; + } + if (shift == 0) break; + shift -= 4; + } + } +}; diff --git a/hikari/efi/constants/constants.zig b/hikari/efi/constants/constants.zig new file mode 100644 index 0000000..409651a --- /dev/null +++ b/hikari/efi/constants/constants.zig @@ -0,0 +1,9 @@ +//! Hikari EFI Constants + +pub const status = @import("status.zig"); +pub const memory = @import("memory.zig"); +pub const guids = @import("guids.zig"); +pub const unit = @import("unit.zig"); +pub const keyboard = @import("keyboard.zig"); +pub const graphics = @import("graphics.zig"); +pub const tables = @import("tables.zig"); diff --git a/hikari/efi/constants/graphics.zig b/hikari/efi/constants/graphics.zig new file mode 100644 index 0000000..c1055f0 --- /dev/null +++ b/hikari/efi/constants/graphics.zig @@ -0,0 +1,13 @@ +//! Hikari EFI Graphics Constants + +pub const pixel_format_rgb: u32 = 0; +pub const pixel_format_bgr: u32 = 1; +pub const pixel_format_bitmask: u32 = 2; +pub const pixel_format_blt_only: u32 = 3; + +pub const blt_operation_video_fill: u32 = 0; +pub const blt_operation_video_to_buffer: u32 = 1; +pub const blt_operation_buffer_to_video: u32 = 2; +pub const blt_operation_video_to_video: u32 = 3; + +pub const graphics_output_protocol_revision: u32 = 0x00010000; diff --git a/hikari/efi/constants/guids.zig b/hikari/efi/constants/guids.zig new file mode 100644 index 0000000..a50117f --- /dev/null +++ b/hikari/efi/constants/guids.zig @@ -0,0 +1,130 @@ +//! Hikari EFI Protocol GUIDs + +const types = @import("../types/types.zig"); +const Guid = types.Guid; + +pub const loaded_image_protocol = Guid{ + .time_low = 0x5B1B31A1, + .time_mid = 0x9562, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8E, 0x3F, 0x00, 0xA0, 0xC9, 0x69, 0x72, 0x3B }, +}; + +pub const simple_unit_system_protocol = Guid{ + .time_low = 0x0964e5b22, + .time_mid = 0x6459, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, +}; + +pub const unit_info = Guid{ + .time_low = 0x09576e92, + .time_mid = 0x6d3f, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, +}; + +pub const unit_system_info = Guid{ + .time_low = 0x09576e93, + .time_mid = 0x6d3f, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, +}; + +pub const graphics_output_protocol = Guid{ + .time_low = 0x9042a9de, + .time_mid = 0x23dc, + .time_high_and_version = 0x4a38, + .clock_sequence_and_node = .{ 0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a }, +}; + +pub const block_io_protocol = Guid{ + .time_low = 0x964e5b21, + .time_mid = 0x6459, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, +}; + +pub const block_io2_protocol = Guid{ + .time_low = 0xa77b2472, + .time_mid = 0xe282, + .time_high_and_version = 0x4e9f, + .clock_sequence_and_node = .{ 0xa2, 0x45, 0xc2, 0xc0, 0xe2, 0x7b, 0xbc, 0xc1 }, +}; + +pub const disk_io_protocol = Guid{ + .time_low = 0xce345171, + .time_mid = 0xba0b, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8e, 0x4f, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, +}; + +pub const disk_io2_protocol = Guid{ + .time_low = 0x151c8eae, + .time_mid = 0x7f2c, + .time_high_and_version = 0x472c, + .clock_sequence_and_node = .{ 0x9e, 0x54, 0x98, 0x28, 0x19, 0x4f, 0x6a, 0x88 }, +}; + +pub const device_location_protocol = Guid{ + .time_low = 0x09576e91, + .time_mid = 0x6d3f, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, +}; + +pub const simple_text_input_protocol = Guid{ + .time_low = 0x387477c1, + .time_mid = 0x69c7, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, +}; + +pub const simple_text_output_protocol = Guid{ + .time_low = 0x387477c2, + .time_mid = 0x69c7, + .time_high_and_version = 0x11d2, + .clock_sequence_and_node = .{ 0x8e, 0x39, 0x00, 0xa0, 0xc9, 0x69, 0x72, 0x3b }, +}; + +pub const acpi_20_table = Guid{ + .time_low = 0x8868e871, + .time_mid = 0xe4f1, + .time_high_and_version = 0x11d3, + .clock_sequence_and_node = .{ 0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81 }, +}; + +pub const acpi_10_table = Guid{ + .time_low = 0xeb9d2d30, + .time_mid = 0x2d88, + .time_high_and_version = 0x11d3, + .clock_sequence_and_node = .{ 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, +}; + +pub const smbios_table = Guid{ + .time_low = 0xeb9d2d31, + .time_mid = 0x2d88, + .time_high_and_version = 0x11d3, + .clock_sequence_and_node = .{ 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d }, +}; + +pub const smbios3_table = Guid{ + .time_low = 0xf2fd1544, + .time_mid = 0x9794, + .time_high_and_version = 0x4a2c, + .clock_sequence_and_node = .{ 0x99, 0x2e, 0xe5, 0xbb, 0xcf, 0x20, 0xe3, 0x94 }, +}; + +pub const gpt_partition_type_efi_system = Guid{ + .time_low = 0xC12A7328, + .time_mid = 0xF81F, + .time_high_and_version = 0x11D2, + .clock_sequence_and_node = .{ 0xBA, 0x4B, 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B }, +}; + +pub const gpt_partition_type_akiba_afs = Guid{ + .time_low = 0x414B4942, + .time_mid = 0x4146, + .time_high_and_version = 0x5300, + .clock_sequence_and_node = .{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }, +}; diff --git a/hikari/efi/constants/keyboard.zig b/hikari/efi/constants/keyboard.zig new file mode 100644 index 0000000..0dd5633 --- /dev/null +++ b/hikari/efi/constants/keyboard.zig @@ -0,0 +1,54 @@ +//! Hikari EFI Keyboard Scan Code Constants + +pub const scan_null: u16 = 0x0000; +pub const scan_up: u16 = 0x0001; +pub const scan_down: u16 = 0x0002; +pub const scan_right: u16 = 0x0003; +pub const scan_left: u16 = 0x0004; +pub const scan_home: u16 = 0x0005; +pub const scan_end: u16 = 0x0006; +pub const scan_insert: u16 = 0x0007; +pub const scan_delete: u16 = 0x0008; +pub const scan_page_up: u16 = 0x0009; +pub const scan_page_down: u16 = 0x000A; +pub const scan_f1: u16 = 0x000B; +pub const scan_f2: u16 = 0x000C; +pub const scan_f3: u16 = 0x000D; +pub const scan_f4: u16 = 0x000E; +pub const scan_f5: u16 = 0x000F; +pub const scan_f6: u16 = 0x0010; +pub const scan_f7: u16 = 0x0011; +pub const scan_f8: u16 = 0x0012; +pub const scan_f9: u16 = 0x0013; +pub const scan_f10: u16 = 0x0014; +pub const scan_f11: u16 = 0x0015; +pub const scan_f12: u16 = 0x0016; +pub const scan_escape: u16 = 0x0017; +pub const scan_f13: u16 = 0x0068; +pub const scan_f14: u16 = 0x0069; +pub const scan_f15: u16 = 0x006A; +pub const scan_f16: u16 = 0x006B; +pub const scan_f17: u16 = 0x006C; +pub const scan_f18: u16 = 0x006D; +pub const scan_f19: u16 = 0x006E; +pub const scan_f20: u16 = 0x006F; +pub const scan_f21: u16 = 0x0070; +pub const scan_f22: u16 = 0x0071; +pub const scan_f23: u16 = 0x0072; +pub const scan_f24: u16 = 0x0073; +pub const scan_mute: u16 = 0x007F; +pub const scan_volume_up: u16 = 0x0080; +pub const scan_volume_down: u16 = 0x0081; +pub const scan_brightness_up: u16 = 0x0100; +pub const scan_brightness_down: u16 = 0x0101; +pub const scan_suspend: u16 = 0x0102; +pub const scan_hibernate: u16 = 0x0103; +pub const scan_toggle_display: u16 = 0x0104; +pub const scan_recovery: u16 = 0x0105; +pub const scan_eject: u16 = 0x0106; + +pub const unicode_char_null: u16 = 0x0000; +pub const unicode_char_backspace: u16 = 0x0008; +pub const unicode_char_tab: u16 = 0x0009; +pub const unicode_char_linefeed: u16 = 0x000A; +pub const unicode_char_carriage_return: u16 = 0x000D; diff --git a/hikari/efi/constants/memory.zig b/hikari/efi/constants/memory.zig new file mode 100644 index 0000000..f45ae53 --- /dev/null +++ b/hikari/efi/constants/memory.zig @@ -0,0 +1,39 @@ +//! Hikari EFI Memory Constants + +pub const memory_type_reserved: u32 = 0; +pub const memory_type_loader_code: u32 = 1; +pub const memory_type_loader_data: u32 = 2; +pub const memory_type_boot_services_code: u32 = 3; +pub const memory_type_boot_services_data: u32 = 4; +pub const memory_type_runtime_services_code: u32 = 5; +pub const memory_type_runtime_services_data: u32 = 6; +pub const memory_type_conventional: u32 = 7; +pub const memory_type_unusable: u32 = 8; +pub const memory_type_acpi_reclaim: u32 = 9; +pub const memory_type_acpi_nvs: u32 = 10; +pub const memory_type_mmio: u32 = 11; +pub const memory_type_mmio_port_space: u32 = 12; +pub const memory_type_pal_code: u32 = 13; +pub const memory_type_persistent: u32 = 14; +pub const memory_type_unaccepted: u32 = 15; + +pub const memory_attribute_uncacheable: u64 = 0x0000000000000001; +pub const memory_attribute_write_combining: u64 = 0x0000000000000002; +pub const memory_attribute_write_through: u64 = 0x0000000000000004; +pub const memory_attribute_write_back: u64 = 0x0000000000000008; +pub const memory_attribute_uncacheable_exported: u64 = 0x0000000000000010; +pub const memory_attribute_write_protected: u64 = 0x0000000000001000; +pub const memory_attribute_read_protected: u64 = 0x0000000000002000; +pub const memory_attribute_execute_protected: u64 = 0x0000000000004000; +pub const memory_attribute_nonvolatile: u64 = 0x0000000000008000; +pub const memory_attribute_more_reliable: u64 = 0x0000000000010000; +pub const memory_attribute_read_only: u64 = 0x0000000000020000; +pub const memory_attribute_specific_purpose: u64 = 0x0000000000040000; +pub const memory_attribute_crypto_capable: u64 = 0x0000000000080000; +pub const memory_attribute_runtime: u64 = 0x8000000000000000; + +pub const allocate_type_any_pages: u32 = 0; +pub const allocate_type_max_address: u32 = 1; +pub const allocate_type_address: u32 = 2; + +pub const page_size: u64 = 4096; diff --git a/hikari/efi/constants/status.zig b/hikari/efi/constants/status.zig new file mode 100644 index 0000000..ae959ab --- /dev/null +++ b/hikari/efi/constants/status.zig @@ -0,0 +1,39 @@ +//! Hikari EFI Status Code Constants + +pub const success: usize = 0; + +pub const high_bit: usize = 1 << 63; + +pub const load_error: usize = high_bit | 1; +pub const invalid_parameter: usize = high_bit | 2; +pub const unsupported: usize = high_bit | 3; +pub const bad_buffer_size: usize = high_bit | 4; +pub const buffer_too_small: usize = high_bit | 5; +pub const not_ready: usize = high_bit | 6; +pub const device_error: usize = high_bit | 7; +pub const write_protected: usize = high_bit | 8; +pub const out_of_resources: usize = high_bit | 9; +pub const volume_corrupted: usize = high_bit | 10; +pub const volume_full: usize = high_bit | 11; +pub const no_media: usize = high_bit | 12; +pub const media_changed: usize = high_bit | 13; +pub const not_found: usize = high_bit | 14; +pub const access_denied: usize = high_bit | 15; +pub const no_response: usize = high_bit | 16; +pub const no_mapping: usize = high_bit | 17; +pub const timeout: usize = high_bit | 18; +pub const not_started: usize = high_bit | 19; +pub const already_started: usize = high_bit | 20; +pub const aborted: usize = high_bit | 21; +pub const icmp_error: usize = high_bit | 22; +pub const tftp_error: usize = high_bit | 23; +pub const protocol_error: usize = high_bit | 24; +pub const incompatible_version: usize = high_bit | 25; +pub const security_violation: usize = high_bit | 26; +pub const crc_error: usize = high_bit | 27; +pub const end_of_media: usize = high_bit | 28; +pub const end_of_unit: usize = high_bit | 31; +pub const invalid_language: usize = high_bit | 32; +pub const compromised_data: usize = high_bit | 33; +pub const ip_address_conflict: usize = high_bit | 34; +pub const http_error: usize = high_bit | 35; diff --git a/hikari/efi/constants/tables.zig b/hikari/efi/constants/tables.zig new file mode 100644 index 0000000..592113e --- /dev/null +++ b/hikari/efi/constants/tables.zig @@ -0,0 +1,23 @@ +//! Hikari EFI Table Signature Constants + +pub const system_table_signature: u64 = 0x5453595320494249; // "IBI SYST" +pub const boot_services_signature: u64 = 0x56524553544f4f42; // "BOOTSERV" +pub const runtime_services_signature: u64 = 0x56524553544e5552; // "RUNTSERV" + +pub const system_table_revision_2_100: u32 = (2 << 16) | 100; +pub const system_table_revision_2_90: u32 = (2 << 16) | 90; +pub const system_table_revision_2_80: u32 = (2 << 16) | 80; +pub const system_table_revision_2_70: u32 = (2 << 16) | 70; +pub const system_table_revision_2_60: u32 = (2 << 16) | 60; +pub const system_table_revision_2_50: u32 = (2 << 16) | 50; +pub const system_table_revision_2_40: u32 = (2 << 16) | 40; +pub const system_table_revision_2_31: u32 = (2 << 16) | 31; +pub const system_table_revision_2_30: u32 = (2 << 16) | 30; +pub const system_table_revision_2_20: u32 = (2 << 16) | 20; +pub const system_table_revision_2_10: u32 = (2 << 16) | 10; +pub const system_table_revision_2_00: u32 = (2 << 16) | 0; +pub const system_table_revision_1_10: u32 = (1 << 16) | 10; +pub const system_table_revision_1_02: u32 = (1 << 16) | 2; + +pub const specification_major_revision: u32 = 2; +pub const specification_minor_revision: u32 = 100; diff --git a/hikari/efi/constants/unit.zig b/hikari/efi/constants/unit.zig new file mode 100644 index 0000000..9d4e4ae --- /dev/null +++ b/hikari/efi/constants/unit.zig @@ -0,0 +1,19 @@ +//! Hikari EFI Unit Constants + +pub const unit_mode_read: u64 = 0x0000000000000001; +pub const unit_mode_write: u64 = 0x0000000000000002; +pub const unit_mode_create: u64 = 0x8000000000000000; + +pub const unit_attribute_read_only: u64 = 0x0000000000000001; +pub const unit_attribute_hidden: u64 = 0x0000000000000002; +pub const unit_attribute_system: u64 = 0x0000000000000004; +pub const unit_attribute_reserved: u64 = 0x0000000000000008; +pub const unit_attribute_stack: u64 = 0x0000000000000010; +pub const unit_attribute_archive: u64 = 0x0000000000000020; +pub const unit_attribute_valid: u64 = 0x0000000000000037; + +pub const unit_protocol_revision: u64 = 0x00010000; +pub const unit_protocol_revision2: u64 = 0x00020000; +pub const unit_protocol_latest_revision: u64 = unit_protocol_revision2; + +pub const simple_unit_system_protocol_revision: u64 = 0x00010000; diff --git a/hikari/efi/efi.zig b/hikari/efi/efi.zig new file mode 100644 index 0000000..8d67232 --- /dev/null +++ b/hikari/efi/efi.zig @@ -0,0 +1,6 @@ +//! Hikari EFI Interface + +pub const constants = @import("constants/constants.zig"); +pub const types = @import("types/types.zig"); +pub const services = @import("services/services.zig"); +pub const protocols = @import("protocols/protocols.zig"); diff --git a/hikari/efi/protocols/block_io.zig b/hikari/efi/protocols/block_io.zig new file mode 100644 index 0000000..76ff976 --- /dev/null +++ b/hikari/efi/protocols/block_io.zig @@ -0,0 +1,38 @@ +//! Hikari EFI Block I/O Protocol + +const types = @import("../types/types.zig"); +const block = @import("../types/block.zig"); + +pub const BlockIoProtocol = extern struct { + revision: u64, + media: *block.BlockIoMedia, + + reset: *const fn ( + self: *BlockIoProtocol, + extended_verification: bool, + ) callconv(.C) types.Status, + + read_blocks: *const fn ( + self: *BlockIoProtocol, + media_id: u32, + lba: types.Lba, + buffer_size: usize, + buffer: [*]u8, + ) callconv(.C) types.Status, + + write_blocks: *const fn ( + self: *BlockIoProtocol, + media_id: u32, + lba: types.Lba, + buffer_size: usize, + buffer: [*]const u8, + ) callconv(.C) types.Status, + + flush_blocks: *const fn ( + self: *BlockIoProtocol, + ) callconv(.C) types.Status, +}; + +pub const block_io_protocol_revision1: u64 = 0x00010000; +pub const block_io_protocol_revision2: u64 = 0x00020001; +pub const block_io_protocol_revision3: u64 = 0x0002001F; diff --git a/hikari/efi/protocols/device_location.zig b/hikari/efi/protocols/device_location.zig new file mode 100644 index 0000000..0378b5f --- /dev/null +++ b/hikari/efi/protocols/device_location.zig @@ -0,0 +1,49 @@ +//! Hikari EFI Device Location Protocol + +pub const DeviceLocationProtocol = extern struct { + device_type: u8, + sub_type: u8, + length: [2]u8, +}; + +pub const device_location_type_hardware: u8 = 0x01; +pub const device_location_type_acpi: u8 = 0x02; +pub const device_location_type_messaging: u8 = 0x03; +pub const device_location_type_media: u8 = 0x04; +pub const device_location_type_bios_boot: u8 = 0x05; +pub const device_location_type_end: u8 = 0x7F; + +pub const device_location_sub_type_end_entire: u8 = 0xFF; +pub const device_location_sub_type_end_instance: u8 = 0x01; + +pub const device_location_sub_type_hard_drive: u8 = 0x01; +pub const device_location_sub_type_cdrom: u8 = 0x02; +pub const device_location_sub_type_vendor: u8 = 0x03; +pub const device_location_sub_type_unit_location: u8 = 0x04; +pub const device_location_sub_type_media_protocol: u8 = 0x05; +pub const device_location_sub_type_piwg_firmware_unit: u8 = 0x06; +pub const device_location_sub_type_piwg_firmware_volume: u8 = 0x07; +pub const device_location_sub_type_relative_offset_range: u8 = 0x08; +pub const device_location_sub_type_ram_disk: u8 = 0x09; + +pub const HardDriveDeviceLocation = extern struct { + header: DeviceLocationProtocol, + partition_number: u32, + partition_start: u64, + partition_size: u64, + partition_signature: [16]u8, + partition_format: u8, + signature_type: u8, +}; + +pub const partition_format_mbr: u8 = 0x01; +pub const partition_format_gpt: u8 = 0x02; + +pub const signature_type_none: u8 = 0x00; +pub const signature_type_mbr: u8 = 0x01; +pub const signature_type_guid: u8 = 0x02; + +pub const UnitLocationDeviceLocation = extern struct { + header: DeviceLocationProtocol, + location_name: [*:0]u16, +}; diff --git a/hikari/efi/protocols/disk_io.zig b/hikari/efi/protocols/disk_io.zig new file mode 100644 index 0000000..223d8cc --- /dev/null +++ b/hikari/efi/protocols/disk_io.zig @@ -0,0 +1,25 @@ +//! Hikari EFI Disk I/O Protocol + +const types = @import("../types/types.zig"); + +pub const DiskIoProtocol = extern struct { + revision: u64, + + read_disk: *const fn ( + self: *DiskIoProtocol, + media_id: u32, + offset: u64, + buffer_size: usize, + buffer: [*]u8, + ) callconv(.C) types.Status, + + write_disk: *const fn ( + self: *DiskIoProtocol, + media_id: u32, + offset: u64, + buffer_size: usize, + buffer: [*]const u8, + ) callconv(.C) types.Status, +}; + +pub const disk_io_protocol_revision: u64 = 0x00010000; diff --git a/hikari/efi/protocols/graphics_output.zig b/hikari/efi/protocols/graphics_output.zig new file mode 100644 index 0000000..94d87d3 --- /dev/null +++ b/hikari/efi/protocols/graphics_output.zig @@ -0,0 +1,33 @@ +//! Hikari EFI Graphics Output Protocol + +const types = @import("../types/types.zig"); +const graphics = @import("../types/graphics.zig"); + +pub const GraphicsOutputProtocol = extern struct { + query_mode: *const fn ( + self: *GraphicsOutputProtocol, + mode_number: u32, + size_of_info: *usize, + info: **graphics.GraphicsOutputModeInformation, + ) callconv(.C) types.Status, + + set_mode: *const fn ( + self: *GraphicsOutputProtocol, + mode_number: u32, + ) callconv(.C) types.Status, + + blt: *const fn ( + self: *GraphicsOutputProtocol, + blt_buffer: ?[*]graphics.BltPixel, + blt_operation: graphics.BltOperation, + source_x: usize, + source_y: usize, + destination_x: usize, + destination_y: usize, + width: usize, + height: usize, + delta: usize, + ) callconv(.C) types.Status, + + mode: *graphics.GraphicsOutputProtocolMode, +}; diff --git a/hikari/efi/protocols/loaded_image.zig b/hikari/efi/protocols/loaded_image.zig new file mode 100644 index 0000000..f596417 --- /dev/null +++ b/hikari/efi/protocols/loaded_image.zig @@ -0,0 +1,22 @@ +//! Hikari EFI Loaded Image Protocol + +const types = @import("../types/types.zig"); +const memory = @import("../types/memory.zig"); + +pub const LoadedImageProtocol = extern struct { + revision: u32, + parent_handle: types.Handle, + system_table: *anyopaque, + device_handle: types.Handle, + unit_location: *anyopaque, + reserved: *anyopaque, + load_options_size: u32, + load_options: *anyopaque, + image_base: [*]u8, + image_size: u64, + image_code_type: memory.MemoryType, + image_data_type: memory.MemoryType, + unload: *const fn (image_handle: types.Handle) callconv(.C) types.Status, +}; + +pub const loaded_image_protocol_revision: u32 = 0x1000; diff --git a/hikari/efi/protocols/protocols.zig b/hikari/efi/protocols/protocols.zig new file mode 100644 index 0000000..2e086fa --- /dev/null +++ b/hikari/efi/protocols/protocols.zig @@ -0,0 +1,21 @@ +//! Hikari EFI Protocols + +pub const simple_text_input = @import("simple_text_input.zig"); +pub const simple_text_output = @import("simple_text_output.zig"); +pub const graphics_output = @import("graphics_output.zig"); +pub const file = @import("file.zig"); +pub const simple_file_system = @import("simple_file_system.zig"); +pub const block_io = @import("block_io.zig"); +pub const disk_io = @import("disk_io.zig"); +pub const loaded_image = @import("loaded_image.zig"); +pub const device_path = @import("device_path.zig"); + +pub const SimpleTextInputProtocol = simple_text_input.SimpleTextInputProtocol; +pub const SimpleTextOutputProtocol = simple_text_output.SimpleTextOutputProtocol; +pub const GraphicsOutputProtocol = graphics_output.GraphicsOutputProtocol; +pub const FileProtocol = file.FileProtocol; +pub const SimpleFileSystemProtocol = simple_file_system.SimpleFileSystemProtocol; +pub const BlockIoProtocol = block_io.BlockIoProtocol; +pub const DiskIoProtocol = disk_io.DiskIoProtocol; +pub const LoadedImageProtocol = loaded_image.LoadedImageProtocol; +pub const DevicePathProtocol = device_path.DevicePathProtocol; diff --git a/hikari/efi/protocols/simple_text_input.zig b/hikari/efi/protocols/simple_text_input.zig new file mode 100644 index 0000000..c4f07cd --- /dev/null +++ b/hikari/efi/protocols/simple_text_input.zig @@ -0,0 +1,18 @@ +//! Hikari EFI Simple Text Input Protocol + +const types = @import("../types/types.zig"); +const input = @import("../types/input.zig"); + +pub const SimpleTextInputProtocol = extern struct { + reset: *const fn ( + self: *SimpleTextInputProtocol, + extended_verification: bool, + ) callconv(.C) types.Status, + + read_key_stroke: *const fn ( + self: *SimpleTextInputProtocol, + key: *input.InputKey, + ) callconv(.C) types.Status, + + wait_for_key: types.Event, +}; diff --git a/hikari/efi/protocols/simple_text_output.zig b/hikari/efi/protocols/simple_text_output.zig new file mode 100644 index 0000000..168bbc9 --- /dev/null +++ b/hikari/efi/protocols/simple_text_output.zig @@ -0,0 +1,90 @@ +//! Hikari EFI Simple Text Output Protocol + +const types = @import("../types/types.zig"); + +pub const SimpleTextOutputProtocol = extern struct { + reset: *const fn ( + self: *SimpleTextOutputProtocol, + extended_verification: bool, + ) callconv(.C) types.Status, + + output_string: *const fn ( + self: *SimpleTextOutputProtocol, + string: [*:0]const types.Char16, + ) callconv(.C) types.Status, + + test_string: *const fn ( + self: *SimpleTextOutputProtocol, + string: [*:0]const types.Char16, + ) callconv(.C) types.Status, + + query_mode: *const fn ( + self: *SimpleTextOutputProtocol, + mode_number: usize, + columns: *usize, + rows: *usize, + ) callconv(.C) types.Status, + + set_mode: *const fn ( + self: *SimpleTextOutputProtocol, + mode_number: usize, + ) callconv(.C) types.Status, + + set_attribute: *const fn ( + self: *SimpleTextOutputProtocol, + attribute: usize, + ) callconv(.C) types.Status, + + clear_screen: *const fn ( + self: *SimpleTextOutputProtocol, + ) callconv(.C) types.Status, + + set_cursor_position: *const fn ( + self: *SimpleTextOutputProtocol, + column: usize, + row: usize, + ) callconv(.C) types.Status, + + enable_cursor: *const fn ( + self: *SimpleTextOutputProtocol, + visible: bool, + ) callconv(.C) types.Status, + + mode: *SimpleTextOutputMode, +}; + +pub const SimpleTextOutputMode = extern struct { + max_mode: i32, + mode: i32, + attribute: i32, + cursor_column: i32, + cursor_row: i32, + cursor_visible: bool, +}; + +pub const text_attribute_black: usize = 0x00; +pub const text_attribute_blue: usize = 0x01; +pub const text_attribute_green: usize = 0x02; +pub const text_attribute_cyan: usize = 0x03; +pub const text_attribute_red: usize = 0x04; +pub const text_attribute_magenta: usize = 0x05; +pub const text_attribute_brown: usize = 0x06; +pub const text_attribute_lightgray: usize = 0x07; +pub const text_attribute_bright: usize = 0x08; +pub const text_attribute_darkgray: usize = 0x08; +pub const text_attribute_lightblue: usize = 0x09; +pub const text_attribute_lightgreen: usize = 0x0A; +pub const text_attribute_lightcyan: usize = 0x0B; +pub const text_attribute_lightred: usize = 0x0C; +pub const text_attribute_lightmagenta: usize = 0x0D; +pub const text_attribute_yellow: usize = 0x0E; +pub const text_attribute_white: usize = 0x0F; + +pub const background_black: usize = 0x00; +pub const background_blue: usize = 0x10; +pub const background_green: usize = 0x20; +pub const background_cyan: usize = 0x30; +pub const background_red: usize = 0x40; +pub const background_magenta: usize = 0x50; +pub const background_brown: usize = 0x60; +pub const background_lightgray: usize = 0x70; diff --git a/hikari/efi/protocols/simple_unit_system.zig b/hikari/efi/protocols/simple_unit_system.zig new file mode 100644 index 0000000..73cce41 --- /dev/null +++ b/hikari/efi/protocols/simple_unit_system.zig @@ -0,0 +1,13 @@ +//! Hikari EFI Simple Unit System Protocol + +const types = @import("../types/types.zig"); +const unit = @import("unit.zig"); + +pub const SimpleUnitSystemProtocol = extern struct { + revision: u64, + + open_volume: *const fn ( + self: *SimpleUnitSystemProtocol, + root: **unit.UnitProtocol, + ) callconv(.C) types.Status, +}; diff --git a/hikari/efi/protocols/unit.zig b/hikari/efi/protocols/unit.zig new file mode 100644 index 0000000..1bb9cb4 --- /dev/null +++ b/hikari/efi/protocols/unit.zig @@ -0,0 +1,96 @@ +//! Hikari EFI Unit Protocol + +const types = @import("../types/types.zig"); + +pub const UnitProtocol = extern struct { + revision: u64, + + open: *const fn ( + self: *UnitProtocol, + new_handle: **UnitProtocol, + unit_name: [*:0]const types.Char16, + open_mode: u64, + attributes: u64, + ) callconv(.C) types.Status, + + close: *const fn ( + self: *UnitProtocol, + ) callconv(.C) types.Status, + + delete: *const fn ( + self: *UnitProtocol, + ) callconv(.C) types.Status, + + read: *const fn ( + self: *UnitProtocol, + buffer_size: *usize, + buffer: [*]u8, + ) callconv(.C) types.Status, + + write: *const fn ( + self: *UnitProtocol, + buffer_size: *usize, + buffer: [*]const u8, + ) callconv(.C) types.Status, + + get_position: *const fn ( + self: *UnitProtocol, + position: *u64, + ) callconv(.C) types.Status, + + set_position: *const fn ( + self: *UnitProtocol, + position: u64, + ) callconv(.C) types.Status, + + get_info: *const fn ( + self: *UnitProtocol, + information_type: *align(8) const types.Guid, + buffer_size: *usize, + buffer: [*]u8, + ) callconv(.C) types.Status, + + set_info: *const fn ( + self: *UnitProtocol, + information_type: *align(8) const types.Guid, + buffer_size: usize, + buffer: [*]const u8, + ) callconv(.C) types.Status, + + flush: *const fn ( + self: *UnitProtocol, + ) callconv(.C) types.Status, + + open_ex: *const fn ( + self: *UnitProtocol, + new_handle: **UnitProtocol, + unit_name: [*:0]const types.Char16, + open_mode: u64, + attributes: u64, + token: *UnitIoToken, + ) callconv(.C) types.Status, + + read_ex: *const fn ( + self: *UnitProtocol, + token: *UnitIoToken, + ) callconv(.C) types.Status, + + write_ex: *const fn ( + self: *UnitProtocol, + token: *UnitIoToken, + ) callconv(.C) types.Status, + + flush_ex: *const fn ( + self: *UnitProtocol, + token: *UnitIoToken, + ) callconv(.C) types.Status, +}; + +pub const UnitIoToken = extern struct { + event: types.Event, + status: types.Status, + buffer_size: usize, + buffer: ?*anyopaque, +}; + +pub const unit_position_end: u64 = 0xFFFFFFFFFFFFFFFF; diff --git a/hikari/efi/services/boot.zig b/hikari/efi/services/boot.zig new file mode 100644 index 0000000..6964896 --- /dev/null +++ b/hikari/efi/services/boot.zig @@ -0,0 +1,295 @@ +//! Hikari EFI Boot Services + +const types = @import("../types/types.zig"); +const table = @import("../types/table.zig"); +const memory = @import("../types/memory.zig"); + +pub const BootServices = extern struct { + header: table.TableHeader, + + raise_tpl: *const fn (new_tpl: usize) callconv(.C) usize, + restore_tpl: *const fn (old_tpl: usize) callconv(.C) void, + + allocate_pages: *const fn ( + allocate_type: memory.AllocateType, + memory_type: memory.MemoryType, + pages: usize, + physical_address: *types.PhysicalAddress, + ) callconv(.C) types.Status, + + free_pages: *const fn ( + physical_address: types.PhysicalAddress, + pages: usize, + ) callconv(.C) types.Status, + + get_memory_map: *const fn ( + memory_map_size: *usize, + memory_map: [*]memory.MemoryDescriptor, + map_key: *usize, + descriptor_size: *usize, + descriptor_version: *u32, + ) callconv(.C) types.Status, + + allocate_pool: *const fn ( + pool_type: memory.MemoryType, + size: usize, + buffer: *[*]align(8) u8, + ) callconv(.C) types.Status, + + free_pool: *const fn ( + buffer: [*]align(8) u8, + ) callconv(.C) types.Status, + + create_event: *const fn ( + event_type: u32, + notify_tpl: usize, + notify_function: ?*const fn (types.Event, ?*anyopaque) callconv(.C) void, + notify_context: ?*anyopaque, + event: *types.Event, + ) callconv(.C) types.Status, + + set_timer: *const fn ( + event: types.Event, + timer_type: TimerDelay, + trigger_time: u64, + ) callconv(.C) types.Status, + + wait_for_event: *const fn ( + number_of_events: usize, + events: [*]const types.Event, + index: *usize, + ) callconv(.C) types.Status, + + signal_event: *const fn ( + event: types.Event, + ) callconv(.C) types.Status, + + close_event: *const fn ( + event: types.Event, + ) callconv(.C) types.Status, + + check_event: *const fn ( + event: types.Event, + ) callconv(.C) types.Status, + + install_protocol_interface: *const fn ( + handle: *types.Handle, + protocol: *align(8) const types.Guid, + interface_type: InterfaceType, + interface: ?*anyopaque, + ) callconv(.C) types.Status, + + reinstall_protocol_interface: *const fn ( + handle: types.Handle, + protocol: *align(8) const types.Guid, + old_interface: ?*anyopaque, + new_interface: ?*anyopaque, + ) callconv(.C) types.Status, + + uninstall_protocol_interface: *const fn ( + handle: types.Handle, + protocol: *align(8) const types.Guid, + interface: ?*anyopaque, + ) callconv(.C) types.Status, + + handle_protocol: *const fn ( + handle: types.Handle, + protocol: *align(8) const types.Guid, + interface: *?*anyopaque, + ) callconv(.C) types.Status, + + reserved: *anyopaque, + + register_protocol_notify: *const fn ( + protocol: *align(8) const types.Guid, + event: types.Event, + registration: **anyopaque, + ) callconv(.C) types.Status, + + locate_handle: *const fn ( + search_type: memory.LocateSearchType, + protocol: ?*align(8) const types.Guid, + search_key: ?*anyopaque, + buffer_size: *usize, + buffer: [*]types.Handle, + ) callconv(.C) types.Status, + + locate_device_location: *const fn ( + protocol: *align(8) const types.Guid, + device_location: **anyopaque, + device: *types.Handle, + ) callconv(.C) types.Status, + + install_configuration_table: *const fn ( + guid: *align(8) const types.Guid, + table_ptr: ?*anyopaque, + ) callconv(.C) types.Status, + + load_image: *const fn ( + boot_policy: bool, + parent_image_handle: types.Handle, + device_location: ?*anyopaque, + source_buffer: ?[*]const u8, + source_size: usize, + image_handle: *types.Handle, + ) callconv(.C) types.Status, + + start_image: *const fn ( + image_handle: types.Handle, + exit_data_size: *usize, + exit_data: ?*[*]types.Char16, + ) callconv(.C) types.Status, + + exit: *const fn ( + image_handle: types.Handle, + exit_status: types.Status, + exit_data_size: usize, + exit_data: ?[*]const types.Char16, + ) callconv(.C) types.Status, + + unload_image: *const fn ( + image_handle: types.Handle, + ) callconv(.C) types.Status, + + exit_boot_services: *const fn ( + image_handle: types.Handle, + map_key: usize, + ) callconv(.C) types.Status, + + get_next_monotonic_count: *const fn ( + count: *u64, + ) callconv(.C) types.Status, + + stall: *const fn ( + microseconds: usize, + ) callconv(.C) types.Status, + + set_watchdog_timer: *const fn ( + timeout: usize, + watchdog_code: u64, + data_size: usize, + watchdog_data: ?[*]const types.Char16, + ) callconv(.C) types.Status, + + connect_controller: *const fn ( + controller_handle: types.Handle, + driver_image_handle: ?types.Handle, + remaining_device_location: ?*anyopaque, + recursive: bool, + ) callconv(.C) types.Status, + + disconnect_controller: *const fn ( + controller_handle: types.Handle, + driver_image_handle: ?types.Handle, + child_handle: ?types.Handle, + ) callconv(.C) types.Status, + + open_protocol: *const fn ( + handle: types.Handle, + protocol: *align(8) const types.Guid, + interface: ?*?*anyopaque, + agent_handle: ?types.Handle, + controller_handle: ?types.Handle, + attributes: u32, + ) callconv(.C) types.Status, + + close_protocol: *const fn ( + handle: types.Handle, + protocol: *align(8) const types.Guid, + agent_handle: types.Handle, + controller_handle: ?types.Handle, + ) callconv(.C) types.Status, + + open_protocol_information: *const fn ( + handle: types.Handle, + protocol: *align(8) const types.Guid, + entry_buffer: *[*]OpenProtocolInformationEntry, + entry_count: *usize, + ) callconv(.C) types.Status, + + protocols_per_handle: *const fn ( + handle: types.Handle, + protocol_buffer: *[*]*align(8) types.Guid, + protocol_buffer_count: *usize, + ) callconv(.C) types.Status, + + locate_handle_buffer: *const fn ( + search_type: memory.LocateSearchType, + protocol: ?*align(8) const types.Guid, + search_key: ?*anyopaque, + handle_count: *usize, + buffer: *[*]types.Handle, + ) callconv(.C) types.Status, + + locate_protocol: *const fn ( + protocol: *align(8) const types.Guid, + registration: ?*anyopaque, + interface: *?*anyopaque, + ) callconv(.C) types.Status, + + install_multiple_protocol_interfaces: *const anyopaque, + uninstall_multiple_protocol_interfaces: *const anyopaque, + + calculate_crc32: *const fn ( + data: [*]const u8, + data_size: usize, + crc32: *u32, + ) callconv(.C) types.Status, + + copy_memory: *const fn ( + destination: [*]u8, + source: [*]const u8, + length: usize, + ) callconv(.C) void, + + set_memory: *const fn ( + buffer: [*]u8, + size: usize, + value: u8, + ) callconv(.C) void, + + create_event_ex: *const fn ( + event_type: u32, + notify_tpl: usize, + notify_function: ?*const fn (types.Event, ?*anyopaque) callconv(.C) void, + notify_context: ?*const anyopaque, + event_group: ?*align(8) const types.Guid, + event: *types.Event, + ) callconv(.C) types.Status, +}; + +pub const TimerDelay = enum(u32) { + cancel = 0, + periodic = 1, + relative = 2, +}; + +pub const InterfaceType = enum(u32) { + native = 0, +}; + +pub const OpenProtocolInformationEntry = extern struct { + agent_handle: types.Handle, + controller_handle: types.Handle, + attributes: u32, + open_count: u32, +}; + +pub const open_protocol_by_handle_protocol: u32 = 0x00000001; +pub const open_protocol_get_protocol: u32 = 0x00000002; +pub const open_protocol_test_protocol: u32 = 0x00000004; +pub const open_protocol_by_child_controller: u32 = 0x00000008; +pub const open_protocol_by_driver: u32 = 0x00000010; +pub const open_protocol_exclusive: u32 = 0x00000020; + +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; + +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 = 0x60000202; diff --git a/hikari/efi/services/runtime.zig b/hikari/efi/services/runtime.zig new file mode 100644 index 0000000..add3277 --- /dev/null +++ b/hikari/efi/services/runtime.zig @@ -0,0 +1,116 @@ +//! Hikari EFI Runtime Services + +const types = @import("../types/types.zig"); +const table = @import("../types/table.zig"); +const time = @import("../types/time.zig"); +const memory = @import("../types/memory.zig"); +const reset = @import("../types/reset.zig"); + +pub const RuntimeServices = extern struct { + header: table.TableHeader, + + get_time: *const fn ( + current_time: *time.Time, + capabilities: ?*time.TimeCapabilities, + ) callconv(.C) types.Status, + + set_time: *const fn ( + new_time: *const time.Time, + ) callconv(.C) types.Status, + + get_wakeup_time: *const fn ( + enabled: *bool, + pending: *bool, + wakeup_time: *time.Time, + ) callconv(.C) types.Status, + + set_wakeup_time: *const fn ( + enable: bool, + wakeup_time: ?*const time.Time, + ) callconv(.C) types.Status, + + set_virtual_address_map: *const fn ( + memory_map_size: usize, + descriptor_size: usize, + descriptor_version: u32, + virtual_map: [*]memory.MemoryDescriptor, + ) callconv(.C) types.Status, + + convert_pointer: *const fn ( + debug_disposition: usize, + address: **anyopaque, + ) callconv(.C) types.Status, + + get_variable: *const fn ( + variable_name: [*:0]const types.Char16, + vendor_guid: *align(8) const types.Guid, + attributes: ?*u32, + data_size: *usize, + data: ?[*]u8, + ) callconv(.C) types.Status, + + get_next_variable_name: *const fn ( + variable_name_size: *usize, + variable_name: [*:0]types.Char16, + vendor_guid: *align(8) types.Guid, + ) callconv(.C) types.Status, + + set_variable: *const fn ( + variable_name: [*:0]const types.Char16, + vendor_guid: *align(8) const types.Guid, + attributes: u32, + data_size: usize, + data: [*]const u8, + ) callconv(.C) types.Status, + + get_next_high_monotonic_count: *const fn ( + high_count: *u32, + ) callconv(.C) types.Status, + + reset_system: *const fn ( + reset_type: reset.ResetType, + reset_status: types.Status, + data_size: usize, + reset_data: ?*const anyopaque, + ) callconv(.C) noreturn, + + update_capsule: *const fn ( + capsule_header_array: **CapsuleHeader, + capsule_count: usize, + scatter_gather_list: types.PhysicalAddress, + ) callconv(.C) types.Status, + + query_capsule_capabilities: *const fn ( + capsule_header_array: **CapsuleHeader, + capsule_count: usize, + maximum_capsule_size: *u64, + reset_type: *reset.ResetType, + ) callconv(.C) types.Status, + + query_variable_info: *const fn ( + attributes: u32, + maximum_variable_storage_size: *u64, + remaining_variable_storage_size: *u64, + maximum_variable_size: *u64, + ) callconv(.C) types.Status, +}; + +pub const CapsuleHeader = extern struct { + capsule_guid: types.Guid, + header_size: u32, + flags: u32, + capsule_image_size: u32, +}; + +pub const variable_non_volatile: u32 = 0x00000001; +pub const variable_bootservice_access: u32 = 0x00000002; +pub const variable_runtime_access: u32 = 0x00000004; +pub const variable_hardware_error_record: u32 = 0x00000008; +pub const variable_authenticated_write_access: u32 = 0x00000010; +pub const variable_time_based_authenticated_write_access: u32 = 0x00000020; +pub const variable_append_write: u32 = 0x00000040; +pub const variable_enhanced_authenticated_access: u32 = 0x00000080; + +pub const capsule_flags_persist_across_reset: u32 = 0x00010000; +pub const capsule_flags_populate_system_table: u32 = 0x00020000; +pub const capsule_flags_initiate_reset: u32 = 0x00040000; diff --git a/hikari/efi/services/services.zig b/hikari/efi/services/services.zig new file mode 100644 index 0000000..08b0ef3 --- /dev/null +++ b/hikari/efi/services/services.zig @@ -0,0 +1,9 @@ +//! Hikari EFI Services + +pub const boot = @import("boot.zig"); +pub const runtime = @import("runtime.zig"); +pub const system = @import("system.zig"); + +pub const BootServices = boot.BootServices; +pub const RuntimeServices = runtime.RuntimeServices; +pub const SystemTable = system.SystemTable; diff --git a/hikari/efi/services/system.zig b/hikari/efi/services/system.zig new file mode 100644 index 0000000..d0dc301 --- /dev/null +++ b/hikari/efi/services/system.zig @@ -0,0 +1,24 @@ +//! Hikari EFI System Table + +const types = @import("../types/types.zig"); +const table = @import("../types/table.zig"); +const boot = @import("boot.zig"); +const runtime = @import("runtime.zig"); +const simple_text_input = @import("../protocols/simple_text_input.zig"); +const simple_text_output = @import("../protocols/simple_text_output.zig"); + +pub const SystemTable = extern struct { + header: table.TableHeader, + firmware_vendor: [*:0]const types.Char16, + firmware_revision: u32, + console_input_handle: types.Handle, + console_input: *simple_text_input.SimpleTextInputProtocol, + console_output_handle: types.Handle, + console_output: *simple_text_output.SimpleTextOutputProtocol, + standard_error_handle: types.Handle, + standard_error: *simple_text_output.SimpleTextOutputProtocol, + runtime_services: *runtime.RuntimeServices, + boot_services: *boot.BootServices, + number_of_table_entries: usize, + configuration_table: [*]table.ConfigurationTableEntry, +}; diff --git a/hikari/efi/types/base.zig b/hikari/efi/types/base.zig new file mode 100644 index 0000000..6df9ddf --- /dev/null +++ b/hikari/efi/types/base.zig @@ -0,0 +1,33 @@ +//! Hikari EFI Base Types + +pub const Handle = *opaque {}; +pub const Event = *opaque {}; +pub const Status = usize; +pub const PhysicalAddress = u64; +pub const VirtualAddress = u64; +pub const Char8 = u8; +pub const Char16 = u16; +pub const Boolean = bool; +pub const Lba = u64; + +pub const Guid = extern struct { + time_low: u32, + time_mid: u16, + time_high_and_version: u16, + clock_sequence_and_node: [8]u8, + + pub fn equals(self: Guid, other: Guid) bool { + return self.time_low == other.time_low and + self.time_mid == other.time_mid and + self.time_high_and_version == other.time_high_and_version and + @as(u64, @bitCast(self.clock_sequence_and_node)) == @as(u64, @bitCast(other.clock_sequence_and_node)); + } +}; + +pub fn is_error(status: Status) bool { + return (status >> 63) == 1; +} + +pub fn is_success(status: Status) bool { + return status == 0; +} diff --git a/hikari/efi/types/block.zig b/hikari/efi/types/block.zig new file mode 100644 index 0000000..46b0ff6 --- /dev/null +++ b/hikari/efi/types/block.zig @@ -0,0 +1,18 @@ +//! Hikari EFI Block I/O Types + +const base = @import("base.zig"); + +pub const BlockIoMedia = extern struct { + media_id: u32, + removable_media: bool, + media_present: bool, + logical_partition: bool, + read_only: bool, + write_caching: bool, + block_size: u32, + io_align: u32, + last_block: base.Lba, + lowest_aligned_lba: base.Lba, + logical_blocks_per_physical_block: u32, + optimal_transfer_length_granularity: u32, +}; diff --git a/hikari/efi/types/graphics.zig b/hikari/efi/types/graphics.zig new file mode 100644 index 0000000..dca2097 --- /dev/null +++ b/hikari/efi/types/graphics.zig @@ -0,0 +1,49 @@ +//! Hikari EFI Graphics Types + +const base = @import("base.zig"); + +pub const PixelFormat = enum(u32) { + rgb = 0, + bgr = 1, + bitmask = 2, + blt_only = 3, +}; + +pub const PixelBitmask = extern struct { + red_mask: u32, + green_mask: u32, + blue_mask: u32, + reserved_mask: u32, +}; + +pub const BltPixel = extern struct { + blue: u8, + green: u8, + red: u8, + reserved: u8, +}; + +pub const BltOperation = enum(u32) { + video_fill = 0, + video_to_buffer = 1, + buffer_to_video = 2, + video_to_video = 3, +}; + +pub const GraphicsOutputModeInformation = extern struct { + version: u32, + horizontal_resolution: u32, + vertical_resolution: u32, + pixel_format: PixelFormat, + pixel_information: PixelBitmask, + pixels_per_scan_line: u32, +}; + +pub const GraphicsOutputProtocolMode = extern struct { + max_mode: u32, + mode: u32, + info: *GraphicsOutputModeInformation, + size_of_info: usize, + framebuffer_base: base.PhysicalAddress, + framebuffer_size: usize, +}; diff --git a/hikari/efi/types/input.zig b/hikari/efi/types/input.zig new file mode 100644 index 0000000..2daa966 --- /dev/null +++ b/hikari/efi/types/input.zig @@ -0,0 +1,8 @@ +//! Hikari EFI Input Types + +const base = @import("base.zig"); + +pub const InputKey = extern struct { + scan_code: u16, + unicode_char: base.Char16, +}; diff --git a/hikari/efi/types/memory.zig b/hikari/efi/types/memory.zig new file mode 100644 index 0000000..5775ad0 --- /dev/null +++ b/hikari/efi/types/memory.zig @@ -0,0 +1,42 @@ +//! Hikari EFI Memory Types + +const base = @import("base.zig"); + +pub const MemoryType = enum(u32) { + reserved = 0, + loader_code = 1, + loader_data = 2, + boot_services_code = 3, + boot_services_data = 4, + runtime_services_code = 5, + runtime_services_data = 6, + conventional = 7, + unusable = 8, + acpi_reclaim = 9, + acpi_nvs = 10, + mmio = 11, + mmio_port_space = 12, + pal_code = 13, + persistent = 14, + unaccepted = 15, +}; + +pub const AllocateType = enum(u32) { + any_pages = 0, + max_address = 1, + address = 2, +}; + +pub const MemoryDescriptor = extern struct { + memory_type: MemoryType, + physical_start: base.PhysicalAddress, + virtual_start: base.VirtualAddress, + number_of_pages: u64, + attribute: u64, +}; + +pub const LocateSearchType = enum(u32) { + all_handles = 0, + by_register_notify = 1, + by_protocol = 2, +}; diff --git a/hikari/efi/types/reset.zig b/hikari/efi/types/reset.zig new file mode 100644 index 0000000..4bf206f --- /dev/null +++ b/hikari/efi/types/reset.zig @@ -0,0 +1,8 @@ +//! Hikari EFI Reset Types + +pub const ResetType = enum(u32) { + cold = 0, + warm = 1, + shutdown = 2, + platform_specific = 3, +}; diff --git a/hikari/efi/types/table.zig b/hikari/efi/types/table.zig new file mode 100644 index 0000000..efcfd4b --- /dev/null +++ b/hikari/efi/types/table.zig @@ -0,0 +1,16 @@ +//! Hikari EFI Table Types + +const base = @import("base.zig"); + +pub const TableHeader = extern struct { + signature: u64, + revision: u32, + header_size: u32, + crc32: u32, + reserved: u32, +}; + +pub const ConfigurationTableEntry = extern struct { + vendor_guid: base.Guid, + vendor_table: *anyopaque, +}; diff --git a/hikari/efi/types/time.zig b/hikari/efi/types/time.zig new file mode 100644 index 0000000..6b860f5 --- /dev/null +++ b/hikari/efi/types/time.zig @@ -0,0 +1,26 @@ +//! Hikari EFI Time Types + +pub const Time = extern struct { + year: u16, + month: u8, + day: u8, + hour: u8, + minute: u8, + second: u8, + pad1: u8, + nanosecond: u32, + time_zone: i16, + daylight: u8, + pad2: u8, +}; + +pub const TimeCapabilities = extern struct { + resolution: u32, + accuracy: u32, + sets_to_zero: bool, +}; + +pub const timezone_unspecified: i16 = 0x07FF; + +pub const daylight_adjust: u8 = 0x01; +pub const daylight_time: u8 = 0x02; diff --git a/hikari/efi/types/types.zig b/hikari/efi/types/types.zig new file mode 100644 index 0000000..bcee99c --- /dev/null +++ b/hikari/efi/types/types.zig @@ -0,0 +1,23 @@ +//! Hikari EFI Types + +pub const base = @import("base.zig"); +pub const memory = @import("memory.zig"); +pub const table = @import("table.zig"); +pub const time = @import("time.zig"); +pub const input = @import("input.zig"); +pub const graphics = @import("graphics.zig"); +pub const unit = @import("unit.zig"); +pub const block = @import("block.zig"); +pub const reset = @import("reset.zig"); + +pub const Handle = base.Handle; +pub const Event = base.Event; +pub const Status = base.Status; +pub const PhysicalAddress = base.PhysicalAddress; +pub const VirtualAddress = base.VirtualAddress; +pub const Char16 = base.Char16; +pub const Guid = base.Guid; +pub const Lba = base.Lba; + +pub const is_error = base.is_error; +pub const is_success = base.is_success; diff --git a/hikari/efi/types/unit.zig b/hikari/efi/types/unit.zig new file mode 100644 index 0000000..8ad8783 --- /dev/null +++ b/hikari/efi/types/unit.zig @@ -0,0 +1,24 @@ +//! Hikari EFI Unit Types + +const base = @import("base.zig"); +const time = @import("time.zig"); + +pub const UnitInfo = extern struct { + size: u64, + unit_size: u64, + physical_size: u64, + create_time: time.Time, + last_access_time: time.Time, + modification_time: time.Time, + attribute: u64, + unit_name: [256]base.Char16, +}; + +pub const UnitSystemInfo = extern struct { + size: u64, + read_only: bool, + volume_size: u64, + free_space: u64, + block_size: u32, + volume_label: [256]base.Char16, +}; diff --git a/hikari/fs/afs/afs.zig b/hikari/fs/afs/afs.zig new file mode 100644 index 0000000..5551b95 --- /dev/null +++ b/hikari/fs/afs/afs.zig @@ -0,0 +1,30 @@ +//! Hikari AFS UnitSystem + +pub const constants = @import("constants.zig"); +pub const types = @import("types.zig"); +pub const btree = @import("btree.zig"); +pub const reader = @import("reader.zig"); + +pub const VolumeHeader = types.VolumeHeader; +pub const SpanDescriptor = types.SpanDescriptor; +pub const ChannelInfo = types.ChannelInfo; +pub const BTreeNodeDescriptor = types.BTreeNodeDescriptor; +pub const BTreeHeaderRecord = types.BTreeHeaderRecord; +pub const IndexKey = types.IndexKey; +pub const StackRecord = types.StackRecord; +pub const UnitRecord = types.UnitRecord; +pub const ThreadRecord = types.ThreadRecord; +pub const SpanKey = types.SpanKey; +pub const SpanRecord = types.SpanRecord; +pub const Permissions = types.Permissions; +pub const AliasInfo = types.AliasInfo; +pub const TwinInfo = types.TwinInfo; +pub const JournalInfoCell = types.JournalInfoCell; +pub const JournalHeader = types.JournalHeader; +pub const Timestamp = types.Timestamp; + +pub const BTree = btree.BTree; +pub const BTreeError = btree.BTreeError; + +pub const Reader = reader.Reader; +pub const ReadError = reader.ReadError; diff --git a/hikari/fs/afs/btree.zig b/hikari/fs/afs/btree.zig new file mode 100644 index 0000000..88b6dc0 --- /dev/null +++ b/hikari/fs/afs/btree.zig @@ -0,0 +1,354 @@ +//! Hikari AFS B-Tree + +const efi = @import("../../efi/efi.zig"); +const constants = @import("constants.zig"); +const types = @import("types.zig"); + +pub const BTreeError = error{ + read_failed, + invalid_node, + invalid_header, + key_not_found, + tree_empty, + allocation_failed, +}; + +pub const BTree = struct { + block_io: *efi.protocols.BlockIoProtocol, + boot_services: *efi.services.BootServices, + partition_start_lba: u64, + cell_size: u32, + base_span: types.SpanDescriptor, + node_size: u32, + root_node: u32, + first_leaf: u32, + last_leaf: u32, + depth: u16, + node_buffer: [*]u8, + + pub fn initialize( + block_io: *efi.protocols.BlockIoProtocol, + boot_services: *efi.services.BootServices, + partition_start_lba: u64, + cell_size: u32, + base_span: types.SpanDescriptor, + header: *const types.BTreeHeaderRecord, + ) BTreeError!BTree { + var node_buffer: [*]align(8) u8 = undefined; + const alloc_status = boot_services.allocate_pool( + .loader_data, + header.node_size, + &node_buffer, + ); + if (efi.types.is_error(alloc_status)) { + return BTreeError.allocation_failed; + } + + return BTree{ + .block_io = block_io, + .boot_services = boot_services, + .partition_start_lba = partition_start_lba, + .cell_size = cell_size, + .base_span = base_span, + .node_size = header.node_size, + .root_node = header.root_node, + .first_leaf = header.first_leaf_node, + .last_leaf = header.last_leaf_node, + .depth = header.depth, + .node_buffer = node_buffer, + }; + } + + pub fn read_node(self: *BTree, node_number: u32) BTreeError!*types.BTreeNodeDescriptor { + const nodes_per_cell = self.cell_size / self.node_size; + const cell_offset = node_number / nodes_per_cell; + const node_offset_in_cell = (node_number % nodes_per_cell) * self.node_size; + + const cell_lba = self.partition_start_lba + + ((self.base_span.start_cell + cell_offset) * self.cell_size / self.block_io.media.block_size); + + var cell_buffer: [*]align(8) u8 = undefined; + const alloc_status = self.boot_services.allocate_pool( + .loader_data, + self.cell_size, + &cell_buffer, + ); + if (efi.types.is_error(alloc_status)) { + return BTreeError.allocation_failed; + } + + const read_status = self.block_io.read_blocks( + self.block_io, + self.block_io.media.media_id, + cell_lba, + self.cell_size, + cell_buffer, + ); + if (efi.types.is_error(read_status)) { + return BTreeError.read_failed; + } + + const node_ptr = cell_buffer + node_offset_in_cell; + var i: u32 = 0; + while (i < self.node_size) : (i += 1) { + self.node_buffer[i] = node_ptr[i]; + } + + _ = self.boot_services.free_pool(cell_buffer); + + return @ptrCast(@alignCast(self.node_buffer)); + } + + pub fn get_record_offset(self: *BTree, record_index: u16) u16 { + const offset_table_start = self.node_size - (@as(u32, record_index) + 1) * 2; + const offset_ptr: *align(1) const u16 = @ptrCast(self.node_buffer + offset_table_start); + return offset_ptr.*; + } + + pub fn get_record_ptr(self: *BTree, record_index: u16) [*]u8 { + const offset = self.get_record_offset(record_index); + return self.node_buffer + offset; + } + + pub fn search_index( + self: *BTree, + parent_node_id: u32, + identity: []const u16, + ) BTreeError!?*const types.UnitRecord { + if (self.depth == 0) { + return BTreeError.tree_empty; + } + + var current_node = self.root_node; + var current_depth: u16 = self.depth; + + while (current_depth > 0) { + const node_desc = try self.read_node(current_node); + + if (node_desc.is_leaf()) { + return self.search_leaf_for_unit(node_desc, parent_node_id, identity); + } + + const next_node = self.search_index_node(node_desc, parent_node_id, identity); + if (next_node) |node| { + current_node = node; + current_depth -= 1; + } else { + return null; + } + } + + return null; + } + + fn search_index_node( + self: *BTree, + node_desc: *types.BTreeNodeDescriptor, + parent_node_id: u32, + identity: []const u16, + ) ?u32 { + var i: u16 = 0; + while (i < node_desc.record_count) : (i += 1) { + const record_ptr = self.get_record_ptr(i); + const key: *const types.IndexKey = @ptrCast(@alignCast(record_ptr)); + + const cmp = compare_keys(parent_node_id, identity, key); + if (cmp <= 0) { + const child_ptr: *align(1) const u32 = @ptrCast(record_ptr + key.key_length + 2); + return child_ptr.*; + } + } + + if (node_desc.record_count > 0) { + const last_record = self.get_record_ptr(node_desc.record_count - 1); + const last_key: *const types.IndexKey = @ptrCast(@alignCast(last_record)); + const child_ptr: *align(1) const u32 = @ptrCast(last_record + last_key.key_length + 2); + return child_ptr.*; + } + + return null; + } + + fn search_leaf_for_unit( + self: *BTree, + node_desc: *types.BTreeNodeDescriptor, + parent_node_id: u32, + identity: []const u16, + ) ?*const types.UnitRecord { + var i: u16 = 0; + while (i < node_desc.record_count) : (i += 1) { + const record_ptr = self.get_record_ptr(i); + const key: *const types.IndexKey = @ptrCast(@alignCast(record_ptr)); + + const cmp = compare_keys(parent_node_id, identity, key); + if (cmp == 0) { + const record_start = record_ptr + key.key_length + 2; + const record_type_ptr: *align(1) const u16 = @ptrCast(record_start); + const record_type = record_type_ptr.*; + + if (record_type == constants.index_record_type_unit) { + return @ptrCast(@alignCast(record_start)); + } + } + } + + return null; + } + + pub fn search_index_for_stack( + self: *BTree, + parent_node_id: u32, + identity: []const u16, + ) BTreeError!?*const types.StackRecord { + if (self.depth == 0) { + return BTreeError.tree_empty; + } + + var current_node = self.root_node; + var current_depth: u16 = self.depth; + + while (current_depth > 0) { + const node_desc = try self.read_node(current_node); + + if (node_desc.is_leaf()) { + return self.search_leaf_for_stack(node_desc, parent_node_id, identity); + } + + const next_node = self.search_index_node(node_desc, parent_node_id, identity); + if (next_node) |node| { + current_node = node; + current_depth -= 1; + } else { + return null; + } + } + + return null; + } + + fn search_leaf_for_stack( + self: *BTree, + node_desc: *types.BTreeNodeDescriptor, + parent_node_id: u32, + identity: []const u16, + ) ?*const types.StackRecord { + var i: u16 = 0; + while (i < node_desc.record_count) : (i += 1) { + const record_ptr = self.get_record_ptr(i); + const key: *const types.IndexKey = @ptrCast(@alignCast(record_ptr)); + + const cmp = compare_keys(parent_node_id, identity, key); + if (cmp == 0) { + const record_start = record_ptr + key.key_length + 2; + const record_type_ptr: *align(1) const u16 = @ptrCast(record_start); + const record_type = record_type_ptr.*; + + if (record_type == constants.index_record_type_stack) { + return @ptrCast(@alignCast(record_start)); + } + } + } + + return null; + } + + pub fn get_thread_record( + self: *BTree, + node_id: u32, + ) BTreeError!?*const types.ThreadRecord { + var empty_identity: [0]u16 = undefined; + return self.search_thread(node_id, &empty_identity); + } + + fn search_thread( + self: *BTree, + node_id: u32, + identity: []const u16, + ) BTreeError!?*const types.ThreadRecord { + if (self.depth == 0) { + return BTreeError.tree_empty; + } + + var current_node = self.root_node; + var current_depth: u16 = self.depth; + + while (current_depth > 0) { + const node_desc = try self.read_node(current_node); + + if (node_desc.is_leaf()) { + return self.search_leaf_for_thread(node_desc, node_id, identity); + } + + const next_node = self.search_index_node(node_desc, node_id, identity); + if (next_node) |node| { + current_node = node; + current_depth -= 1; + } else { + return null; + } + } + + return null; + } + + fn search_leaf_for_thread( + self: *BTree, + node_desc: *types.BTreeNodeDescriptor, + node_id: u32, + identity: []const u16, + ) ?*const types.ThreadRecord { + var i: u16 = 0; + while (i < node_desc.record_count) : (i += 1) { + const record_ptr = self.get_record_ptr(i); + const key: *const types.IndexKey = @ptrCast(@alignCast(record_ptr)); + + const cmp = compare_keys(node_id, identity, key); + if (cmp == 0) { + const record_start = record_ptr + key.key_length + 2; + const record_type_ptr: *align(1) const u16 = @ptrCast(record_start); + const record_type = record_type_ptr.*; + + if (record_type == constants.index_record_type_stack_thread or + record_type == constants.index_record_type_unit_thread) + { + return @ptrCast(@alignCast(record_start)); + } + } + } + + return null; + } +}; + +fn compare_keys(parent_node_id: u32, identity: []const u16, key: *const types.IndexKey) i32 { + if (parent_node_id < key.parent_node_id) { + return -1; + } + if (parent_node_id > key.parent_node_id) { + return 1; + } + + const key_identity_len = key.get_identity_length(); + const min_len = if (identity.len < key_identity_len) identity.len else key_identity_len; + + var i: usize = 0; + while (i < min_len) : (i += 1) { + const a = identity[i]; + const b = key.identity[i]; + if (a < b) { + return -1; + } + if (a > b) { + return 1; + } + } + + if (identity.len < key_identity_len) { + return -1; + } + if (identity.len > key_identity_len) { + return 1; + } + + return 0; +} diff --git a/hikari/fs/afs/constants.zig b/hikari/fs/afs/constants.zig new file mode 100644 index 0000000..892fef1 --- /dev/null +++ b/hikari/fs/afs/constants.zig @@ -0,0 +1,66 @@ +//! Hikari AFS Constants + +pub const signature: u64 = 0x2153464142494B41; // "AKIBAFS!" +pub const version: u16 = 0x0001; + +pub const volume_header_cell: u64 = 0; +pub const volume_header_size: u32 = 512; +pub const alternate_volume_header_offset: u64 = 1024; + +pub const default_cell_size: u32 = 4096; +pub const minimum_cell_size: u32 = 512; +pub const maximum_cell_size: u32 = 65536; + +pub const max_identity_length: usize = 1024; + +pub const special_node_id_origin: u32 = 1; +pub const special_node_id_origin_stack: u32 = 2; +pub const special_node_id_span_overflow: u32 = 3; +pub const special_node_id_index: u32 = 4; +pub const special_node_id_attributes: u32 = 5; +pub const special_node_id_allocation_map: u32 = 6; +pub const special_node_id_startup: u32 = 7; +pub const special_node_id_repair: u32 = 8; +pub const first_user_node_id: u32 = 16; + +pub const index_record_type_stack: u16 = 0x0001; +pub const index_record_type_unit: u16 = 0x0002; +pub const index_record_type_stack_thread: u16 = 0x0003; +pub const index_record_type_unit_thread: u16 = 0x0004; + +pub const btree_node_type_leaf: i8 = -1; +pub const btree_node_type_index: i8 = 0; +pub const btree_node_type_header: i8 = 1; +pub const btree_node_type_map: i8 = 2; + +pub const btree_header_node_number: u32 = 0; + +pub const channel_data: u8 = 0x00; +pub const channel_resource: u8 = 0xFF; + +pub const unit_flag_locked: u16 = 0x0001; +pub const unit_flag_has_thread: u16 = 0x0002; +pub const unit_flag_has_alias: u16 = 0x0004; +pub const unit_flag_has_security: u16 = 0x0008; +pub const unit_flag_has_twins: u16 = 0x0010; +pub const unit_flag_has_resource_channel: u16 = 0x0020; + +pub const compression_none: u32 = 0; +pub const compression_zlib: u32 = 1; +pub const compression_lz4: u32 = 2; +pub const compression_zstd: u32 = 3; + +pub const encryption_none: u32 = 0; +pub const encryption_aes_128_xts: u32 = 1; +pub const encryption_aes_256_xts: u32 = 2; + +pub const attribute_inline_data_max: u32 = 3802; + +pub const journal_signature: u32 = 0x4A4E524C; // "JNRL" +pub const journal_header_size: u32 = 512; +pub const journal_info_cell: u64 = 2; + +pub const journal_info_on_other_device: u32 = 0x00000001; +pub const journal_info_needs_init: u32 = 0x00000002; + +pub const span_inline_count: usize = 8; diff --git a/hikari/fs/afs/reader.zig b/hikari/fs/afs/reader.zig new file mode 100644 index 0000000..1bb74d2 --- /dev/null +++ b/hikari/fs/afs/reader.zig @@ -0,0 +1,333 @@ +//! Hikari AFS Reader + +const efi = @import("../../efi/efi.zig"); +const constants = @import("constants.zig"); +const types = @import("types.zig"); +const btree = @import("btree.zig"); + +pub const ReadError = error{ + invalid_volume_header, + read_failed, + allocation_failed, + not_found, + not_a_stack, + unit_too_large, + invalid_span, + btree_error, +}; + +pub const Reader = struct { + block_io: *efi.protocols.BlockIoProtocol, + boot_services: *efi.services.BootServices, + partition_start_lba: u64, + volume_header: types.VolumeHeader, + cell_size: u32, + index: btree.BTree, + span_overflow: ?btree.BTree, + cell_buffer: [*]u8, + + pub fn initialize( + block_io: *efi.protocols.BlockIoProtocol, + boot_services: *efi.services.BootServices, + partition_start_lba: u64, + ) ReadError!Reader { + const block_size = block_io.media.block_size; + + var sector_buffer: [*]align(8) u8 = undefined; + var alloc_status = boot_services.allocate_pool( + .loader_data, + block_size, + §or_buffer, + ); + if (efi.types.is_error(alloc_status)) { + return ReadError.allocation_failed; + } + + const read_status = block_io.read_blocks( + block_io, + block_io.media.media_id, + partition_start_lba, + block_size, + sector_buffer, + ); + if (efi.types.is_error(read_status)) { + return ReadError.read_failed; + } + + const volume_header: *const types.VolumeHeader = @ptrCast(@alignCast(sector_buffer)); + if (!volume_header.is_valid()) { + return ReadError.invalid_volume_header; + } + + const cell_size = volume_header.cell_size; + + var cell_buffer: [*]align(8) u8 = undefined; + alloc_status = boot_services.allocate_pool( + .loader_data, + cell_size, + &cell_buffer, + ); + if (efi.types.is_error(alloc_status)) { + return ReadError.allocation_failed; + } + + const index_header = try read_btree_header( + block_io, + boot_services, + partition_start_lba, + cell_size, + volume_header.index_span, + ); + + const index = btree.BTree.initialize( + block_io, + boot_services, + partition_start_lba, + cell_size, + volume_header.index_span, + index_header, + ) catch { + return ReadError.btree_error; + }; + + var span_overflow: ?btree.BTree = null; + if (!volume_header.span_overflow_span.is_empty()) { + const span_overflow_header = try read_btree_header( + block_io, + boot_services, + partition_start_lba, + cell_size, + volume_header.span_overflow_span, + ); + + span_overflow = btree.BTree.initialize( + block_io, + boot_services, + partition_start_lba, + cell_size, + volume_header.span_overflow_span, + span_overflow_header, + ) catch null; + } + + const header_copy = volume_header.*; + + _ = boot_services.free_pool(sector_buffer); + + return Reader{ + .block_io = block_io, + .boot_services = boot_services, + .partition_start_lba = partition_start_lba, + .volume_header = header_copy, + .cell_size = cell_size, + .index = index, + .span_overflow = span_overflow, + .cell_buffer = cell_buffer, + }; + } + + pub fn open_location(self: *Reader, location: []const u8) ReadError!types.UnitRecord { + var current_node_id: u32 = constants.special_node_id_origin_stack; + + var start: usize = 0; + if (location.len > 0 and (location[0] == '/' or location[0] == '\\')) { + start = 1; + } + + var iter_start = start; + var last_unit: ?types.UnitRecord = null; + + while (iter_start < location.len) { + var iter_end = iter_start; + while (iter_end < location.len and location[iter_end] != '/' and location[iter_end] != '\\') { + iter_end += 1; + } + + if (iter_end == iter_start) { + iter_start = iter_end + 1; + continue; + } + + const component = location[iter_start..iter_end]; + + var identity_utf16: [constants.max_identity_length]u16 = undefined; + var identity_len: usize = 0; + for (component) |byte| { + identity_utf16[identity_len] = byte; + identity_len += 1; + } + const identity = identity_utf16[0..identity_len]; + + const stack_record = self.index.search_index_for_stack(current_node_id, identity) catch { + return ReadError.btree_error; + }; + + if (stack_record) |stack| { + current_node_id = stack.node_id; + iter_start = iter_end + 1; + continue; + } + + const unit_record = self.index.search_index(current_node_id, identity) catch { + return ReadError.btree_error; + }; + + if (unit_record) |unit| { + if (iter_end >= location.len) { + last_unit = unit.*; + break; + } else { + return ReadError.not_a_stack; + } + } + + return ReadError.not_found; + } + + if (last_unit) |unit| { + return unit; + } + + return ReadError.not_found; + } + + pub fn read_cell(self: *Reader, cell_number: u64) ReadError!void { + const cell_lba = self.partition_start_lba + + (cell_number * self.cell_size / self.block_io.media.block_size); + + const read_status = self.block_io.read_blocks( + self.block_io, + self.block_io.media.media_id, + cell_lba, + self.cell_size, + self.cell_buffer, + ); + if (efi.types.is_error(read_status)) { + return ReadError.read_failed; + } + } + + pub fn read_unit(self: *Reader, unit: *const types.UnitRecord, buffer: [*]u8, max_size: u64) ReadError!u64 { + const channel = &unit.data_channel; + const unit_size = channel.logical_size; + + if (unit_size > max_size) { + return ReadError.unit_too_large; + } + + var bytes_read: u64 = 0; + var span_index: usize = 0; + + while (bytes_read < unit_size and span_index < constants.span_inline_count) { + const span = channel.get_span(span_index); + if (span == null) { + break; + } + + const span_bytes = span.?.byte_size(self.cell_size); + const bytes_remaining = unit_size - bytes_read; + const bytes_to_read = if (bytes_remaining < span_bytes) bytes_remaining else span_bytes; + + try self.read_span_data(span.?, buffer + bytes_read, bytes_to_read); + bytes_read += bytes_to_read; + span_index += 1; + } + + if (bytes_read < unit_size and self.span_overflow != null) { + bytes_read = try self.read_overflow_spans(unit, buffer, bytes_read, unit_size); + } + + return bytes_read; + } + + fn read_span_data(self: *Reader, span: *const types.SpanDescriptor, buffer: [*]u8, size: u64) ReadError!void { + var bytes_read: u64 = 0; + var current_cell = span.start_cell; + + while (bytes_read < size) { + try self.read_cell(current_cell); + + const bytes_remaining = size - bytes_read; + const bytes_to_copy = if (bytes_remaining < self.cell_size) bytes_remaining else self.cell_size; + + var i: u64 = 0; + while (i < bytes_to_copy) : (i += 1) { + buffer[bytes_read + i] = self.cell_buffer[i]; + } + + bytes_read += bytes_to_copy; + current_cell += 1; + } + } + + fn read_overflow_spans( + self: *Reader, + unit: *const types.UnitRecord, + buffer: [*]u8, + start_offset: u64, + total_size: u64, + ) ReadError!u64 { + _ = self; + _ = unit; + _ = buffer; + _ = start_offset; + return total_size; + } + + pub fn read_unit_to_allocated(self: *Reader, unit: *const types.UnitRecord) ReadError!struct { buffer: [*]u8, size: u64 } { + const unit_size = unit.data_channel.logical_size; + + var buffer: [*]align(8) u8 = undefined; + const alloc_status = self.boot_services.allocate_pool( + .loader_data, + unit_size, + &buffer, + ); + if (efi.types.is_error(alloc_status)) { + return ReadError.allocation_failed; + } + + const bytes_read = try self.read_unit(unit, buffer, unit_size); + return .{ .buffer = buffer, .size = bytes_read }; + } +}; + +fn read_btree_header( + block_io: *efi.protocols.BlockIoProtocol, + boot_services: *efi.services.BootServices, + partition_start_lba: u64, + cell_size: u32, + span: types.SpanDescriptor, +) ReadError!*const types.BTreeHeaderRecord { + var cell_buffer: [*]align(8) u8 = undefined; + const alloc_status = boot_services.allocate_pool( + .loader_data, + cell_size, + &cell_buffer, + ); + if (efi.types.is_error(alloc_status)) { + return ReadError.allocation_failed; + } + + const cell_lba = partition_start_lba + + (span.start_cell * cell_size / block_io.media.block_size); + + const read_status = block_io.read_blocks( + block_io, + block_io.media.media_id, + cell_lba, + cell_size, + cell_buffer, + ); + if (efi.types.is_error(read_status)) { + return ReadError.read_failed; + } + + const node_desc: *const types.BTreeNodeDescriptor = @ptrCast(@alignCast(cell_buffer)); + if (!node_desc.is_header()) { + return ReadError.read_failed; + } + + const header_offset = @sizeOf(types.BTreeNodeDescriptor); + return @ptrCast(@alignCast(cell_buffer + header_offset)); +} diff --git a/hikari/fs/afs/types.zig b/hikari/fs/afs/types.zig new file mode 100644 index 0000000..bf9d1a8 --- /dev/null +++ b/hikari/fs/afs/types.zig @@ -0,0 +1,336 @@ +//! Hikari AFS Types + +const constants = @import("constants.zig"); + +pub const VolumeHeader = extern struct { + signature: u64, + version: u16, + attributes: u32, + last_bind_timestamp: u64, + last_check_timestamp: u64, + creation_timestamp: u64, + modification_timestamp: u64, + backup_timestamp: u64, + checked_timestamp: u64, + unit_count: u32, + stack_count: u32, + cell_size: u32, + total_cells: u32, + free_cells: u32, + next_node_id: u32, + write_count: u32, + encoding_bitmap: u64, + allocation_map_size: u32, + allocation_map_clump: u32, + index_node_size: u32, + index_total_nodes: u32, + index_free_nodes: u32, + index_clump_size: u32, + index_root_node: u32, + index_first_leaf: u32, + index_last_leaf: u32, + index_depth: u16, + index_record_count: u32, + span_overflow_node_size: u32, + span_overflow_total_nodes: u32, + span_overflow_free_nodes: u32, + span_overflow_clump_size: u32, + span_overflow_root_node: u32, + span_overflow_first_leaf: u32, + span_overflow_last_leaf: u32, + span_overflow_depth: u16, + span_overflow_record_count: u32, + attributes_node_size: u32, + attributes_total_nodes: u32, + attributes_free_nodes: u32, + attributes_clump_size: u32, + attributes_root_node: u32, + attributes_first_leaf: u32, + attributes_last_leaf: u32, + attributes_depth: u16, + attributes_record_count: u32, + allocation_map_span: SpanDescriptor, + index_span: SpanDescriptor, + span_overflow_span: SpanDescriptor, + attributes_span: SpanDescriptor, + startup_span: SpanDescriptor, + journal_info_cell: u64, + journal_info_size: u32, + compression_type: u32, + encryption_type: u32, + reserved: [64]u8, + + pub fn is_valid(self: *const VolumeHeader) bool { + if (self.signature != constants.signature) { + return false; + } + if (self.version != constants.version) { + return false; + } + if (self.cell_size < constants.minimum_cell_size or + self.cell_size > constants.maximum_cell_size) + { + return false; + } + return true; + } +}; + +pub const SpanDescriptor = extern struct { + start_cell: u64, + cell_count: u64, + + pub fn is_empty(self: *const SpanDescriptor) bool { + return self.cell_count == 0; + } + + pub fn end_cell(self: *const SpanDescriptor) u64 { + return self.start_cell + self.cell_count; + } + + pub fn contains(self: *const SpanDescriptor, cell: u64) bool { + return cell >= self.start_cell and cell < self.end_cell(); + } + + pub fn byte_size(self: *const SpanDescriptor, cell_size: u32) u64 { + return self.cell_count * cell_size; + } +}; + +pub const ChannelInfo = extern struct { + logical_size: u64, + physical_size: u64, + clump_size: u32, + total_cells: u32, + spans: [constants.span_inline_count]SpanDescriptor, + + pub fn get_span(self: *const ChannelInfo, index: usize) ?*const SpanDescriptor { + if (index >= constants.span_inline_count) { + return null; + } + if (self.spans[index].is_empty()) { + return null; + } + return &self.spans[index]; + } +}; + +pub const BTreeNodeDescriptor = extern struct { + forward_link: u32, + backward_link: u32, + node_type: i8, + height: u8, + record_count: u16, + reserved: u16, + + pub fn is_leaf(self: *const BTreeNodeDescriptor) bool { + return self.node_type == constants.btree_node_type_leaf; + } + + pub fn is_index(self: *const BTreeNodeDescriptor) bool { + return self.node_type == constants.btree_node_type_index; + } + + pub fn is_header(self: *const BTreeNodeDescriptor) bool { + return self.node_type == constants.btree_node_type_header; + } + + pub fn is_map(self: *const BTreeNodeDescriptor) bool { + return self.node_type == constants.btree_node_type_map; + } +}; + +pub const BTreeHeaderRecord = extern struct { + depth: u16, + root_node: u32, + leaf_record_count: u32, + first_leaf_node: u32, + last_leaf_node: u32, + node_size: u16, + max_key_length: u16, + total_nodes: u32, + free_nodes: u32, + reserved1: u16, + clump_size: u32, + btree_type: u8, + key_compare_type: u8, + attributes: u32, + reserved2: [64]u8, +}; + +pub const IndexKey = extern struct { + key_length: u16, + parent_node_id: u32, + identity: [constants.max_identity_length]u16, + + pub fn get_identity_length(self: *const IndexKey) usize { + const total_key_len = self.key_length; + if (total_key_len <= 6) { + return 0; + } + return (total_key_len - 6) / 2; + } +}; + +pub const StackRecord = extern struct { + record_type: u16, + flags: u16, + valence: u32, + node_id: u32, + creation_timestamp: u64, + modification_timestamp: u64, + attribute_modification_timestamp: u64, + access_timestamp: u64, + backup_timestamp: u64, + permissions: Permissions, + special: SpecialInfo, + text_encoding: u32, + reserved: u32, + + pub fn is_empty(self: *const StackRecord) bool { + return self.valence == 0; + } +}; + +pub const UnitRecord = extern struct { + record_type: u16, + flags: u16, + reserved1: u32, + node_id: u32, + creation_timestamp: u64, + modification_timestamp: u64, + attribute_modification_timestamp: u64, + access_timestamp: u64, + backup_timestamp: u64, + permissions: Permissions, + special: SpecialInfo, + text_encoding: u32, + reserved2: u32, + data_channel: ChannelInfo, + resource_channel: ChannelInfo, + + pub fn has_resource_channel(self: *const UnitRecord) bool { + return (self.flags & constants.unit_flag_has_resource_channel) != 0; + } + + pub fn has_twins(self: *const UnitRecord) bool { + return (self.flags & constants.unit_flag_has_twins) != 0; + } +}; + +pub const ThreadRecord = extern struct { + record_type: u16, + reserved: u16, + parent_node_id: u32, + identity_length: u16, + identity: [constants.max_identity_length]u16, + + pub fn get_identity(self: *const ThreadRecord) []const u16 { + return self.identity[0..self.identity_length]; + } +}; + +pub const SpanKey = extern struct { + key_length: u16, + channel_type: u8, + padding: u8, + node_id: u32, + start_cell: u32, +}; + +pub const SpanRecord = extern struct { + start_cell: u64, + cell_count: u64, +}; + +pub const AttributeKey = extern struct { + key_length: u16, + padding: u16, + node_id: u32, + start_cell: u32, + attribute_identity_length: u16, + attribute_identity: [128]u16, +}; + +pub const AttributeInlineRecord = extern struct { + record_type: u32, + reserved: [4]u8, + data_size: u32, + data: [constants.attribute_inline_data_max]u8, +}; + +pub const AttributeChannelRecord = extern struct { + record_type: u32, + reserved: u32, + channel: ChannelInfo, +}; + +pub const Permissions = extern struct { + owner_id: u32, + group_id: u32, + admin_flags: u8, + owner_flags: u8, + mode: u16, + special: SpecialPermissions, +}; + +pub const SpecialPermissions = extern union { + inode_number: u32, + link_count: u32, + raw_device: u32, +}; + +pub const SpecialInfo = extern union { + raw: [16]u8, + alias_info: AliasInfo, + twin_info: TwinInfo, +}; + +pub const AliasInfo = extern struct { + target_node_id: u32, + target_parent_node_id: u32, + reserved: [8]u8, +}; + +pub const TwinInfo = extern struct { + first_twin_node_id: u32, + reserved: [12]u8, +}; + +pub const JournalInfoCell = extern struct { + flags: u32, + device_signature: [32]u32, + offset: u64, + size: u64, + reserved: [128]u8, +}; + +pub const JournalHeader = extern struct { + magic: u32, + endian: u32, + start: u64, + end: u64, + size: u64, + cell_size: u32, + checksum_type: u32, + checksum: u32, + sequence: u64, +}; + +pub const JournalCellList = extern struct { + max_cells: u16, + cell_count: u16, + reserved: u32, + cells: [1]JournalCellInfo, +}; + +pub const JournalCellInfo = extern struct { + cell_number: u64, + cell_size: u64, +}; + +pub const Timestamp = extern struct { + seconds: i64, + nanoseconds: u32, + reserved: u32, +}; diff --git a/hikari/fs/fat32/constants.zig b/hikari/fs/fat32/constants.zig new file mode 100644 index 0000000..8abc7b9 --- /dev/null +++ b/hikari/fs/fat32/constants.zig @@ -0,0 +1,43 @@ +//! Hikari FAT32 Constants + +pub const boot_signature: u16 = 0xAA55; +pub const fs_type_fat32: [8]u8 = .{ 'F', 'A', 'T', '3', '2', ' ', ' ', ' ' }; + +pub const attribute_read_only: u8 = 0x01; +pub const attribute_hidden: u8 = 0x02; +pub const attribute_system: u8 = 0x04; +pub const attribute_volume_id: u8 = 0x08; +pub const attribute_stack: u8 = 0x10; +pub const attribute_archive: u8 = 0x20; +pub const attribute_long_identity: u8 = attribute_read_only | attribute_hidden | attribute_system | attribute_volume_id; +pub const attribute_long_identity_mask: u8 = attribute_read_only | attribute_hidden | attribute_system | attribute_volume_id | attribute_stack | attribute_archive; + +pub const entry_free: u8 = 0xE5; +pub const entry_end: u8 = 0x00; +pub const entry_kanji_lead: u8 = 0x05; + +pub const cluster_free: u32 = 0x00000000; +pub const cluster_reserved_start: u32 = 0x00000001; +pub const cluster_reserved_end: u32 = 0x00000001; +pub const cluster_data_start: u32 = 0x00000002; +pub const cluster_bad: u32 = 0x0FFFFFF7; +pub const cluster_end_of_chain_start: u32 = 0x0FFFFFF8; +pub const cluster_end_of_chain: u32 = 0x0FFFFFFF; +pub const cluster_mask: u32 = 0x0FFFFFFF; + +pub const long_identity_sequence_mask: u8 = 0x1F; +pub const long_identity_last_entry: u8 = 0x40; +pub const long_identity_chars_per_entry: usize = 13; + +pub const short_identity_length: usize = 8; +pub const short_extension_length: usize = 3; +pub const short_identity_total_length: usize = short_identity_length + short_extension_length; + +pub const sector_size_minimum: u16 = 512; +pub const sector_size_maximum: u16 = 4096; + +pub const fs_info_signature_1: u32 = 0x41615252; +pub const fs_info_signature_2: u32 = 0x61417272; +pub const fs_info_signature_3: u32 = 0xAA550000; +pub const fs_info_unknown_free_count: u32 = 0xFFFFFFFF; +pub const fs_info_unknown_next_free: u32 = 0xFFFFFFFF; diff --git a/hikari/fs/fat32/fat32.zig b/hikari/fs/fat32/fat32.zig new file mode 100644 index 0000000..295b586 --- /dev/null +++ b/hikari/fs/fat32/fat32.zig @@ -0,0 +1,15 @@ +//! Hikari FAT32 UnitSystem + +pub const constants = @import("constants.zig"); +pub const types = @import("types.zig"); +pub const reader = @import("reader.zig"); + +pub const BootSector = types.BootSector; +pub const FsInfo = types.FsInfo; +pub const StackEntry = types.StackEntry; +pub const LongIdentityEntry = types.LongIdentityEntry; +pub const TimeFormat = types.TimeFormat; +pub const DateFormat = types.DateFormat; + +pub const Reader = reader.Reader; +pub const ReadError = reader.ReadError; diff --git a/hikari/fs/fat32/reader.zig b/hikari/fs/fat32/reader.zig new file mode 100644 index 0000000..bb4b63b --- /dev/null +++ b/hikari/fs/fat32/reader.zig @@ -0,0 +1,303 @@ +//! Hikari FAT32 Reader + +const efi = @import("../../efi/efi.zig"); +const constants = @import("constants.zig"); +const types = @import("types.zig"); + +pub const ReadError = error{ + invalid_boot_sector, + invalid_fs_info, + read_failed, + allocation_failed, + not_found, + not_a_stack, + invalid_cluster, + unit_too_large, +}; + +pub const Reader = struct { + block_io: *efi.protocols.BlockIoProtocol, + boot_services: *efi.services.BootServices, + partition_start_lba: u64, + boot_sector: types.BootSector, + bytes_per_sector: u32, + sectors_per_cluster: u32, + bytes_per_cluster: u32, + fat_start_lba: u64, + data_start_lba: u64, + origin_cluster: u32, + cluster_buffer: [*]u8, + sector_buffer: [*]u8, + + pub fn initialize( + block_io: *efi.protocols.BlockIoProtocol, + boot_services: *efi.services.BootServices, + partition_start_lba: u64, + ) ReadError!Reader { + const block_size = block_io.media.block_size; + + var sector_buffer: [*]align(8) u8 = undefined; + var alloc_status = boot_services.allocate_pool( + .loader_data, + block_size, + §or_buffer, + ); + if (efi.types.is_error(alloc_status)) { + return ReadError.allocation_failed; + } + + const read_status = block_io.read_blocks( + block_io, + block_io.media.media_id, + partition_start_lba, + block_size, + sector_buffer, + ); + if (efi.types.is_error(read_status)) { + return ReadError.read_failed; + } + + const boot_sector: *const types.BootSector = @ptrCast(@alignCast(sector_buffer)); + if (!boot_sector.is_valid()) { + return ReadError.invalid_boot_sector; + } + + const bytes_per_sector: u32 = boot_sector.bytes_per_sector; + const sectors_per_cluster: u32 = boot_sector.sectors_per_cluster; + const bytes_per_cluster = bytes_per_sector * sectors_per_cluster; + + var cluster_buffer: [*]align(8) u8 = undefined; + alloc_status = boot_services.allocate_pool( + .loader_data, + bytes_per_cluster, + &cluster_buffer, + ); + if (efi.types.is_error(alloc_status)) { + return ReadError.allocation_failed; + } + + const fat_start_lba = partition_start_lba + boot_sector.get_fat_start_sector(); + const data_start_lba = partition_start_lba + boot_sector.get_data_start_sector(); + + return Reader{ + .block_io = block_io, + .boot_services = boot_services, + .partition_start_lba = partition_start_lba, + .boot_sector = boot_sector.*, + .bytes_per_sector = bytes_per_sector, + .sectors_per_cluster = sectors_per_cluster, + .bytes_per_cluster = bytes_per_cluster, + .fat_start_lba = fat_start_lba, + .data_start_lba = data_start_lba, + .origin_cluster = boot_sector.origin_cluster, + .cluster_buffer = cluster_buffer, + .sector_buffer = sector_buffer, + }; + } + + pub fn read_cluster(self: *Reader, cluster: u32) ReadError!void { + if (cluster < 2) { + return ReadError.invalid_cluster; + } + + const cluster_lba = self.data_start_lba + (@as(u64, cluster - 2) * self.sectors_per_cluster); + const read_status = self.block_io.read_blocks( + self.block_io, + self.block_io.media.media_id, + cluster_lba, + self.bytes_per_cluster, + self.cluster_buffer, + ); + if (efi.types.is_error(read_status)) { + return ReadError.read_failed; + } + } + + pub fn get_next_cluster(self: *Reader, cluster: u32) ReadError!?u32 { + const fat_offset = cluster * 4; + const fat_sector = fat_offset / self.bytes_per_sector; + const fat_offset_in_sector = fat_offset % self.bytes_per_sector; + + const fat_sector_lba = self.fat_start_lba + fat_sector; + const read_status = self.block_io.read_blocks( + self.block_io, + self.block_io.media.media_id, + fat_sector_lba, + self.bytes_per_sector, + self.sector_buffer, + ); + if (efi.types.is_error(read_status)) { + return ReadError.read_failed; + } + + const fat_entry_ptr: *align(1) const u32 = @ptrCast(self.sector_buffer + fat_offset_in_sector); + const next_cluster = fat_entry_ptr.* & constants.cluster_mask; + + if (next_cluster >= constants.cluster_end_of_chain_start) { + return null; + } + if (next_cluster == constants.cluster_bad) { + return ReadError.invalid_cluster; + } + if (next_cluster < 2) { + return ReadError.invalid_cluster; + } + + return next_cluster; + } + + pub fn find_in_stack(self: *Reader, stack_cluster: u32, identity: []const u8) ReadError!?types.StackEntry { + var current_cluster = stack_cluster; + + while (true) { + try self.read_cluster(current_cluster); + + const entries_per_cluster = self.bytes_per_cluster / @sizeOf(types.StackEntry); + const entries: [*]const types.StackEntry = @ptrCast(@alignCast(self.cluster_buffer)); + + var i: usize = 0; + while (i < entries_per_cluster) : (i += 1) { + const entry = &entries[i]; + + if (entry.is_end()) { + return null; + } + if (entry.is_free()) { + continue; + } + if (entry.is_long_identity()) { + continue; + } + if (entry.is_volume_id()) { + continue; + } + + var short_identity_buffer: [12]u8 = undefined; + const short_identity_len = entry.get_short_identity(&short_identity_buffer); + const short_identity = short_identity_buffer[0..short_identity_len]; + + if (case_insensitive_equal(short_identity, identity)) { + return entry.*; + } + } + + const next_cluster = try self.get_next_cluster(current_cluster); + if (next_cluster) |next| { + current_cluster = next; + } else { + return null; + } + } + } + + pub fn open_location(self: *Reader, location: []const u8) ReadError!types.StackEntry { + var current_cluster = self.origin_cluster; + var is_stack = true; + + var start: usize = 0; + if (location.len > 0 and (location[0] == '/' or location[0] == '\\')) { + start = 1; + } + + var iter_start = start; + while (iter_start < location.len) { + if (!is_stack) { + return ReadError.not_a_stack; + } + + var iter_end = iter_start; + while (iter_end < location.len and location[iter_end] != '/' and location[iter_end] != '\\') { + iter_end += 1; + } + + if (iter_end == iter_start) { + iter_start = iter_end + 1; + continue; + } + + const component = location[iter_start..iter_end]; + const entry = try self.find_in_stack(current_cluster, component); + + if (entry) |found| { + current_cluster = found.get_first_cluster(); + is_stack = found.is_stack(); + + if (iter_end >= location.len) { + return found; + } + } else { + return ReadError.not_found; + } + + iter_start = iter_end + 1; + } + + return ReadError.not_found; + } + + pub fn read_unit(self: *Reader, entry: *const types.StackEntry, buffer: [*]u8, max_size: u32) ReadError!u32 { + const unit_size = entry.unit_size; + if (unit_size > max_size) { + return ReadError.unit_too_large; + } + + var current_cluster = entry.get_first_cluster(); + var bytes_read: u32 = 0; + + while (bytes_read < unit_size) { + try self.read_cluster(current_cluster); + + const bytes_remaining = unit_size - bytes_read; + const bytes_to_copy = if (bytes_remaining < self.bytes_per_cluster) bytes_remaining else self.bytes_per_cluster; + + var i: u32 = 0; + while (i < bytes_to_copy) : (i += 1) { + buffer[bytes_read + i] = self.cluster_buffer[i]; + } + + bytes_read += bytes_to_copy; + + if (bytes_read < unit_size) { + const next_cluster = try self.get_next_cluster(current_cluster); + if (next_cluster) |next| { + current_cluster = next; + } else { + break; + } + } + } + + return bytes_read; + } + + pub fn read_unit_to_allocated(self: *Reader, entry: *const types.StackEntry) ReadError!struct { buffer: [*]u8, size: u32 } { + const unit_size = entry.unit_size; + + var buffer: [*]align(8) u8 = undefined; + const alloc_status = self.boot_services.allocate_pool( + .loader_data, + unit_size, + &buffer, + ); + if (efi.types.is_error(alloc_status)) { + return ReadError.allocation_failed; + } + + const bytes_read = try self.read_unit(entry, buffer, unit_size); + return .{ .buffer = buffer, .size = bytes_read }; + } +}; + +fn case_insensitive_equal(a: []const u8, b: []const u8) bool { + if (a.len != b.len) { + return false; + } + for (a, b) |char_a, char_b| { + const upper_a = if (char_a >= 'a' and char_a <= 'z') char_a - 32 else char_a; + const upper_b = if (char_b >= 'a' and char_b <= 'z') char_b - 32 else char_b; + if (upper_a != upper_b) { + return false; + } + } + return true; +} diff --git a/hikari/fs/fat32/types.zig b/hikari/fs/fat32/types.zig new file mode 100644 index 0000000..24d92a1 --- /dev/null +++ b/hikari/fs/fat32/types.zig @@ -0,0 +1,214 @@ +//! Hikari FAT32 Types + +const constants = @import("constants.zig"); + +pub const BootSector = extern struct { + jump_boot: [3]u8, + oem_identity: [8]u8, + bytes_per_sector: u16 align(1), + sectors_per_cluster: u8, + reserved_sector_count: u16 align(1), + fat_count: u8, + origin_entry_count: u16 align(1), + total_sectors_16: u16 align(1), + media_type: u8, + fat_size_16: u16 align(1), + sectors_per_track: u16 align(1), + head_count: u16 align(1), + hidden_sector_count: u32 align(1), + total_sectors_32: u32 align(1), + fat_size_32: u32 align(1), + ext_flags: u16 align(1), + fs_version: u16 align(1), + origin_cluster: u32 align(1), + fs_info_sector: u16 align(1), + backup_boot_sector: u16 align(1), + reserved: [12]u8, + drive_number: u8, + reserved1: u8, + boot_signature: u8, + volume_id: u32 align(1), + volume_label: [11]u8, + fs_type: [8]u8, + boot_code: [420]u8, + signature: u16 align(1), + + pub fn is_valid(self: *const BootSector) bool { + if (self.signature != constants.boot_signature) { + return false; + } + if (self.bytes_per_sector < constants.sector_size_minimum or + self.bytes_per_sector > constants.sector_size_maximum) + { + return false; + } + if (self.sectors_per_cluster == 0) { + return false; + } + if (self.fat_count == 0) { + return false; + } + if (self.fat_size_32 == 0) { + return false; + } + return true; + } + + pub fn get_fat_start_sector(self: *const BootSector) u32 { + return self.reserved_sector_count; + } + + pub fn get_data_start_sector(self: *const BootSector) u32 { + return self.reserved_sector_count + (self.fat_count * self.fat_size_32); + } + + pub fn get_total_clusters(self: *const BootSector) u32 { + const data_sectors = self.total_sectors_32 - self.get_data_start_sector(); + return data_sectors / self.sectors_per_cluster; + } + + pub fn cluster_to_sector(self: *const BootSector, cluster: u32) u32 { + return self.get_data_start_sector() + ((cluster - 2) * self.sectors_per_cluster); + } +}; + +pub const FsInfo = extern struct { + signature_1: u32 align(1), + reserved_1: [480]u8, + signature_2: u32 align(1), + free_cluster_count: u32 align(1), + next_free_cluster: u32 align(1), + reserved_2: [12]u8, + signature_3: u32 align(1), + + pub fn is_valid(self: *const FsInfo) bool { + return self.signature_1 == constants.fs_info_signature_1 and + self.signature_2 == constants.fs_info_signature_2 and + self.signature_3 == constants.fs_info_signature_3; + } +}; + +pub const StackEntry = extern struct { + identity: [8]u8, + extension: [3]u8, + attributes: u8, + reserved_nt: u8, + creation_time_tenths: u8, + creation_time: u16 align(1), + creation_date: u16 align(1), + last_access_date: u16 align(1), + first_cluster_high: u16 align(1), + write_time: u16 align(1), + write_date: u16 align(1), + first_cluster_low: u16 align(1), + unit_size: u32 align(1), + + pub fn is_free(self: *const StackEntry) bool { + return self.identity[0] == constants.entry_free; + } + + pub fn is_end(self: *const StackEntry) bool { + return self.identity[0] == constants.entry_end; + } + + pub fn is_long_identity(self: *const StackEntry) bool { + return (self.attributes & constants.attribute_long_identity_mask) == constants.attribute_long_identity; + } + + pub fn is_stack(self: *const StackEntry) bool { + return (self.attributes & constants.attribute_stack) != 0; + } + + pub fn is_volume_id(self: *const StackEntry) bool { + return (self.attributes & constants.attribute_volume_id) != 0; + } + + pub fn get_first_cluster(self: *const StackEntry) u32 { + return (@as(u32, self.first_cluster_high) << 16) | self.first_cluster_low; + } + + pub fn get_short_identity(self: *const StackEntry, buffer: *[12]u8) usize { + var length: usize = 0; + + var identity_byte = self.identity[0]; + if (identity_byte == constants.entry_kanji_lead) { + identity_byte = constants.entry_free; + } + + var i: usize = 0; + while (i < 8 and self.identity[i] != ' ') : (i += 1) { + if (i == 0) { + buffer[length] = identity_byte; + } else { + buffer[length] = self.identity[i]; + } + length += 1; + } + + if (self.extension[0] != ' ') { + buffer[length] = '.'; + length += 1; + + var j: usize = 0; + while (j < 3 and self.extension[j] != ' ') : (j += 1) { + buffer[length] = self.extension[j]; + length += 1; + } + } + + return length; + } +}; + +pub const LongIdentityEntry = extern struct { + sequence_number: u8, + identity_1: [10]u8, + attributes: u8, + entry_type: u8, + checksum: u8, + identity_2: [12]u8, + first_cluster_low: u16 align(1), + identity_3: [4]u8, + + pub fn is_last(self: *const LongIdentityEntry) bool { + return (self.sequence_number & constants.long_identity_last_entry) != 0; + } + + pub fn get_sequence(self: *const LongIdentityEntry) u8 { + return self.sequence_number & constants.long_identity_sequence_mask; + } + + pub fn extract_chars(self: *const LongIdentityEntry, buffer: *[13]u16) void { + var index: usize = 0; + + var i: usize = 0; + while (i < 10) : (i += 2) { + buffer[index] = @as(u16, self.identity_1[i]) | (@as(u16, self.identity_1[i + 1]) << 8); + index += 1; + } + + i = 0; + while (i < 12) : (i += 2) { + buffer[index] = @as(u16, self.identity_2[i]) | (@as(u16, self.identity_2[i + 1]) << 8); + index += 1; + } + + i = 0; + while (i < 4) : (i += 2) { + buffer[index] = @as(u16, self.identity_3[i]) | (@as(u16, self.identity_3[i + 1]) << 8); + index += 1; + } + } +}; + +pub const TimeFormat = packed struct(u16) { + second_div_2: u5, + minute: u6, + hour: u5, +}; + +pub const DateFormat = packed struct(u16) { + day: u5, + month: u4, + year_from_1980: u7, +}; diff --git a/hikari/fs/fs.zig b/hikari/fs/fs.zig new file mode 100644 index 0000000..aef6e6f --- /dev/null +++ b/hikari/fs/fs.zig @@ -0,0 +1,4 @@ +//! Hikari UnitSystem Subsystem + +pub const fat32 = @import("fat32/fat32.zig"); +pub const afs = @import("afs/afs.zig"); diff --git a/hikari/hikari.zig b/hikari/hikari.zig new file mode 100644 index 0000000..c1c03ea --- /dev/null +++ b/hikari/hikari.zig @@ -0,0 +1,322 @@ +//! Hikari UEFI Bootloader for AkibaOS +//! +//! Hikari loads the Mirai kernel from the AFS partition and +//! transfers control with boot parameters. + +const efi = @import("efi/efi.zig"); +const disk = @import("disk/disk.zig"); +const fs = @import("fs/fs.zig"); +const loader = @import("loader/loader.zig"); +const display = @import("display/display.zig"); +const menu = @import("menu/menu.zig"); +const paging = @import("paging/paging.zig"); +const boot = @import("boot/boot.zig"); +const asm_ops = @import("asm/asm.zig"); + +const kernel_location = "/system/akiba/mirai.kernel"; +const font_location = "/system/akiba/fonts/akiba.psf"; + +pub fn hikari(image_handle: efi.types.Handle, system_table: *efi.services.SystemTable) callconv(.C) efi.types.Status { + const boot_services = system_table.boot_services; + const console = system_table.console_output; + + _ = console.clear_screen(console); + print(console, "Hikari Bootloader\r\n"); + print(console, "=================\r\n\r\n"); + + print(console, "Initializing graphics...\r\n"); + const gop = get_graphics_output(boot_services) orelse { + print(console, "ERROR: Failed to get graphics output\r\n"); + return efi.constants.status.unsupported; + }; + + const framebuffer = display.Framebuffer.initialize(gop); + _ = framebuffer; + + print(console, "Locating AFS partition...\r\n"); + const afs_partition = find_afs_partition(boot_services) orelse { + print(console, "ERROR: AFS partition not found\r\n"); + return efi.constants.status.not_found; + }; + + print(console, "Initializing AFS...\r\n"); + var afs_reader = fs.afs.Reader.initialize( + afs_partition.block_io, + boot_services, + afs_partition.start_lba, + ) catch { + print(console, "ERROR: Failed to initialize AFS\r\n"); + return efi.constants.status.device_error; + }; + + print(console, "Loading kernel: "); + print(console, kernel_location); + print(console, "\r\n"); + + const kernel_unit = afs_reader.open_location(kernel_location) catch { + print(console, "ERROR: Kernel not found\r\n"); + return efi.constants.status.not_found; + }; + + const kernel_data = afs_reader.read_unit_to_allocated(&kernel_unit) catch { + print(console, "ERROR: Failed to read kernel\r\n"); + return efi.constants.status.device_error; + }; + + print(console, "Validating ELF...\r\n"); + if (!loader.elf.validate_elf(kernel_data.buffer, kernel_data.size)) { + print(console, "ERROR: Invalid ELF format\r\n"); + return efi.constants.status.invalid_parameter; + } + + print(console, "Loading kernel into memory...\r\n"); + var elf_loader = loader.elf.Loader.initialize(boot_services); + const loaded_image = elf_loader.load(kernel_data.buffer, kernel_data.size) catch { + print(console, "ERROR: Failed to load kernel\r\n"); + return efi.constants.status.load_error; + }; + + print(console, "Setting up page tables...\r\n"); + var page_setup = paging.PageTableSetup.initialize(boot_services) catch { + print(console, "ERROR: Failed to setup page tables\r\n"); + return efi.constants.status.out_of_resources; + }; + + page_setup.map_identity(0, 4 * 1024 * 1024 * 1024) catch {}; + page_setup.map_kernel(loaded_image.base_address, loaded_image.total_size()) catch {}; + page_setup.map_physmap(16 * 1024 * 1024 * 1024) catch {}; + + print(console, "Allocating kernel stack...\r\n"); + const stack_size: u64 = 64 * 1024; + const stack_pages = stack_size / 4096; + var stack_base: efi.types.PhysicalAddress = 0; + const stack_status = boot_services.allocate_pages(.any_pages, .loader_data, stack_pages, &stack_base); + if (efi.types.is_error(stack_status)) { + print(console, "ERROR: Failed to allocate stack\r\n"); + return efi.constants.status.out_of_resources; + } + const stack_top = stack_base + stack_size; + + print(console, "Preparing boot parameters...\r\n"); + var params_addr: efi.types.PhysicalAddress = 0; + const params_status = boot_services.allocate_pages(.any_pages, .loader_data, 1, ¶ms_addr); + if (efi.types.is_error(params_status)) { + print(console, "ERROR: Failed to allocate boot params\r\n"); + return efi.constants.status.out_of_resources; + } + + const boot_params: *boot.BootParams = @ptrFromInt(params_addr); + boot_params.* = boot.BootParams.initialize(); + + boot_params.framebuffer = boot.FramebufferInfo{ + .base = gop.mode.framebuffer_base, + .size = gop.mode.framebuffer_size, + .width = gop.mode.info.horizontal_resolution, + .height = gop.mode.info.vertical_resolution, + .stride = gop.mode.info.pixels_per_scan_line, + .pixel_format = switch (gop.mode.info.pixel_format) { + .rgb => .rgb, + .bgr => .bgr, + else => .unknown, + }, + .red_mask_size = 8, + .red_mask_shift = 16, + .green_mask_size = 8, + .green_mask_shift = 8, + .blue_mask_size = 8, + .blue_mask_shift = 0, + .reserved = .{ 0, 0 }, + }; + + boot_params.kernel = boot.KernelInfo{ + .physical_base = loaded_image.base_address, + .virtual_base = paging.constants.kernel_base, + .size = loaded_image.total_size(), + .entry_point = loaded_image.entry_point, + .pml4_address = page_setup.get_pml4_address(), + .physmap_base = paging.constants.physmap_base, + .physmap_size = paging.constants.physmap_size, + .stack_top = stack_top, + .stack_size = stack_size, + }; + + boot_params.acpi = find_acpi(system_table); + + print(console, "Getting memory map...\r\n"); + var memory_map_size: usize = 0; + var map_key: usize = 0; + var descriptor_size: usize = 0; + var descriptor_version: u32 = 0; + var memory_map: [*]efi.types.memory.MemoryDescriptor = undefined; + + _ = boot_services.get_memory_map( + &memory_map_size, + memory_map, + &map_key, + &descriptor_size, + &descriptor_version, + ); + + memory_map_size += 2 * descriptor_size; + var map_buffer: [*]align(8) u8 = undefined; + _ = boot_services.allocate_pool(.loader_data, memory_map_size, &map_buffer); + memory_map = @ptrCast(@alignCast(map_buffer)); + + const map_status = boot_services.get_memory_map( + &memory_map_size, + memory_map, + &map_key, + &descriptor_size, + &descriptor_version, + ); + + if (efi.types.is_error(map_status)) { + print(console, "ERROR: Failed to get memory map\r\n"); + return efi.constants.status.device_error; + } + + boot_params.memory_map = boot.MemoryMapInfo{ + .entries = @intFromPtr(memory_map), + .entry_count = @truncate(memory_map_size / descriptor_size), + .entry_size = @truncate(descriptor_size), + .descriptor_version = descriptor_version, + .reserved = 0, + }; + + print(console, "Exiting boot services...\r\n"); + const exit_status = boot_services.exit_boot_services(image_handle, map_key); + if (efi.types.is_error(exit_status)) { + _ = boot_services.get_memory_map( + &memory_map_size, + memory_map, + &map_key, + &descriptor_size, + &descriptor_version, + ); + _ = boot_services.exit_boot_services(image_handle, map_key); + } + + asm_ops.jump_to_kernel( + loaded_image.entry_point, + stack_top, + params_addr, + page_setup.get_pml4_address(), + ); +} + +fn print(console: *efi.protocols.SimpleTextOutputProtocol, msg: []const u8) void { + for (msg) |c| { + var buf: [2]u16 = .{ c, 0 }; + _ = console.output_string(console, &buf); + } +} + +fn get_graphics_output(boot_services: *efi.services.BootServices) ?*efi.protocols.GraphicsOutputProtocol { + var gop: ?*anyopaque = null; + const status = boot_services.locate_protocol( + &efi.constants.guids.graphics_output_protocol, + null, + &gop, + ); + if (efi.types.is_error(status)) { + return null; + } + return @ptrCast(@alignCast(gop)); +} + +const PartitionInfo = struct { + block_io: *efi.protocols.BlockIoProtocol, + start_lba: u64, +}; + +fn find_afs_partition(boot_services: *efi.services.BootServices) ?PartitionInfo { + var handle_count: usize = 0; + var handles: [*]efi.types.Handle = undefined; + + const status = boot_services.locate_handle_buffer( + .by_protocol, + &efi.constants.guids.block_io_protocol, + null, + &handle_count, + &handles, + ); + + if (efi.types.is_error(status)) { + return null; + } + + var i: usize = 0; + while (i < handle_count) : (i += 1) { + var block_io: ?*anyopaque = null; + const bio_status = boot_services.handle_protocol( + handles[i], + &efi.constants.guids.block_io_protocol, + &block_io, + ); + + if (efi.types.is_error(bio_status)) { + continue; + } + + const bio: *efi.protocols.BlockIoProtocol = @ptrCast(@alignCast(block_io)); + + if (bio.media.logical_partition) { + continue; + } + + var gpt_parser = disk.gpt.Parser.initialize(bio, boot_services) catch { + continue; + }; + + const afs_part = gpt_parser.find_partition_by_type(efi.constants.guids.gpt_partition_type_akiba_afs); + if (afs_part) |part| { + return PartitionInfo{ + .block_io = bio, + .start_lba = part.starting_lba, + }; + } + } + + return null; +} + +fn find_acpi(system_table: *efi.services.SystemTable) boot.AcpiInfo { + var i: usize = 0; + while (i < system_table.number_of_table_entries) : (i += 1) { + const entry = &system_table.configuration_table[i]; + + if (entry.vendor_guid.equals(efi.constants.guids.acpi_20_table)) { + return boot.AcpiInfo{ + .rsdp_address = @intFromPtr(entry.vendor_table), + .rsdp_version = 2, + .reserved = 0, + }; + } + } + + i = 0; + while (i < system_table.number_of_table_entries) : (i += 1) { + const entry = &system_table.configuration_table[i]; + + if (entry.vendor_guid.equals(efi.constants.guids.acpi_10_table)) { + return boot.AcpiInfo{ + .rsdp_address = @intFromPtr(entry.vendor_table), + .rsdp_version = 1, + .reserved = 0, + }; + } + } + + return boot.AcpiInfo{ + .rsdp_address = 0, + .rsdp_version = 0, + .reserved = 0, + }; +} + +pub fn panic(msg: []const u8, stack_trace: ?*@import("std").builtin.StackTrace, ret_addr: ?usize) noreturn { + _ = msg; + _ = stack_trace; + _ = ret_addr; + asm_ops.halt(); +} diff --git a/hikari/loader/elf/constants.zig b/hikari/loader/elf/constants.zig new file mode 100644 index 0000000..1a91701 --- /dev/null +++ b/hikari/loader/elf/constants.zig @@ -0,0 +1,85 @@ +//! Hikari ELF Constants + +pub const magic: [4]u8 = .{ 0x7F, 'E', 'L', 'F' }; + +pub const class_none: u8 = 0; +pub const class_32: u8 = 1; +pub const class_64: u8 = 2; + +pub const data_none: u8 = 0; +pub const data_little_endian: u8 = 1; +pub const data_big_endian: u8 = 2; + +pub const version_none: u8 = 0; +pub const version_current: u8 = 1; + +pub const osabi_none: u8 = 0; +pub const osabi_sysv: u8 = 0; +pub const osabi_linux: u8 = 3; +pub const osabi_freebsd: u8 = 9; +pub const osabi_standalone: u8 = 255; + +pub const type_none: u16 = 0; +pub const type_relocatable: u16 = 1; +pub const type_executable: u16 = 2; +pub const type_shared: u16 = 3; +pub const type_core: u16 = 4; + +pub const machine_none: u16 = 0; +pub const machine_386: u16 = 3; +pub const machine_x86_64: u16 = 62; +pub const machine_aarch64: u16 = 183; +pub const machine_riscv: u16 = 243; + +pub const segment_null: u32 = 0; +pub const segment_load: u32 = 1; +pub const segment_dynamic: u32 = 2; +pub const segment_interp: u32 = 3; +pub const segment_note: u32 = 4; +pub const segment_shlib: u32 = 5; +pub const segment_phdr: u32 = 6; +pub const segment_tls: u32 = 7; +pub const segment_gnu_eh_frame: u32 = 0x6474E550; +pub const segment_gnu_stack: u32 = 0x6474E551; +pub const segment_gnu_relro: u32 = 0x6474E552; + +pub const segment_flag_execute: u32 = 0x1; +pub const segment_flag_write: u32 = 0x2; +pub const segment_flag_read: u32 = 0x4; + +pub const section_null: u32 = 0; +pub const section_progbits: u32 = 1; +pub const section_symtab: u32 = 2; +pub const section_strtab: u32 = 3; +pub const section_rela: u32 = 4; +pub const section_hash: u32 = 5; +pub const section_dynamic: u32 = 6; +pub const section_note: u32 = 7; +pub const section_nobits: u32 = 8; +pub const section_rel: u32 = 9; +pub const section_shlib: u32 = 10; +pub const section_dynsym: u32 = 11; +pub const section_init_array: u32 = 14; +pub const section_fini_array: u32 = 15; +pub const section_preinit_array: u32 = 16; +pub const section_group: u32 = 17; +pub const section_symtab_shndx: u32 = 18; + +pub const section_flag_write: u64 = 0x1; +pub const section_flag_alloc: u64 = 0x2; +pub const section_flag_execinstr: u64 = 0x4; +pub const section_flag_merge: u64 = 0x10; +pub const section_flag_strings: u64 = 0x20; +pub const section_flag_info_link: u64 = 0x40; +pub const section_flag_link_order: u64 = 0x80; +pub const section_flag_os_nonconforming: u64 = 0x100; +pub const section_flag_group: u64 = 0x200; +pub const section_flag_tls: u64 = 0x400; + +pub const section_index_undefined: u16 = 0; +pub const section_index_abs: u16 = 0xFFF1; +pub const section_index_common: u16 = 0xFFF2; + +pub const elf64_header_size: usize = 64; +pub const elf64_program_header_size: usize = 56; +pub const elf64_section_header_size: usize = 64; diff --git a/hikari/loader/elf/elf.zig b/hikari/loader/elf/elf.zig new file mode 100644 index 0000000..83eab98 --- /dev/null +++ b/hikari/loader/elf/elf.zig @@ -0,0 +1,17 @@ +//! Hikari ELF + +pub const constants = @import("constants.zig"); +pub const types = @import("types.zig"); +pub const loader = @import("loader.zig"); + +pub const Elf64Header = types.Elf64Header; +pub const Elf64ProgramHeader = types.Elf64ProgramHeader; +pub const Elf64SectionHeader = types.Elf64SectionHeader; +pub const Elf64Symbol = types.Elf64Symbol; +pub const Elf64Rela = types.Elf64Rela; +pub const LoadedSegment = types.LoadedSegment; +pub const LoadedImage = types.LoadedImage; + +pub const Loader = loader.Loader; +pub const LoadError = loader.LoadError; +pub const validate_elf = loader.validate_elf; diff --git a/hikari/loader/elf/loader.zig b/hikari/loader/elf/loader.zig new file mode 100644 index 0000000..4188e22 --- /dev/null +++ b/hikari/loader/elf/loader.zig @@ -0,0 +1,201 @@ +//! Hikari ELF Loader + +const efi = @import("../../efi/efi.zig"); +const constants = @import("constants.zig"); +const types = @import("types.zig"); + +pub const LoadError = error{ + invalid_elf_header, + unsupported_architecture, + unsupported_elf_type, + no_loadable_segments, + too_many_segments, + allocation_failed, + segment_overlap, +}; + +pub const Loader = struct { + boot_services: *efi.services.BootServices, + + pub fn initialize(boot_services: *efi.services.BootServices) Loader { + return Loader{ + .boot_services = boot_services, + }; + } + + pub fn load(self: *Loader, unit_data: [*]const u8, unit_size: u64) LoadError!types.LoadedImage { + if (unit_size < constants.elf64_header_size) { + return LoadError.invalid_elf_header; + } + + const header: *const types.Elf64Header = @ptrCast(@alignCast(unit_data)); + + if (!header.is_valid()) { + return LoadError.invalid_elf_header; + } + + if (!header.is_x86_64()) { + return LoadError.unsupported_architecture; + } + + if (!header.is_executable()) { + return LoadError.unsupported_elf_type; + } + + const program_headers = header.get_program_headers(unit_data); + + var load_base: u64 = 0xFFFFFFFFFFFFFFFF; + var load_end: u64 = 0; + var loadable_count: usize = 0; + + for (program_headers) |*phdr| { + if (!phdr.is_loadable()) { + continue; + } + + loadable_count += 1; + + if (phdr.virtual_address < load_base) { + load_base = phdr.virtual_address; + } + + const segment_end = phdr.virtual_address + phdr.memory_size; + if (segment_end > load_end) { + load_end = segment_end; + } + } + + if (loadable_count == 0) { + return LoadError.no_loadable_segments; + } + + if (loadable_count > 16) { + return LoadError.too_many_segments; + } + + const total_size = load_end - load_base; + const pages_needed = (total_size + 4095) / 4096; + + var physical_base: efi.types.PhysicalAddress = load_base; + const alloc_status = self.boot_services.allocate_pages( + .address, + .loader_data, + pages_needed, + &physical_base, + ); + + if (efi.types.is_error(alloc_status)) { + physical_base = 0; + const fallback_status = self.boot_services.allocate_pages( + .any_pages, + .loader_data, + pages_needed, + &physical_base, + ); + + if (efi.types.is_error(fallback_status)) { + return LoadError.allocation_failed; + } + } + + const dest_base: [*]u8 = @ptrFromInt(physical_base); + + var i: u64 = 0; + while (i < total_size) : (i += 1) { + dest_base[i] = 0; + } + + var image = types.LoadedImage{ + .entry_point = header.entry, + .base_address = physical_base, + .end_address = physical_base + total_size, + .segments = undefined, + .segment_count = 0, + }; + + for (program_headers) |*phdr| { + if (!phdr.is_loadable()) { + continue; + } + + const segment_offset = phdr.virtual_address - load_base; + const dest: [*]u8 = dest_base + segment_offset; + const src = unit_data + phdr.offset; + + var j: u64 = 0; + while (j < phdr.unit_size) : (j += 1) { + dest[j] = src[j]; + } + + image.segments[image.segment_count] = types.LoadedSegment{ + .virtual_address = phdr.virtual_address, + .physical_address = physical_base + segment_offset, + .memory_size = phdr.memory_size, + .flags = phdr.flags, + }; + image.segment_count += 1; + } + + if (physical_base != load_base) { + image.entry_point = (header.entry - load_base) + physical_base; + } + + return image; + } + + pub fn get_memory_requirements(unit_data: [*]const u8) LoadError!struct { base: u64, size: u64 } { + const header: *const types.Elf64Header = @ptrCast(@alignCast(unit_data)); + + if (!header.is_valid()) { + return LoadError.invalid_elf_header; + } + + const program_headers = header.get_program_headers(unit_data); + + var load_base: u64 = 0xFFFFFFFFFFFFFFFF; + var load_end: u64 = 0; + + for (program_headers) |*phdr| { + if (!phdr.is_loadable()) { + continue; + } + + if (phdr.virtual_address < load_base) { + load_base = phdr.virtual_address; + } + + const segment_end = phdr.virtual_address + phdr.memory_size; + if (segment_end > load_end) { + load_end = segment_end; + } + } + + if (load_base == 0xFFFFFFFFFFFFFFFF) { + return LoadError.no_loadable_segments; + } + + return .{ + .base = load_base, + .size = load_end - load_base, + }; + } + + pub fn get_entry_point(unit_data: [*]const u8) LoadError!u64 { + const header: *const types.Elf64Header = @ptrCast(@alignCast(unit_data)); + + if (!header.is_valid()) { + return LoadError.invalid_elf_header; + } + + return header.entry; + } +}; + +pub fn validate_elf(data: [*]const u8, size: u64) bool { + if (size < constants.elf64_header_size) { + return false; + } + + const header: *const types.Elf64Header = @ptrCast(@alignCast(data)); + return header.is_valid() and header.is_x86_64(); +} diff --git a/hikari/loader/elf/types.zig b/hikari/loader/elf/types.zig new file mode 100644 index 0000000..1d4a3d4 --- /dev/null +++ b/hikari/loader/elf/types.zig @@ -0,0 +1,182 @@ +//! Hikari ELF Types + +const constants = @import("constants.zig"); + +pub const Elf64Header = extern struct { + magic: [4]u8, + class: u8, + data: u8, + version: u8, + osabi: u8, + abi_version: u8, + padding: [7]u8, + elf_type: u16 align(1), + machine: u16 align(1), + elf_version: u32 align(1), + entry: u64 align(1), + program_header_offset: u64 align(1), + section_header_offset: u64 align(1), + flags: u32 align(1), + header_size: u16 align(1), + program_header_entry_size: u16 align(1), + program_header_count: u16 align(1), + section_header_entry_size: u16 align(1), + section_header_count: u16 align(1), + section_identity_index: u16 align(1), + + pub fn is_valid(self: *const Elf64Header) bool { + if (self.magic[0] != constants.magic[0] or + self.magic[1] != constants.magic[1] or + self.magic[2] != constants.magic[2] or + self.magic[3] != constants.magic[3]) + { + return false; + } + if (self.class != constants.class_64) { + return false; + } + if (self.data != constants.data_little_endian) { + return false; + } + if (self.version != constants.version_current) { + return false; + } + return true; + } + + pub fn is_executable(self: *const Elf64Header) bool { + return self.elf_type == constants.type_executable; + } + + pub fn is_x86_64(self: *const Elf64Header) bool { + return self.machine == constants.machine_x86_64; + } + + pub fn get_program_headers(self: *const Elf64Header, base: [*]const u8) []const Elf64ProgramHeader { + const phdr_ptr: [*]const Elf64ProgramHeader = @ptrCast(@alignCast(base + self.program_header_offset)); + return phdr_ptr[0..self.program_header_count]; + } + + pub fn get_section_headers(self: *const Elf64Header, base: [*]const u8) []const Elf64SectionHeader { + const shdr_ptr: [*]const Elf64SectionHeader = @ptrCast(@alignCast(base + self.section_header_offset)); + return shdr_ptr[0..self.section_header_count]; + } +}; + +pub const Elf64ProgramHeader = extern struct { + segment_type: u32 align(1), + flags: u32 align(1), + offset: u64 align(1), + virtual_address: u64 align(1), + physical_address: u64 align(1), + unit_size: u64 align(1), + memory_size: u64 align(1), + alignment: u64 align(1), + + pub fn is_loadable(self: *const Elf64ProgramHeader) bool { + return self.segment_type == constants.segment_load; + } + + pub fn is_executable(self: *const Elf64ProgramHeader) bool { + return (self.flags & constants.segment_flag_execute) != 0; + } + + pub fn is_writable(self: *const Elf64ProgramHeader) bool { + return (self.flags & constants.segment_flag_write) != 0; + } + + pub fn is_readable(self: *const Elf64ProgramHeader) bool { + return (self.flags & constants.segment_flag_read) != 0; + } + + pub fn get_data(self: *const Elf64ProgramHeader, base: [*]const u8) []const u8 { + return (base + self.offset)[0..self.unit_size]; + } +}; + +pub const Elf64SectionHeader = extern struct { + identity_offset: u32 align(1), + section_type: u32 align(1), + flags: u64 align(1), + address: u64 align(1), + offset: u64 align(1), + size: u64 align(1), + link: u32 align(1), + info: u32 align(1), + address_alignment: u64 align(1), + entry_size: u64 align(1), + + pub fn is_allocatable(self: *const Elf64SectionHeader) bool { + return (self.flags & constants.section_flag_alloc) != 0; + } + + pub fn is_writable(self: *const Elf64SectionHeader) bool { + return (self.flags & constants.section_flag_write) != 0; + } + + pub fn is_executable(self: *const Elf64SectionHeader) bool { + return (self.flags & constants.section_flag_execinstr) != 0; + } + + pub fn is_nobits(self: *const Elf64SectionHeader) bool { + return self.section_type == constants.section_nobits; + } + + pub fn get_data(self: *const Elf64SectionHeader, base: [*]const u8) []const u8 { + return (base + self.offset)[0..self.size]; + } +}; + +pub const Elf64Symbol = extern struct { + identity_offset: u32 align(1), + info: u8, + other: u8, + section_index: u16 align(1), + value: u64 align(1), + size: u64 align(1), + + pub fn get_binding(self: *const Elf64Symbol) u8 { + return self.info >> 4; + } + + pub fn get_type(self: *const Elf64Symbol) u8 { + return self.info & 0x0F; + } + + pub fn get_visibility(self: *const Elf64Symbol) u8 { + return self.other & 0x03; + } +}; + +pub const Elf64Rela = extern struct { + offset: u64 align(1), + info: u64 align(1), + addend: i64 align(1), + + pub fn get_symbol(self: *const Elf64Rela) u32 { + return @truncate(self.info >> 32); + } + + pub fn get_type(self: *const Elf64Rela) u32 { + return @truncate(self.info & 0xFFFFFFFF); + } +}; + +pub const LoadedSegment = struct { + virtual_address: u64, + physical_address: u64, + memory_size: u64, + flags: u32, +}; + +pub const LoadedImage = struct { + entry_point: u64, + base_address: u64, + end_address: u64, + segments: [16]LoadedSegment, + segment_count: usize, + + pub fn total_size(self: *const LoadedImage) u64 { + return self.end_address - self.base_address; + } +}; diff --git a/hikari/loader/loader.zig b/hikari/loader/loader.zig new file mode 100644 index 0000000..2302cd5 --- /dev/null +++ b/hikari/loader/loader.zig @@ -0,0 +1,3 @@ +//! Hikari Loader Subsystem + +pub const elf = @import("elf/elf.zig"); diff --git a/hikari/menu/input.zig b/hikari/menu/input.zig new file mode 100644 index 0000000..34fa2a9 --- /dev/null +++ b/hikari/menu/input.zig @@ -0,0 +1,82 @@ +//! Hikari Menu Input + +const efi = @import("../efi/efi.zig"); + +pub const InputAction = enum { + none, + up, + down, + select, + cancel, + page_up, + page_down, + home, + end, +}; + +pub const Input = struct { + system_table: *efi.services.SystemTable, + + pub fn initialize(system_table: *efi.services.SystemTable) Input { + return Input{ + .system_table = system_table, + }; + } + + pub fn poll(self: *Input) InputAction { + var key: efi.types.input.InputKey = undefined; + const status = self.system_table.console_input.read_key_stroke( + self.system_table.console_input, + &key, + ); + + if (efi.types.is_error(status)) { + return .none; + } + + if (key.scan_code != 0) { + return switch (key.scan_code) { + efi.constants.keyboard.scan_up => .up, + efi.constants.keyboard.scan_down => .down, + efi.constants.keyboard.scan_escape => .cancel, + efi.constants.keyboard.scan_page_up => .page_up, + efi.constants.keyboard.scan_page_down => .page_down, + efi.constants.keyboard.scan_home => .home, + efi.constants.keyboard.scan_end => .end, + else => .none, + }; + } + + if (key.unicode_char != 0) { + return switch (key.unicode_char) { + '\r', ' ' => .select, + 'j', 'J' => .down, + 'k', 'K' => .up, + 'q', 'Q' => .cancel, + else => .none, + }; + } + + return .none; + } + + pub fn wait_for_key(self: *Input) void { + var index: usize = 0; + const events = [_]efi.types.Event{self.system_table.console_input.wait_for_key}; + _ = self.system_table.boot_services.wait_for_event(1, &events, &index); + } + + pub fn wait_for_action(self: *Input) InputAction { + while (true) { + const action = self.poll(); + if (action != .none) { + return action; + } + self.wait_for_key(); + } + } + + pub fn clear_input_buffer(self: *Input) void { + while (self.poll() != .none) {} + } +}; diff --git a/hikari/menu/menu.zig b/hikari/menu/menu.zig new file mode 100644 index 0000000..14ca544 --- /dev/null +++ b/hikari/menu/menu.zig @@ -0,0 +1,125 @@ +//! Hikari Menu Subsystem + +pub const theme = @import("theme.zig"); +pub const input = @import("input.zig"); +pub const renderer = @import("renderer.zig"); + +pub const Theme = theme.Theme; +pub const Input = input.Input; +pub const InputAction = input.InputAction; +pub const Renderer = renderer.Renderer; +pub const MenuItem = renderer.MenuItem; + +pub const BootMenu = struct { + renderer: *Renderer, + input: *Input, + items: []const MenuItem, + selected: usize, + scroll_offset: usize, + title: []const u8, + + pub fn initialize( + menu_renderer: *Renderer, + menu_input: *Input, + items: []const MenuItem, + title: []const u8, + ) BootMenu { + return BootMenu{ + .renderer = menu_renderer, + .input = menu_input, + .items = items, + .selected = 0, + .scroll_offset = 0, + .title = title, + }; + } + + pub fn run(self: *BootMenu) ?usize { + self.input.clear_input_buffer(); + self.draw(); + + while (true) { + const action = self.input.wait_for_action(); + + switch (action) { + .up => self.move_up(), + .down => self.move_down(), + .page_up => self.page_up(), + .page_down => self.page_down(), + .home => self.go_home(), + .end => self.go_end(), + .select => { + if (self.items[self.selected].enabled) { + return self.selected; + } + }, + .cancel => return null, + .none => {}, + } + + self.draw(); + } + } + + fn draw(self: *BootMenu) void { + self.renderer.draw_background(); + self.renderer.draw_title(self.title); + self.renderer.draw_menu_items(self.items, self.selected, self.scroll_offset); + self.renderer.draw_description(self.items[self.selected].description); + self.renderer.draw_scrollbar(self.items.len, self.renderer.visible_items, self.scroll_offset); + self.renderer.draw_footer(); + } + + fn move_up(self: *BootMenu) void { + if (self.selected > 0) { + self.selected -= 1; + if (self.selected < self.scroll_offset) { + self.scroll_offset = self.selected; + } + } + } + + fn move_down(self: *BootMenu) void { + if (self.selected < self.items.len - 1) { + self.selected += 1; + if (self.selected >= self.scroll_offset + self.renderer.visible_items) { + self.scroll_offset = self.selected - self.renderer.visible_items + 1; + } + } + } + + fn page_up(self: *BootMenu) void { + if (self.selected >= self.renderer.visible_items) { + self.selected -= self.renderer.visible_items; + } else { + self.selected = 0; + } + if (self.selected < self.scroll_offset) { + self.scroll_offset = self.selected; + } + } + + fn page_down(self: *BootMenu) void { + const remaining = self.items.len - 1 - self.selected; + if (remaining >= self.renderer.visible_items) { + self.selected += self.renderer.visible_items; + } else { + self.selected = self.items.len - 1; + } + if (self.selected >= self.scroll_offset + self.renderer.visible_items) { + self.scroll_offset = self.selected - self.renderer.visible_items + 1; + } + } + + fn go_home(self: *BootMenu) void { + self.selected = 0; + self.scroll_offset = 0; + } + + fn go_end(self: *BootMenu) void { + self.selected = self.items.len - 1; + if (self.items.len > self.renderer.visible_items) { + self.scroll_offset = self.items.len - self.renderer.visible_items; + } + } +}; diff --git a/hikari/menu/renderer.zig b/hikari/menu/renderer.zig new file mode 100644 index 0000000..9cbf3ae --- /dev/null +++ b/hikari/menu/renderer.zig @@ -0,0 +1,199 @@ +//! Hikari Menu Renderer + +const display = @import("../display/display.zig"); +const theme_mod = @import("theme.zig"); + +pub const MenuItem = struct { + label: []const u8, + description: []const u8, + enabled: bool, +}; + +pub const Renderer = struct { + fb: *display.Framebuffer, + text: *display.TextRenderer, + theme: theme_mod.Theme, + menu_x: u32, + menu_y: u32, + menu_width: u32, + visible_items: u32, + + pub fn initialize( + fb: *display.Framebuffer, + text_renderer: *display.TextRenderer, + theme: theme_mod.Theme, + ) Renderer { + const char_width = text_renderer.font.width; + const char_height = text_renderer.font.height; + + const menu_width = 60; + const menu_x = (text_renderer.columns - menu_width) / 2; + const menu_y: u32 = 4; + const visible_items = text_renderer.rows - 10; + + _ = char_width; + _ = char_height; + + return Renderer{ + .fb = fb, + .text = text_renderer, + .theme = theme, + .menu_x = menu_x, + .menu_y = menu_y, + .menu_width = menu_width, + .visible_items = visible_items, + }; + } + + pub fn draw_background(self: *Renderer) void { + self.fb.clear(self.theme.background); + } + + pub fn draw_title(self: *Renderer, title: []const u8) void { + self.text.set_colors(self.theme.title_fg, self.theme.background); + + const title_x = (self.text.columns - @as(u32, @intCast(title.len))) / 2; + self.text.set_cursor(title_x, 1); + self.text.print(title); + + self.text.set_cursor(self.menu_x, 2); + var i: u32 = 0; + while (i < self.menu_width) : (i += 1) { + self.text.put_char('-'); + } + } + + pub fn draw_menu_items( + self: *Renderer, + items: []const MenuItem, + selected: usize, + scroll_offset: usize, + ) void { + var row: u32 = 0; + while (row < self.visible_items and (scroll_offset + row) < items.len) : (row += 1) { + const item_index = scroll_offset + row; + const item = items[item_index]; + const is_selected = item_index == selected; + + self.draw_menu_item(item, row, is_selected); + } + } + + fn draw_menu_item(self: *Renderer, item: MenuItem, row: u32, selected: bool) void { + const y = self.menu_y + row; + + if (selected) { + self.text.set_colors(self.theme.highlight_fg, self.theme.highlight_bg); + } else if (!item.enabled) { + self.text.set_colors(self.theme.border, self.theme.background); + } else { + self.text.set_colors(self.theme.foreground, self.theme.background); + } + + self.text.set_cursor(self.menu_x, y); + + if (selected) { + self.text.print("> "); + } else { + self.text.print(" "); + } + + self.text.print(item.label); + + var i: u32 = @intCast(item.label.len + 2); + while (i < self.menu_width) : (i += 1) { + self.text.put_char(' '); + } + } + + pub fn draw_description(self: *Renderer, description: []const u8) void { + const desc_y = self.text.rows - 3; + + self.text.set_colors(self.theme.foreground, self.theme.background); + self.text.set_cursor(self.menu_x, desc_y); + + var i: u32 = 0; + while (i < self.menu_width) : (i += 1) { + self.text.put_char(' '); + } + + self.text.set_cursor(self.menu_x, desc_y); + self.text.print(description); + } + + pub fn draw_footer(self: *Renderer) void { + const footer_y = self.text.rows - 1; + + self.text.set_colors(self.theme.border, self.theme.background); + self.text.set_cursor(self.menu_x, footer_y); + self.text.print("Up/Down: Navigate Enter: Select Esc: Cancel"); + } + + pub fn draw_scrollbar(self: *Renderer, total_items: usize, visible: usize, offset: usize) void { + if (total_items <= visible) { + return; + } + + const scrollbar_x = self.menu_x + self.menu_width + 1; + const scrollbar_height = self.visible_items; + + const thumb_height = (visible * scrollbar_height) / total_items; + const thumb_height_clamped = if (thumb_height < 1) 1 else @as(u32, @intCast(thumb_height)); + + const thumb_offset = (offset * scrollbar_height) / total_items; + + var i: u32 = 0; + while (i < scrollbar_height) : (i += 1) { + self.text.set_cursor(scrollbar_x, self.menu_y + i); + + if (i >= thumb_offset and i < thumb_offset + thumb_height_clamped) { + self.text.set_colors(self.theme.highlight_bg, self.theme.background); + self.text.put_char('#'); + } else { + self.text.set_colors(self.theme.border, self.theme.background); + self.text.put_char('|'); + } + } + } + + pub fn draw_progress(self: *Renderer, message: []const u8, percent: u32) void { + const progress_y = self.text.rows / 2; + const progress_width: u32 = 40; + const progress_x = (self.text.columns - progress_width) / 2; + + self.text.set_colors(self.theme.foreground, self.theme.background); + self.text.set_cursor((self.text.columns - @as(u32, @intCast(message.len))) / 2, progress_y - 1); + self.text.print(message); + + self.text.set_cursor(progress_x, progress_y); + self.text.put_char('['); + + const filled = (percent * (progress_width - 2)) / 100; + var i: u32 = 0; + while (i < progress_width - 2) : (i += 1) { + if (i < filled) { + self.text.set_colors(self.theme.success, self.theme.background); + self.text.put_char('='); + } else { + self.text.set_colors(self.theme.border, self.theme.background); + self.text.put_char('-'); + } + } + + self.text.set_colors(self.theme.foreground, self.theme.background); + self.text.put_char(']'); + } + + pub fn draw_message(self: *Renderer, message: []const u8, is_error: bool) void { + const msg_y = self.text.rows / 2; + + if (is_error) { + self.text.set_colors(self.theme.error_color, self.theme.background); + } else { + self.text.set_colors(self.theme.success, self.theme.background); + } + + self.text.set_cursor((self.text.columns - @as(u32, @intCast(message.len))) / 2, msg_y); + self.text.print(message); + } +}; diff --git a/hikari/menu/theme.zig b/hikari/menu/theme.zig new file mode 100644 index 0000000..b306ec0 --- /dev/null +++ b/hikari/menu/theme.zig @@ -0,0 +1,55 @@ +//! Hikari Menu Theme + +const display = @import("../display/display.zig"); + +pub const Theme = struct { + background: display.Color, + foreground: display.Color, + highlight_bg: display.Color, + highlight_fg: display.Color, + title_fg: display.Color, + border: display.Color, + shadow: display.Color, + success: display.Color, + warning: display.Color, + error_color: display.Color, + + pub const default = Theme{ + .background = display.Color.rgb(20, 20, 30), + .foreground = display.Color.rgb(220, 220, 230), + .highlight_bg = display.Color.rgb(80, 60, 140), + .highlight_fg = display.Color.rgb(255, 255, 255), + .title_fg = display.Color.rgb(180, 140, 255), + .border = display.Color.rgb(60, 50, 80), + .shadow = display.Color.rgb(10, 10, 15), + .success = display.Color.rgb(80, 200, 120), + .warning = display.Color.rgb(255, 200, 80), + .error_color = display.Color.rgb(255, 80, 80), + }; + + pub const light = Theme{ + .background = display.Color.rgb(240, 240, 245), + .foreground = display.Color.rgb(30, 30, 40), + .highlight_bg = display.Color.rgb(100, 80, 180), + .highlight_fg = display.Color.rgb(255, 255, 255), + .title_fg = display.Color.rgb(60, 40, 120), + .border = display.Color.rgb(180, 180, 190), + .shadow = display.Color.rgb(200, 200, 210), + .success = display.Color.rgb(40, 160, 80), + .warning = display.Color.rgb(200, 150, 40), + .error_color = display.Color.rgb(200, 60, 60), + }; + + pub const akiba = Theme{ + .background = display.Color.rgb(15, 0, 30), + .foreground = display.Color.rgb(255, 120, 200), + .highlight_bg = display.Color.rgb(255, 0, 128), + .highlight_fg = display.Color.rgb(255, 255, 255), + .title_fg = display.Color.rgb(0, 255, 255), + .border = display.Color.rgb(128, 0, 255), + .shadow = display.Color.rgb(5, 0, 10), + .success = display.Color.rgb(0, 255, 128), + .warning = display.Color.rgb(255, 255, 0), + .error_color = display.Color.rgb(255, 0, 64), + }; +}; diff --git a/hikari/paging/constants.zig b/hikari/paging/constants.zig new file mode 100644 index 0000000..73a4b53 --- /dev/null +++ b/hikari/paging/constants.zig @@ -0,0 +1,35 @@ +//! Hikari Paging Constants + +pub const page_size: u64 = 4096; +pub const page_shift: u6 = 12; + +pub const entries_per_table: u64 = 512; +pub const entry_shift: u6 = 9; + +pub const pml4_shift: u6 = 39; +pub const pdpt_shift: u6 = 30; +pub const pd_shift: u6 = 21; +pub const pt_shift: u6 = 12; + +pub const huge_page_size_1g: u64 = 1 << 30; +pub const huge_page_size_2m: u64 = 1 << 21; + +pub const flag_present: u64 = 1 << 0; +pub const flag_writable: u64 = 1 << 1; +pub const flag_user: u64 = 1 << 2; +pub const flag_write_through: u64 = 1 << 3; +pub const flag_cache_disable: u64 = 1 << 4; +pub const flag_accessed: u64 = 1 << 5; +pub const flag_dirty: u64 = 1 << 6; +pub const flag_huge_page: u64 = 1 << 7; +pub const flag_global: u64 = 1 << 8; +pub const flag_no_execute: u64 = 1 << 63; + +pub const address_mask: u64 = 0x000FFFFFFFFFF000; + +pub const kernel_base: u64 = 0xFFFFFFFF80000000; +pub const physmap_base: u64 = 0xFFFF800000000000; +pub const physmap_size: u64 = 512 * huge_page_size_1g; + +pub const pml4_index_kernel: u64 = 511; +pub const pml4_index_physmap: u64 = 256; diff --git a/hikari/paging/paging.zig b/hikari/paging/paging.zig new file mode 100644 index 0000000..0ad9321 --- /dev/null +++ b/hikari/paging/paging.zig @@ -0,0 +1,21 @@ +//! Hikari Paging Subsystem + +pub const constants = @import("constants.zig"); +pub const types = @import("types.zig"); +pub const setup = @import("setup.zig"); + +pub const PageTableEntry = types.PageTableEntry; +pub const PageTable = types.PageTable; +pub const PageMapLevel4 = types.PageMapLevel4; +pub const PageDirectoryPointerTable = types.PageDirectoryPointerTable; +pub const PageDirectory = types.PageDirectory; +pub const PageTableLevel1 = types.PageTableLevel1; + +pub const PageTableSetup = setup.PageTableSetup; +pub const SetupError = setup.SetupError; + +pub const get_pml4_index = types.get_pml4_index; +pub const get_pdpt_index = types.get_pdpt_index; +pub const get_pd_index = types.get_pd_index; +pub const get_pt_index = types.get_pt_index; +pub const get_page_offset = types.get_page_offset; diff --git a/hikari/paging/setup.zig b/hikari/paging/setup.zig new file mode 100644 index 0000000..d897b78 --- /dev/null +++ b/hikari/paging/setup.zig @@ -0,0 +1,208 @@ +//! Hikari Page Table Setup + +const efi = @import("../efi/efi.zig"); +const constants = @import("constants.zig"); +const types = @import("types.zig"); + +pub const SetupError = error{ + allocation_failed, + invalid_mapping, +}; + +pub const PageTableSetup = struct { + boot_services: *efi.services.BootServices, + pml4: *types.PageMapLevel4, + allocated_tables: [64]*types.PageTable, + allocated_count: usize, + + pub fn initialize(boot_services: *efi.services.BootServices) SetupError!PageTableSetup { + var pml4_addr: efi.types.PhysicalAddress = 0; + const status = boot_services.allocate_pages( + .any_pages, + .loader_data, + 1, + &pml4_addr, + ); + + if (efi.types.is_error(status)) { + return SetupError.allocation_failed; + } + + const pml4: *types.PageMapLevel4 = @ptrFromInt(pml4_addr); + pml4.clear(); + + return PageTableSetup{ + .boot_services = boot_services, + .pml4 = pml4, + .allocated_tables = undefined, + .allocated_count = 0, + }; + } + + pub fn allocate_table(self: *PageTableSetup) SetupError!*types.PageTable { + var table_addr: efi.types.PhysicalAddress = 0; + const status = self.boot_services.allocate_pages( + .any_pages, + .loader_data, + 1, + &table_addr, + ); + + if (efi.types.is_error(status)) { + return SetupError.allocation_failed; + } + + const table: *types.PageTable = @ptrFromInt(table_addr); + table.clear(); + + if (self.allocated_count < 64) { + self.allocated_tables[self.allocated_count] = table; + self.allocated_count += 1; + } + + return table; + } + + pub fn map_identity(self: *PageTableSetup, start: u64, size: u64) SetupError!void { + try self.map_range(start, start, size, constants.flag_present | constants.flag_writable); + } + + pub fn map_kernel(self: *PageTableSetup, physical: u64, size: u64) SetupError!void { + try self.map_range( + constants.kernel_base, + physical, + size, + constants.flag_present | constants.flag_writable | constants.flag_global, + ); + } + + pub fn map_physmap(self: *PageTableSetup, max_physical: u64) SetupError!void { + const pdpt = try self.allocate_table(); + + const pml4_entry = types.PageTableEntry.from_address( + @intFromPtr(pdpt), + constants.flag_present | constants.flag_writable, + ); + self.pml4.set_entry(constants.pml4_index_physmap, pml4_entry); + + const size_to_map = if (max_physical > constants.physmap_size) + constants.physmap_size + else + max_physical; + + const gb_count = (size_to_map + constants.huge_page_size_1g - 1) / constants.huge_page_size_1g; + + var i: u64 = 0; + while (i < gb_count and i < 512) : (i += 1) { + const physical_addr = i * constants.huge_page_size_1g; + const pdpt_entry = types.PageTableEntry.from_address( + physical_addr, + constants.flag_present | constants.flag_writable | constants.flag_huge_page | constants.flag_global, + ); + pdpt.set_entry(@truncate(i), pdpt_entry); + } + } + + pub fn map_range(self: *PageTableSetup, virtual: u64, physical: u64, size: u64, flags: u64) SetupError!void { + var virt = virtual & ~@as(u64, constants.page_size - 1); + var phys = physical & ~@as(u64, constants.page_size - 1); + var remaining = size; + + while (remaining > 0) { + const pml4_idx = types.get_pml4_index(virt); + var pdpt: *types.PageDirectoryPointerTable = undefined; + + if (self.pml4.get_entry(pml4_idx).is_present()) { + pdpt = @ptrFromInt(self.pml4.get_entry(pml4_idx).get_address()); + } else { + pdpt = try self.allocate_table(); + const entry = types.PageTableEntry.from_address( + @intFromPtr(pdpt), + constants.flag_present | constants.flag_writable, + ); + self.pml4.set_entry(pml4_idx, entry); + } + + const pdpt_idx = types.get_pdpt_index(virt); + var pd: *types.PageDirectory = undefined; + + if (pdpt.get_entry(pdpt_idx).is_present()) { + if (pdpt.get_entry(pdpt_idx).is_huge()) { + virt += constants.huge_page_size_1g; + phys += constants.huge_page_size_1g; + if (remaining >= constants.huge_page_size_1g) { + remaining -= constants.huge_page_size_1g; + } else { + remaining = 0; + } + continue; + } + pd = @ptrFromInt(pdpt.get_entry(pdpt_idx).get_address()); + } else { + pd = try self.allocate_table(); + const entry = types.PageTableEntry.from_address( + @intFromPtr(pd), + constants.flag_present | constants.flag_writable, + ); + pdpt.set_entry(pdpt_idx, entry); + } + + const pd_idx = types.get_pd_index(virt); + + if (remaining >= constants.huge_page_size_2m and + (virt & (constants.huge_page_size_2m - 1)) == 0 and + (phys & (constants.huge_page_size_2m - 1)) == 0) + { + const entry = types.PageTableEntry.from_address( + phys, + flags | constants.flag_huge_page, + ); + pd.set_entry(pd_idx, entry); + + virt += constants.huge_page_size_2m; + phys += constants.huge_page_size_2m; + remaining -= constants.huge_page_size_2m; + continue; + } + + var pt: *types.PageTableLevel1 = undefined; + + if (pd.get_entry(pd_idx).is_present()) { + if (pd.get_entry(pd_idx).is_huge()) { + virt += constants.huge_page_size_2m; + phys += constants.huge_page_size_2m; + if (remaining >= constants.huge_page_size_2m) { + remaining -= constants.huge_page_size_2m; + } else { + remaining = 0; + } + continue; + } + pt = @ptrFromInt(pd.get_entry(pd_idx).get_address()); + } else { + pt = try self.allocate_table(); + const entry = types.PageTableEntry.from_address( + @intFromPtr(pt), + constants.flag_present | constants.flag_writable, + ); + pd.set_entry(pd_idx, entry); + } + + const pt_idx = types.get_pt_index(virt); + const entry = types.PageTableEntry.from_address(phys, flags); + pt.set_entry(pt_idx, entry); + + virt += constants.page_size; + phys += constants.page_size; + if (remaining >= constants.page_size) { + remaining -= constants.page_size; + } else { + remaining = 0; + } + } + } + + pub fn get_pml4_address(self: *PageTableSetup) u64 { + return @intFromPtr(self.pml4); + } +}; diff --git a/hikari/paging/types.zig b/hikari/paging/types.zig new file mode 100644 index 0000000..f5e19b4 --- /dev/null +++ b/hikari/paging/types.zig @@ -0,0 +1,88 @@ +//! Hikari Paging Types + +const constants = @import("constants.zig"); + +pub const PageTableEntry = packed struct(u64) { + present: bool, + writable: bool, + user: bool, + write_through: bool, + cache_disable: bool, + accessed: bool, + dirty: bool, + huge_page: bool, + global: bool, + available_low: u3, + address_bits: u40, + available_high: u11, + no_execute: bool, + + pub fn empty() PageTableEntry { + return @bitCast(@as(u64, 0)); + } + + pub fn from_address(address: u64, flags: u64) PageTableEntry { + const entry: u64 = (address & constants.address_mask) | flags; + return @bitCast(entry); + } + + pub fn get_address(self: PageTableEntry) u64 { + const raw: u64 = @bitCast(self); + return raw & constants.address_mask; + } + + pub fn is_present(self: PageTableEntry) bool { + return self.present; + } + + pub fn is_huge(self: PageTableEntry) bool { + return self.huge_page; + } + + pub fn to_raw(self: PageTableEntry) u64 { + return @bitCast(self); + } +}; + +pub const PageTable = struct { + entries: [512]PageTableEntry, + + pub fn clear(self: *PageTable) void { + for (&self.entries) |*entry| { + entry.* = PageTableEntry.empty(); + } + } + + pub fn get_entry(self: *PageTable, index: usize) *PageTableEntry { + return &self.entries[index]; + } + + pub fn set_entry(self: *PageTable, index: usize, entry: PageTableEntry) void { + self.entries[index] = entry; + } +}; + +pub const PageMapLevel4 = PageTable; +pub const PageDirectoryPointerTable = PageTable; +pub const PageDirectory = PageTable; +pub const PageTableLevel1 = PageTable; + +pub fn get_pml4_index(address: u64) usize { + return @truncate((address >> constants.pml4_shift) & 0x1FF); +} + +pub fn get_pdpt_index(address: u64) usize { + return @truncate((address >> constants.pdpt_shift) & 0x1FF); +} + +pub fn get_pd_index(address: u64) usize { + return @truncate((address >> constants.pd_shift) & 0x1FF); +} + +pub fn get_pt_index(address: u64) usize { + return @truncate((address >> constants.pt_shift) & 0x1FF); +} + +pub fn get_page_offset(address: u64) usize { + return @truncate(address & 0xFFF); +} -- cgit v1.2.3