commit 0709f980afbc7ac326c2c2a909f360375a3cc7ba Author: David Gonzalez Martin Date: Sun Jul 9 11:24:30 2023 -0600 first commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4589729 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Default behavior, if core.autocrlf is unset. +* text=auto + +# Files to be converted to native line endings on checkout. +*.cpp text +*.h text + +# Text files to always have LF (unix) line endings on checkout. +*.zig text eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..c44cc56 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [davidgm94] diff --git a/.github/workflows/lightning.yml b/.github/workflows/lightning.yml new file mode 100644 index 0000000..cc6f4ec --- /dev/null +++ b/.github/workflows/lightning.yml @@ -0,0 +1,44 @@ +name: Lightning + +on: + pull_request: + paths: + - ".github/workflows/lightning.yml" + - "**.zig" + push: + paths: + - ".github/workflows/lightning.yml" + - "**.zig" + branches: + - main + schedule: + - cron: "0 0 * * *" +concurrency: + # Cancels pending runs when a PR gets updated. + group: ${{ github.head_ref || github.run_id }}-${{ github.actor }} + cancel-in-progress: true + +jobs: + build: + strategy: + matrix: + os: [ + ubuntu-latest, + macos-latest, + windows-latest, + ] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - uses: davidgm94/setup-qemu@v3 + - uses: goto-bus-stop/setup-zig@v2 + with: + version: master + + - name: Zig environment variables + run: zig env + - name: Build test executables + run: zig build all_tests -Dci --verbose + - name: Test with QEMU + run: zig build test_all -Dci --verbose + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0f2feb --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +zig-cache/* +zig-out/* +*.png +logfile +debug_disk +.gdb_history +bochsout.txt +*.hdd +loopback_device diff --git a/README.md b/README.md new file mode 100644 index 0000000..a5948b5 --- /dev/null +++ b/README.md @@ -0,0 +1,119 @@ +# Rise: an attempt to write a better operating system + +![Build status](https://img.shields.io/github/actions/workflow/status/davidgm94/rise/lightning.yml?branch=main) + +An experiment of an operating system for modern 64-bit architectures which focuses on building robust, fast and usable system software and learning how to do it along the way. + +The current plan is to explore the idea of the multikernel exposed in the Barrelfish and Arrakis papers (very roughly simplified, an exokernel per core). Hopefully this academic model proves worthy, resulting in a big improvement in multiple aspects. If not, a hybrid kernel model with high-performance async syscalls will be used. + +The operating system design for developers aims at fast iteration times, so there are no external dependencies aside from the execution environment and the compiler. + +Currently only the Limine bootloader and a custom one are supported. Both only support BIOS and UEFI for the x86_64 architecture and both implement SMP trampoline, modules, memory map and framebuffer, among other features. + +The disk image creation is currently raw (not ISO format) made by a handmade written-in-Zig FAT32 driver which needs severe improvements and bug fixing, but it does the job for now. + +For each run, Github CI currently compiles all build and test artifacts and tests all the guest (only x86_64 for now) and host executables. Guest testing is done through QEMU TCG. + +## High-level design goals + +- Multikernel model, which would grant more safety and speed. +- Try to supress interpreted/JIT language uses in every field as much as possible, preferring compiled type-safe native languages instead and then favoring speed, robustness and safety. +- Make everything go reasonably fast (as fast as possible). +- Usable desktop, for both basic and developer purposes. +- Sandboxed execution of programs by default. +- New library and executable format for modern times, which aims at performance and scalability and admits both static and dynamic linking, preferring static. +- Prefer typed commmunication as opposed to strings, for example in program arguments and configuration files. +- Clean up shells, move away from current ones as much as possible: make it type-safe and compiled, commands are function calls from libraries instead of executables, etc. +- Promote open-source driver code (especially for GPUs, since these drivers being close-source is hurting the computing space) and simplified drivers through ISA/DMA. +- (far away in the future) Think of a way to substitute browser's Javascript for native compiled code. + +## External dependencies to compile and run the code (executables your machine should have in the PATH environment variable) + +* The Zig compiler (master) - This is required to compile and run the code. Apart from the operating system being written in Zig, Zig is used as a build system, so no platform-specific scripting language is needed. The easiest way to get it is to download the master binary at the website. +* QEMU - to load and execute the operating system in a virtual environment +* GDB - only for debugging + +## Internal dependencies + +* STB TTF + +## Build and run instructions + +- To build for the default target options (located in config/default.json): `zig build` +- To build and run for the default target options: `zig build run` +- To build and debug for the default target options: `zig build debug` +- To build and test for the default target options: `zig build test` +- To build and debug the tests for the default target options: `zig build test_debug` +- To build all host and guest normal artifacts: `zig build all` +- To build all host and guest test artifacts: `zig build all_tests` +- To build and run all host and guest tests: `zig build test_all` +- To run any other specialized step, please consult the steps listed in `zig build --help` + +## Target architectures: + +- [x] x86_64 +- [ ] RISC-V 64 +- [ ] aarch64 + +Currently only x86_64 is supported, although aarch64 and RISC-V 64 are planned for implementation. + +## Target execution environments + +- [x] Real hardware. BIG DISCLAIMER: Support on real hardware is really primitive as it has been implemented recently. Only the UEFI boot protocol is tested and should only be tried/tested if you know what you are doing. Moreover, since currently there is no CI for real hardware, due to the diversity of the x86-64 platform and the lack of testing, real hardware might not work as emulated ones do. + +### Emulators/Hypervisors + +#### QEMU + - [x] KVM + - [ ] XEN + - [ ] HAX + - [ ] HVF + - [ ] NVMM + - [ ] WHPX + - [x] TCG + +##### Degree of QEMU emulation supported right now: + +- Linux + +* [x] Run +* [x] Debug + +- Windows + +* [x] Run +* [ ] Debug + +- MacOS + +* [x] Run +* [x] Debug + +#### Other execution environments + +- [ ] Bochs +- [ ] VMWare +- [ ] VirtualBox + +## Next tasks to be done + +### General + +* Implement the CPU driver according to the `multikernel` model. + +## Inspirations and acknowledgements + +- Linux kernel: https://kernel.org +- Barrelfish and Arrakis: +* https://barrelfish.org/ +* https://arrakis.cs.washington.edu/ +- Essence: https://gitlab.com/nakst/essence/ +- Limine bootloader: https://github.com/limine-bootloader/limine +- Florence: https://github.com/FlorenceOS/Florence +- Managarm: https://github.com/managarm/managarm +- Jonathan Blow ideas on how an operating system should be: +* https://youtu.be/k0uE_chSnV8 +* https://youtu.be/xXSIs4aTqhI +- Casey Muratori's lecture: https://youtu.be/kZRE7HIO3vk +- Zig Discord channel: https://discord.gg/gxsFFjE +- OSDev Discord channel: https://discord.gg/RnCtsqD diff --git a/bochsrc b/bochsrc new file mode 100644 index 0000000..3d7d9dd --- /dev/null +++ b/bochsrc @@ -0,0 +1,17 @@ +cpu: count=2, reset_on_triple_fault=0 + +display_library: x, options="gui_debug" + +megs: 512 + +clock: sync=realtime, time0=local + +ata0-master: type=disk, path="zig-cache/rise_Debug_x86_64_rise_bios_normal_exe.hdd", mode=flat + +boot: c + +log: ./bochsout.txt + +mouse: enabled=0 + +magic_break: enabled=1 diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..f3a07b2 --- /dev/null +++ b/build.zig @@ -0,0 +1,743 @@ +const std = @import("std"); +const common = @import("src/common.zig"); +const os = common.os; + +// Build types +const Build = std.Build; +const CompileStep = std.Build.CompileStep; +const FileSource = std.Build.FileSource; +const Module = std.Build.Module; +const ModuleDependency = std.Build.ModuleDependency; +const OptionsStep = std.Build.OptionsStep; +const RunStep = std.Build.RunStep; +const Step = std.Build.Step; + +const assert = std.debug.assert; +const Bootloader = common.Bootloader; +const Configuration = common.Configuration; +const Cpu = common.Cpu; +const CrossTarget = common.CrossTarget; +const DiskType = common.DiskType; +const ExecutionType = common.ExecutionType; +const ExecutionEnvironment = common.ExecutionEnvironment; +const FilesystemType = common.FilesystemType; +const OptimizeMode = common.OptimizeMode; +const QEMUOptions = common.QEMUOptions; +const RiseProgram = common.RiseProgram; +const Suffix = common.Suffix; +const Target = common.Target; + +const Error = error{ + not_implemented, + architecture_not_supported, + failed_to_run, +}; + +const source_root_dir = "src"; +const user_program_dir_path = "src/user/programs"; + +var ci = false; +var debug_user = false; +var debug_loader = false; +var modules = Modules{}; +var b: *Build = undefined; +var build_steps: *BuildSteps = undefined; +var default_configuration: Configuration = undefined; +var user_modules: []const common.Module = undefined; +var options = Options{}; + +pub fn build(b_arg: *Build) !void { + b = b_arg; + ci = b.option(bool, "ci", "CI mode") orelse false; + debug_user = b.option(bool, "debug_user", "Debug user program") orelse false; + debug_loader = b.option(bool, "debug_loader", "Debug loader program") orelse false; + const default_cfg_override = b.option([]const u8, "default", "Default configuration JSON file") orelse "config/default.json"; + modules = blk: { + var mods = Modules{}; + inline for (comptime common.enumValues(ModuleID)) |module_id| { + mods.modules.set(module_id, b.createModule(.{ + .source_file = FileSource.relative(switch (module_id) { + .limine_installer => "src/bootloader/limine/installer.zig", + else => switch (module_id) { + .bios, .uefi, .limine => "src/bootloader", + else => "src", + } ++ "/" ++ @tagName(module_id) ++ ".zig", + }), + })); + } + + try mods.setDependencies(.lib, &.{}); + try mods.setDependencies(.host, &.{.lib}); + try mods.setDependencies(.bootloader, &.{ .lib, .privileged }); + try mods.setDependencies(.bios, &.{ .lib, .privileged }); + try mods.setDependencies(.limine, &.{ .lib, .privileged }); + try mods.setDependencies(.uefi, &.{ .lib, .privileged }); + try mods.setDependencies(.limine_installer, &.{ .lib, .privileged }); + try mods.setDependencies(.privileged, &.{ .lib, .bootloader }); + try mods.setDependencies(.cpu, &.{ .privileged, .lib, .bootloader, .rise }); + try mods.setDependencies(.rise, &.{.lib}); + try mods.setDependencies(.user, &.{ .lib, .rise }); + + break :blk mods; + }; + + options = blk: { + var opts = Options{}; + opts.createOption(.bootloader); + opts.createOption(.cpu); + opts.createOption(.user); + opts.createOption(.host); + break :blk opts; + }; + + default_configuration = blk: { + const default_json_file = try std.fs.cwd().readFileAlloc(b.allocator, default_cfg_override, common.maxInt(usize)); + const parsed_cfg = try std.json.parseFromSlice(Configuration, b.allocator, default_json_file, .{}); + const cfg = parsed_cfg.value; + + const optimize_mode = b.option( + std.builtin.Mode, + "optimize", + "Prioritize performance, safety, or binary size (-O flag)", + ) orelse cfg.optimize_mode; + + break :blk Configuration{ + .architecture = b.standardTargetOptions(.{ .default_target = .{ .cpu_arch = cfg.architecture } }).getCpuArch(), + .bootloader = cfg.bootloader, + .boot_protocol = cfg.boot_protocol, + .execution_environment = cfg.execution_environment, + .optimize_mode = optimize_mode, + .execution_type = cfg.execution_type, + .executable_kind = .exe, + }; + }; + + build_steps = try b.allocator.create(BuildSteps); + build_steps.* = .{ + .build_all = b.step("all", "Build all the artifacts"), + .build_all_tests = b.step("all_tests", "Build all the artifacts related to tests"), + .run = b.step("run", "Run the operating system through an emulator"), + .debug = b.step("debug", "Debug the operating system through an emulator"), + .test_run = b.step("test", "Run unit tests"), + .test_debug = b.step("test_debug", "Debug unit tests"), + .test_all = b.step("test_all", "Run all unit tests"), + .test_host = b.step("test_host", "Run host unit tests"), + }; + + const disk_image_builder_modules = &.{ .lib, .host, .bootloader, .limine_installer, .bios }; + const disk_image_root_path = "src/host/disk_image_builder"; + const disk_image_builder = blk: { + const exe = try addCompileStep(.{ + .kind = .exe, + .name = "disk_image_builder", + .root_project_path = disk_image_root_path, + .modules = disk_image_builder_modules, + }); + + b.default_step.dependOn(&exe.step); + + break :blk exe; + }; + + const runner = blk: { + const exe = try addCompileStep(.{ + .kind = .exe, + .name = "runner", + .root_project_path = "src/host/runner", + .modules = &.{ .lib, .host }, + }); + + b.default_step.dependOn(&exe.step); + + break :blk exe; + }; + + const native_tests = [_]struct { + name: []const u8, + root_project_path: []const u8, + modules: []const ModuleID, + c: ?C = null, + + const C = struct { + include_paths: []const []const u8, + source_files: []const SourceFile, + link_libc: bool, + link_libcpp: bool, + + const SourceFile = struct { + path: []const u8, + flags: []const []const u8, + }; + }; + }{ + .{ + .name = "host_native_test", + .root_project_path = "src/host", + .modules = &.{ .lib, .host }, + }, + .{ + .name = "disk_image_builder_native_test", + .root_project_path = disk_image_root_path, + .modules = disk_image_builder_modules, + .c = .{ + .include_paths = &.{"src/bootloader/limine/installables"}, + .source_files = &.{ + .{ + .path = "src/bootloader/limine/installables/limine-deploy.c", + .flags = &.{}, + }, + }, + .link_libc = true, + .link_libcpp = false, + }, + }, + }; + + const native_test_optimize_mode = .ReleaseFast; + for (native_tests) |native_test| { + const test_name = try std.mem.concat(b.allocator, u8, &.{ native_test.name, "_", @tagName(native_test_optimize_mode) }); + const test_exe = try addCompileStep(.{ + .name = test_name, + .root_project_path = native_test.root_project_path, + .optimize_mode = native_test_optimize_mode, + .modules = native_test.modules, + .kind = .@"test", + }); + + if (native_test.c) |c| { + for (c.include_paths) |include_path| { + test_exe.addIncludePath(include_path); + } + + for (c.source_files) |source_file| { + test_exe.addCSourceFile(source_file.path, source_file.flags); + } + + if (c.link_libc) { + test_exe.linkLibC(); + } + + if (c.link_libcpp) { + test_exe.linkLibCpp(); + } + } + + const run_test_step = b.addRunArtifact(test_exe); + //run_test_step.condition = .always; + build_steps.test_all.dependOn(&run_test_step.step); + build_steps.test_host.dependOn(&run_test_step.step); + } + + { + var user_module_list = std.ArrayList(common.Module).init(b.allocator); + var user_program_dir = try std.fs.cwd().openIterableDir(user_program_dir_path, .{ .access_sub_paths = true }); + defer user_program_dir.close(); + + var user_program_iterator = user_program_dir.iterate(); + + while (try user_program_iterator.next()) |entry| { + const dir_name = entry.name; + const file_path = try std.mem.concat(b.allocator, u8, &.{ dir_name, "/module.json" }); + const file = try user_program_dir.dir.readFileAlloc(b.allocator, file_path, common.maxInt(usize)); + const parsed_user_program = try std.json.parseFromSlice(common.UserProgram, b.allocator, file, .{}); + const user_program = parsed_user_program.value; + try user_module_list.append(.{ + .program = user_program, + .name = b.dupe(dir_name), // we have to dupe here otherwise Windows CI fails + }); + } + + user_modules = user_module_list.items; + } + + const executable_kinds = [2]CompileStep.Kind{ .exe, .@"test" }; + + for (common.enumValues(OptimizeMode)) |optimize_mode| { + for (common.supported_architectures, 0..) |architecture, architecture_index| { + const user_target = try getTarget(architecture, .user); + + for (executable_kinds) |executable_kind| { + const is_test = executable_kind == .@"test"; + const cpu_driver_path = "src/cpu"; + const target = try getTarget(architecture, .privileged); + const cpu_driver = try addCompileStep(.{ + .kind = executable_kind, + .name = "cpu_driver", + .root_project_path = cpu_driver_path, + .target = target, + .optimize_mode = optimize_mode, + .modules = &.{ .lib, .bootloader, .privileged, .cpu, .rise }, + }); + + cpu_driver.force_pic = true; + cpu_driver.disable_stack_probing = true; + cpu_driver.stack_protector = false; + cpu_driver.strip = false; + cpu_driver.red_zone = false; + cpu_driver.omit_frame_pointer = false; + + cpu_driver.code_model = switch (architecture) { + .x86_64 => .kernel, + .riscv64 => .medium, + .aarch64 => .small, + else => return Error.architecture_not_supported, + }; + + const cpu_driver_linker_script_path = FileSource.relative(try std.mem.concat(b.allocator, u8, &.{ cpu_driver_path, "/arch/", switch (architecture) { + .x86_64 => "x86/64", + .x86 => "x86/32", + else => @tagName(architecture), + }, "/linker_script.ld" })); + + cpu_driver.setLinkerScriptPath(cpu_driver_linker_script_path); + + var user_module_list = try std.ArrayList(*CompileStep).initCapacity(b.allocator, user_modules.len); + const user_architecture_source_path = try std.mem.concat(b.allocator, u8, &.{ "src/user/arch/", @tagName(architecture), "/" }); + const user_linker_script_path = FileSource.relative(try std.mem.concat(b.allocator, u8, &.{ user_architecture_source_path, "linker_script.ld" })); + for (user_modules) |module| { + const user_module = try addCompileStep(.{ + .kind = executable_kind, + .name = module.name, + .root_project_path = try std.mem.concat(b.allocator, u8, &.{ user_program_dir_path, "/", module.name }), + .target = user_target, + .optimize_mode = optimize_mode, + .modules = &.{ .lib, .user, .rise }, + }); + user_module.strip = false; + + user_module.setLinkerScriptPath(user_linker_script_path); + + user_module_list.appendAssumeCapacity(user_module); + } + + const bootloaders = common.architecture_bootloader_map[architecture_index]; + for (bootloaders) |bootloader_struct| { + const bootloader = bootloader_struct.id; + for (bootloader_struct.protocols) |boot_protocol| { + const rise_loader_path = "src/bootloader/rise/"; + const limine_loader_path = "src/bootloader/limine/"; + const bootloader_name = "loader"; + const bootloader_modules = [_]ModuleID{ .lib, .bootloader, .privileged }; + + const bootloader_compile_step = switch (bootloader) { + .rise => switch (boot_protocol) { + .bios => switch (architecture) { + .x86_64 => blk: { + const bootloader_path = rise_loader_path ++ "bios"; + const executable = try addCompileStep(.{ + .kind = executable_kind, + .name = bootloader_name, + .root_project_path = bootloader_path, + .target = try getTarget(.x86, .privileged), + .optimize_mode = .ReleaseSmall, + .modules = &(bootloader_modules ++ .{.bios}), + }); + + executable.strip = true; + + executable.addAssemblyFile("src/bootloader/arch/x86/64/smp_trampoline.S"); + executable.addAssemblyFile(bootloader_path ++ "/unreal_mode.S"); + executable.setLinkerScriptPath(FileSource.relative(bootloader_path ++ "/linker_script.ld")); + executable.code_model = .small; + + break :blk executable; + }, + else => return Error.architecture_not_supported, + }, + .uefi => blk: { + const bootloader_path = rise_loader_path ++ "uefi"; + const executable = try addCompileStep(.{ + .kind = executable_kind, + .name = bootloader_name, + .root_project_path = bootloader_path, + .target = .{ + .cpu_arch = architecture, + .os_tag = .uefi, + .abi = .msvc, + }, + .optimize_mode = .ReleaseSafe, + .modules = &(bootloader_modules ++ .{.uefi}), + }); + + executable.strip = true; + + switch (architecture) { + .x86_64 => executable.addAssemblyFile("src/bootloader/arch/x86/64/smp_trampoline.S"), + else => {}, + } + + break :blk executable; + }, + }, + .limine => blk: { + const bootloader_path = limine_loader_path; + const executable = try addCompileStep(.{ + .kind = executable_kind, + .name = bootloader_name, + .root_project_path = bootloader_path, + .target = target, + .optimize_mode = .ReleaseSafe, + .modules = &(bootloader_modules ++ .{.limine}), + }); + + executable.force_pic = true; + executable.omit_frame_pointer = false; + executable.want_lto = false; + executable.strip = false; + + executable.code_model = cpu_driver.code_model; + + executable.setLinkerScriptPath(FileSource.relative(try common.concat(b.allocator, u8, &.{ limine_loader_path ++ "arch/", @tagName(architecture), "/linker_script.ld" }))); + + break :blk executable; + }, + }; + + bootloader_compile_step.disable_stack_probing = true; + bootloader_compile_step.stack_protector = false; + bootloader_compile_step.red_zone = false; + + if (architecture == default_configuration.architecture and bootloader == default_configuration.bootloader and boot_protocol == default_configuration.boot_protocol and optimize_mode == default_configuration.optimize_mode and !is_test) { + addObjdump(bootloader_compile_step, bootloader_name); + addFileSize(bootloader_compile_step, bootloader_name); + } + + const execution_environments: []const ExecutionEnvironment = switch (bootloader) { + .rise, .limine => switch (boot_protocol) { + .bios => switch (architecture) { + .x86_64 => &.{.qemu}, + else => return Error.architecture_not_supported, + }, + .uefi => &.{.qemu}, + }, + }; + + const execution_types: []const ExecutionType = + switch (common.canVirtualizeWithQEMU(architecture, ci)) { + true => &.{ .emulated, .accelerated }, + false => &.{.emulated}, + }; + + for (execution_types) |execution_type| { + for (execution_environments) |execution_environment| { + const configuration = Configuration{ + .architecture = architecture, + .bootloader = bootloader, + .boot_protocol = boot_protocol, + .optimize_mode = optimize_mode, + .execution_environment = execution_environment, + .execution_type = execution_type, + .executable_kind = executable_kind, + }; + + var disk_argument_parser = common.ArgumentParser.DiskImageBuilder{}; + const disk_image_builder_run = b.addRunArtifact(disk_image_builder); + const disk_image_path = disk_image_builder_run.addOutputFileArg("disk.hdd"); + + while (disk_argument_parser.next()) |argument_type| switch (argument_type) { + .configuration => inline for (common.fields(Configuration)) |field| disk_image_builder_run.addArg(@tagName(@field(configuration, field.name))), + .image_configuration_path => disk_image_builder_run.addArg(common.ImageConfig.default_path), + .disk_image_path => { + // Must be first + assert(@intFromEnum(argument_type) == 0); + }, + .bootloader => { + disk_image_builder_run.addArtifactArg(bootloader_compile_step); + }, + .cpu => disk_image_builder_run.addArtifactArg(cpu_driver), + .user_programs => for (user_module_list.items) |user_module| disk_image_builder_run.addArtifactArg(user_module), + }; + + const user_init = user_module_list.items[0]; + + const runner_run = try newRunnerRunArtifact(.{ + .configuration = configuration, + .disk_image_path = disk_image_path, + .cpu_driver = cpu_driver, + .loader = bootloader_compile_step, + .user_init = user_init, + .runner = runner, + .qemu_options = .{ + .is_debug = false, + .is_test = is_test, + }, + }); + const runner_debug = try newRunnerRunArtifact(.{ + .configuration = configuration, + .disk_image_path = disk_image_path, + .cpu_driver = cpu_driver, + .loader = bootloader_compile_step, + .user_init = user_init, + .runner = runner, + .qemu_options = .{ + .is_debug = true, + .is_test = is_test, + }, + }); + + if (is_test) { + build_steps.test_all.dependOn(&runner_run.step); + } + + if (architecture == default_configuration.architecture and bootloader == default_configuration.bootloader and boot_protocol == default_configuration.boot_protocol and optimize_mode == default_configuration.optimize_mode and execution_environment == default_configuration.execution_environment and execution_type == default_configuration.execution_type) { + if (is_test) { + build_steps.test_run.dependOn(&runner_run.step); + build_steps.test_debug.dependOn(&runner_debug.step); + } else { + build_steps.run.dependOn(&runner_run.step); + build_steps.debug.dependOn(&runner_debug.step); + + b.default_step.dependOn(&bootloader_compile_step.step); + + b.default_step.dependOn(&cpu_driver.step); + + for (user_module_list.items) |user_module| { + b.default_step.dependOn(&user_module.step); + } + + const artifacts: []const *CompileStep = &.{ cpu_driver, user_init }; + const artifact_names: []const []const u8 = &.{ "cpu", "init" }; + + inline for (artifact_names, 0..) |artifact_name, index| { + const artifact = artifacts[index]; + addObjdump(artifact, artifact_name); + addFileSize(artifact, artifact_name); + } + } + } + } + } + } + } + } + } + } + + if (os == .linux) { + const generate_command = b.addSystemCommand(&.{ "dot", "-Tpng" }); + generate_command.addFileSourceArg(FileSource.relative("capabilities.dot")); + generate_command.addArg("-o"); + const png = generate_command.addOutputFileArg("capabilities.png"); + + const visualize_command = b.addSystemCommand(&.{"xdg-open"}); + visualize_command.addFileSourceArg(png); + + const dot_command = b.step("dot", "TEMPORARY: (developers only) Generate a graph and visualize it"); + dot_command.dependOn(&visualize_command.step); + } +} + +const Options = struct { + arr: std.EnumArray(RiseProgram, *OptionsStep) = std.EnumArray(RiseProgram, *OptionsStep).initUndefined(), + + pub fn createOption(options_struct: *Options, rise_program: RiseProgram) void { + const new_options = b.addOptions(); + new_options.addOption(RiseProgram, "program_type", rise_program); + options_struct.arr.set(rise_program, new_options); + } +}; + +const BuildSteps = struct { + build_all: *Step, + build_all_tests: *Step, + debug: *Step, + run: *Step, + test_run: *Step, + test_debug: *Step, + test_all: *Step, + test_host: *Step, +}; + +fn addObjdump(artifact: *CompileStep, comptime name: []const u8) void { + switch (os) { + .linux, .macos => { + const objdump = b.addSystemCommand(&.{ "objdump", "-dxS", "-Mintel" }); + objdump.addArtifactArg(artifact); + const objdump_step = b.step("objdump_" ++ name, "Objdump " ++ name); + objdump_step.dependOn(&objdump.step); + }, + else => {}, + } +} + +fn addFileSize(artifact: *CompileStep, comptime name: []const u8) void { + switch (os) { + .linux, .macos => { + const file_size = b.addSystemCommand(switch (os) { + .linux => &.{ "stat", "-c", "%s" }, + .macos => &.{ "wc", "-c" }, + else => unreachable, + }); + file_size.addArtifactArg(artifact); + + const file_size_step = b.step("file_size_" ++ name, "Get the file size of " ++ name); + file_size_step.dependOn(&file_size.step); + }, + else => {}, + } +} + +fn newRunnerRunArtifact(arguments: struct { + configuration: Configuration, + disk_image_path: FileSource, + loader: *CompileStep, + runner: *CompileStep, + cpu_driver: *CompileStep, + user_init: *CompileStep, + qemu_options: QEMUOptions, +}) !*RunStep { + const runner = b.addRunArtifact(arguments.runner); + var argument_parser = common.ArgumentParser.Runner{}; + while (argument_parser.next()) |argument_type| switch (argument_type) { + .configuration => inline for (common.fields(Configuration)) |field| runner.addArg(@tagName(@field(arguments.configuration, field.name))), + .image_configuration_path => runner.addArg(common.ImageConfig.default_path), + .cpu_driver => runner.addArtifactArg(arguments.cpu_driver), + .loader_path => runner.addArtifactArg(arguments.loader), + .init => runner.addArtifactArg(arguments.user_init), + .disk_image_path => runner.addFileSourceArg(arguments.disk_image_path), + .qemu_options => inline for (common.fields(QEMUOptions)) |field| runner.addArg(if (@field(arguments.qemu_options, field.name)) "true" else "false"), + .ci => runner.addArg(if (ci) "true" else "false"), + .debug_user => runner.addArg(if (debug_user) "true" else "false"), + .debug_loader => runner.addArg(if (debug_loader) "true" else "false"), + }; + + return runner; +} + +const ExecutableDescriptor = struct { + kind: CompileStep.Kind, + name: []const u8, + root_project_path: []const u8, + target: CrossTarget = .{}, + optimize_mode: OptimizeMode = .Debug, + modules: []const ModuleID, +}; + +fn addCompileStep(executable_descriptor: ExecutableDescriptor) !*CompileStep { + const main_file = try std.mem.concat(b.allocator, u8, &.{ executable_descriptor.root_project_path, "/main.zig" }); + const compile_step = switch (executable_descriptor.kind) { + .exe => blk: { + const executable = b.addExecutable(.{ + .name = executable_descriptor.name, + .root_source_file = FileSource.relative(main_file), + .target = executable_descriptor.target, + .optimize = executable_descriptor.optimize_mode, + }); + + build_steps.build_all.dependOn(&executable.step); + + break :blk executable; + }, + .@"test" => blk: { + const test_file = FileSource.relative(try std.mem.concat(b.allocator, u8, &.{ executable_descriptor.root_project_path, "/test.zig" })); + const test_exe = b.addTest(.{ + .name = executable_descriptor.name, + .root_source_file = test_file, + .target = executable_descriptor.target, + .optimize = executable_descriptor.optimize_mode, + .test_runner = if (executable_descriptor.target.os_tag) |_| main_file else null, + }); + + build_steps.build_all_tests.dependOn(&test_exe.step); + + break :blk test_exe; + }, + else => return Error.not_implemented, + }; + + compile_step.link_gc_sections = true; + + if (executable_descriptor.target.getOs().tag == .freestanding) { + compile_step.entry_symbol_name = "_start"; + } + + compile_step.setMainPkgPath(source_root_dir); + + for (executable_descriptor.modules) |module| { + modules.addModule(compile_step, module); + } + + return compile_step; +} + +const ModuleID = enum { + /// This module has typical common stuff used everywhere + lib, + /// This module contains code that is used by host programs when building and trying to run the OS + host, + /// This module contains code related to the bootloaders + bootloader, + bios, + uefi, + limine, + limine_installer, + /// This module contains code that is used by Rise privileged programs + privileged, + /// This module contains code that is unique to Rise CPU drivers + cpu, + /// This module contains code that is used by userspace programs + user, + /// This module contains code that is interacting between userspace and cpu in Rise + rise, +}; + +pub const Modules = struct { + modules: std.EnumArray(ModuleID, *Module) = std.EnumArray(ModuleID, *Module).initUndefined(), + dependencies: std.EnumArray(ModuleID, []const ModuleDependency) = std.EnumArray(ModuleID, []const ModuleDependency).initUndefined(), + + fn addModule(mods: Modules, compile_step: *CompileStep, module_id: ModuleID) void { + compile_step.addModule(@tagName(module_id), mods.modules.get(module_id)); + } + + fn setDependencies(mods: Modules, module_id: ModuleID, dependencies: []const ModuleID) !void { + const module = mods.modules.get(module_id); + try module.dependencies.put(@tagName(module_id), module); + + for (dependencies) |dependency_id| { + const dependency_module = mods.modules.get(dependency_id); + try module.dependencies.put(@tagName(dependency_id), dependency_module); + } + } +}; + +fn getTarget(asked_arch: Cpu.Arch, execution_mode: common.TraditionalExecutionMode) Error!CrossTarget { + var enabled_features = Cpu.Feature.Set.empty; + var disabled_features = Cpu.Feature.Set.empty; + + if (execution_mode == .privileged) { + switch (asked_arch) { + .x86, .x86_64 => { + // disable FPU + const Feature = Target.x86.Feature; + disabled_features.addFeature(@intFromEnum(Feature.x87)); + disabled_features.addFeature(@intFromEnum(Feature.mmx)); + disabled_features.addFeature(@intFromEnum(Feature.sse)); + disabled_features.addFeature(@intFromEnum(Feature.sse2)); + disabled_features.addFeature(@intFromEnum(Feature.avx)); + disabled_features.addFeature(@intFromEnum(Feature.avx2)); + disabled_features.addFeature(@intFromEnum(Feature.avx512f)); + + enabled_features.addFeature(@intFromEnum(Feature.soft_float)); + }, + else => return Error.architecture_not_supported, + } + } + + return CrossTarget{ + .cpu_arch = asked_arch, + .cpu_model = switch (common.cpu.arch) { + .x86 => .determined_by_cpu_arch, + .x86_64 => if (execution_mode == .privileged) .determined_by_cpu_arch else + // zig fmt off + .determined_by_cpu_arch, + // .determined_by_cpu_arch, + // TODO: this causes some problems: https://github.com/ziglang/zig/issues/15524 + //.{ .explicit = &common.Target.x86.cpu.x86_64_v3 }, + else => .determined_by_cpu_arch, + }, + .os_tag = .freestanding, + .abi = .none, + .cpu_features_add = enabled_features, + .cpu_features_sub = disabled_features, + }; +} diff --git a/config/default.json b/config/default.json new file mode 100644 index 0000000..12994fc --- /dev/null +++ b/config/default.json @@ -0,0 +1,9 @@ +{ + "architecture": "x86_64", + "bootloader": "rise", + "boot_protocol": "bios", + "execution_environment": "qemu", + "optimize_mode": "Debug", + "execution_type": "emulated", + "executable_kind": "exe" +} diff --git a/config/gdb_script b/config/gdb_script new file mode 100644 index 0000000..ae307e5 --- /dev/null +++ b/config/gdb_script @@ -0,0 +1 @@ +set can-use-hw-watchpoints 1 diff --git a/config/image_config.json b/config/image_config.json new file mode 100644 index 0000000..bb3aef3 --- /dev/null +++ b/config/image_config.json @@ -0,0 +1,11 @@ +{ + "sector_count": 131072, + "sector_size": 512, + "image_name": "rise", + "partition_table": "gpt", + "partition": { + "name": "ESP", + "filesystem": "fat32", + "first_lba": 2048 + } +} diff --git a/config/qemu.json b/config/qemu.json new file mode 100644 index 0000000..7dac633 --- /dev/null +++ b/config/qemu.json @@ -0,0 +1,31 @@ +{ + "memory": { + "amount": "2048", + "unit": "megabyte" + }, + "virtualize": false, + "vga": "std", + "smp": 2, + "debugcon": "stdio", + "log": { + "file": null, + "guest_errors": true, + "assembly": false, + "interrupts": true + }, + "trace": [ + "nvme", + "pci", + "ide", + "ata", + "ahci", + "sata", + "apic_report_irq_delivered", + "apic_reset_irq_delivered", + "apic_get_irq_delivered", + "apic_local_deliver", + "apic_deliver_irq", + "apic_mem_readl", + "apic_mem_writel" + ] +} diff --git a/resources/FiraSans-Regular.otf b/resources/FiraSans-Regular.otf new file mode 100644 index 0000000..98ef98c Binary files /dev/null and b/resources/FiraSans-Regular.otf differ diff --git a/resources/zap-light16.psf b/resources/zap-light16.psf new file mode 100644 index 0000000..047bd1b Binary files /dev/null and b/resources/zap-light16.psf differ diff --git a/src/bootloader.zig b/src/bootloader.zig new file mode 100644 index 0000000..ea7e009 --- /dev/null +++ b/src/bootloader.zig @@ -0,0 +1,796 @@ +const bootloader = @This(); + +pub const arch = @import("bootloader/arch.zig"); + +const lib = @import("lib"); +const Allocator = lib.Allocator; +const assert = lib.assert; +//const Allocator = lib.Allocator; +pub const Protocol = lib.Bootloader.Protocol; + +const privileged = @import("privileged"); +const ACPI = privileged.ACPI; +const CPUPageTables = privileged.arch.CPUPageTables; +const PageAllocator = privileged.PageAllocator; +const PhysicalAddress = lib.PhysicalAddress; +const VirtualAddress = lib.VirtualAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +pub const paging = privileged.arch.paging; + +pub const Version = extern struct { + patch: u8, + minor: u16, + major: u8, +}; + +pub const CompactDate = packed struct(u16) { + year: u7, + month: u4, + day: u5, +}; + +const file_alignment = lib.arch.valid_page_sizes[0]; +const last_struct_offset = @offsetOf(Information, "slices"); + +pub const Information = extern struct { + entry_point: u64 align(8), + higher_half: u64 align(8), + total_size: u32, + last_struct_offset: u32 = last_struct_offset, + version: Version, + protocol: lib.Bootloader.Protocol, + bootloader: lib.Bootloader, + stage: Stage, + configuration: packed struct(u32) { + memory_map_diff: u8, + reserved: u24 = 0, + }, + cpu_driver_mappings: CPUDriverMappings, + framebuffer: Framebuffer, + //draw_context: DrawContext, + //font: Font, + smp: SMP.Information, + architecture: Architecture, + cpu_page_tables: CPUPageTables, + slices: lib.EnumStruct(Slice.Name, Slice), + + pub const Architecture = switch (lib.cpu.arch) { + .x86, .x86_64 => extern struct { + rsdp_address: u64, + }, + .aarch64 => extern struct { + foo: u64 = 0, + }, + .riscv64 => extern struct { + foo: u64 = 0, + }, + else => @compileError("Architecture not supported"), + }; + + pub const Slice = extern struct { + offset: u32 = 0, + size: u32 = 0, + len: u32 = 0, + alignment: u32 = 1, + + pub const Name = enum { + bootloader_information, // The main struct + memory_map_entries, + page_counters, + smps, + file_list, + bundle, + }; + + pub const count = lib.enumCount(Name); + + pub const TypeMap = blk: { + var arr: [Slice.count]type = undefined; + arr[@intFromEnum(Slice.Name.bootloader_information)] = Information; + arr[@intFromEnum(Slice.Name.bundle)] = u8; + arr[@intFromEnum(Slice.Name.file_list)] = u8; + arr[@intFromEnum(Slice.Name.memory_map_entries)] = MemoryMapEntry; + arr[@intFromEnum(Slice.Name.page_counters)] = u32; + arr[@intFromEnum(Slice.Name.smps)] = SMP; + break :blk arr; + }; + + pub inline fn dereference(slice: Slice, comptime slice_name: Slice.Name, bootloader_information: *const Information) []Slice.TypeMap[@intFromEnum(slice_name)] { + const Type = Slice.TypeMap[@intFromEnum(slice_name)]; + const address = @intFromPtr(bootloader_information) + slice.offset; + return @as([*]Type, @ptrFromInt(address))[0..slice.len]; + } + }; + + pub const SMP = extern struct { + acpi_id: u32, + lapic_id: u32, + entry_point: u64, + argument: u64, + + pub const Information = switch (lib.cpu.arch) { + .x86, .x86_64 => extern struct { + cpu_count: u32, + bsp_lapic_id: u32, + }, + .aarch64 => extern struct { + cpu_count: u32, + }, + .riscv64 => extern struct { + cpu_count: u32, + }, + else => @compileError("Architecture not supported"), + }; + + pub const Trampoline = extern struct { + comptime { + assert(lib.cpu.arch == .x86 or lib.cpu.arch == .x86_64); + } + + pub const Argument = switch (lib.cpu.arch) { + .x86, .x86_64 => extern struct { + hhdm: u64 align(8), + cr3: u32, + reserved: u16 = 0, + gdt_descriptor: arch.x86_64.GDT.Descriptor, + gdt: arch.x86_64.GDT, + + comptime { + assert(@sizeOf(Argument) == 24 + @sizeOf(arch.x86_64.GDT)); + } + }, + else => {}, + }; + }; + }; + + fn initializeMemoryMap(bootloader_information: *bootloader.Information, init: anytype) !usize { + try init.deinitializeMemoryMap(); + + const memory_map_entries = bootloader_information.getSlice(.memory_map_entries); + var entry_index: usize = 0; + while (try init.memory_map.next()) |entry| : (entry_index += 1) { + memory_map_entries[entry_index] = entry; + } + + return entry_index; + } + + pub fn initialize(initialization: anytype, comptime bootloader_tag: lib.Bootloader, comptime protocol: Protocol) !noreturn { + assert(@typeInfo(@TypeOf(initialization)) == .Pointer); + assert(initialization.early_initialized); + lib.log.info("Booting with bootloader {s} and boot protocol {s}", .{ @tagName(bootloader_tag), @tagName(protocol) }); + + assert(initialization.framebuffer_initialized); + assert(initialization.memory_map_initialized); + assert(initialization.filesystem_initialized); + + const sector_size = initialization.filesystem.getSectorSize(); + + const file_list_file_size = try initialization.filesystem.getFileSize("/files"); + const file_list_peek = try initialization.filesystem.sneakFile("/files", file_list_file_size); + assert(file_list_peek.len > 0); + var stream = lib.fixedBufferStream(file_list_peek); + const file_list_reader = stream.reader(); + const bundle_uncompressed_size = try file_list_reader.readIntLittle(u32); + assert(bundle_uncompressed_size > 0); + const bundle_compressed_size = try file_list_reader.readIntLittle(u32); + assert(bundle_compressed_size > 0); + const bundle_file_count = try file_list_reader.readIntLittle(u32); + assert(bundle_file_count > 0); + + const decompressor_state_allocation_size = 300 * lib.kb; + + const memory_map_entry_count = initialization.memory_map.getEntryCount(); + const cpu_count = try initialization.getCPUCount(); + + const length_size_tuples = bootloader.LengthSizeTuples.new(.{ + .bootloader_information = .{ + .length = 1, + .alignment = @alignOf(bootloader.Information), + }, + .memory_map_entries = .{ + .length = memory_map_entry_count, + .alignment = @alignOf(bootloader.MemoryMapEntry), + }, + .smps = .{ + .length = cpu_count, + .alignment = @max(@sizeOf(u64), @alignOf(bootloader.Information.SMP.Information)), + }, + .page_counters = .{ + .length = memory_map_entry_count, + .alignment = @alignOf(u32), + }, + .file_list = .{ + .length = file_list_file_size, + .alignment = 1, + }, + .bundle = .{ + .length = bundle_uncompressed_size, + .alignment = file_alignment, + }, + }); + + const extra_sizes = [2]usize{ decompressor_state_allocation_size, bundle_compressed_size }; + const aligned_extra_sizes = blk: { + var result: [extra_sizes.len]usize = undefined; + inline for (extra_sizes, &result) |extra_size, *element| { + element.* = lib.alignForward(usize, extra_size, sector_size); + } + + break :blk result; + }; + + const total_aligned_extra_size = blk: { + var result: usize = 0; + inline for (aligned_extra_sizes) |size| { + result += size; + } + + break :blk result; + }; + + var early_mmap_index: usize = 0; + const length_size_tuples_size = length_size_tuples.getAlignedTotalSize(); + const total_allocation_size = length_size_tuples_size + total_aligned_extra_size; + const total_allocation = blk: while (try initialization.memory_map.next()) |entry| : (early_mmap_index += 1) { + if (entry.type == .usable) { + if (entry.region.size >= total_allocation_size) { + break :blk .{ + .index = early_mmap_index, + .region = entry.region, + }; + } + } + } else { + return error.OutOfMemory; + }; + + const bootloader_information = total_allocation.region.address.toIdentityMappedVirtualAddress().access(*bootloader.Information); + bootloader_information.* = bootloader.Information{ + .protocol = protocol, + .bootloader = bootloader_tag, + .version = .{ .major = 0, .minor = 1, .patch = 0 }, + .total_size = length_size_tuples.total_size, + .entry_point = 0, + .higher_half = lib.config.cpu_driver_higher_half_address, + .stage = .early, + .configuration = .{ + .memory_map_diff = 0, + }, + .framebuffer = initialization.framebuffer, + // .draw_context = .{}, + // .font = undefined, + .cpu_driver_mappings = .{}, + .cpu_page_tables = undefined, + .smp = switch (lib.cpu.arch) { + .x86, .x86_64 => .{ + .cpu_count = cpu_count, + .bsp_lapic_id = @as(*volatile u32, @ptrFromInt(0x0FEE00020)).*, + }, + else => @compileError("Architecture not supported"), + }, + .slices = length_size_tuples.createSlices(), + .architecture = switch (lib.cpu.arch) { + .x86, .x86_64 => .{ + .rsdp_address = initialization.getRSDPAddress(), + }, + else => @compileError("Architecture not supported"), + }, + }; + + const page_counters = bootloader_information.getSlice(.page_counters); + @memset(page_counters, 0); + + // Make sure pages are allocated to host the bootloader information and fetch memory entries from firmware (only non-UEFI) + if (bootloader_tag != .rise or protocol != .uefi) { + page_counters[total_allocation.index] = bootloader_information.getAlignedTotalSize() >> lib.arch.page_shifter(lib.arch.valid_page_sizes[0]); + + const new_memory_map_entry_count = try bootloader_information.initializeMemoryMap(initialization); + + if (new_memory_map_entry_count != memory_map_entry_count) @panic("Memory map entry count mismatch"); + } + + const file_list = bootloader_information.getSlice(.file_list); + if (file_list_peek.len == file_list_file_size) { + lib.memcpy(file_list, file_list_peek); + } else { + @panic("Not able to fit in the cache"); + } + + try initialization.filesystem.deinitialize(); + + const bootloader_information_total_aligned_size = bootloader_information.getAlignedTotalSize(); + const extra_allocation_region = total_allocation.region.offset(bootloader_information_total_aligned_size).shrinked(total_aligned_extra_size); + const decompressor_state_buffer = extra_allocation_region.toIdentityMappedVirtualAddress().access(u8)[0..decompressor_state_allocation_size]; + const compressed_bundle_buffer = extra_allocation_region.offset(decompressor_state_allocation_size).toIdentityMappedVirtualAddress().access(u8)[0..lib.alignForward(usize, bundle_compressed_size, sector_size)]; + const compressed_bundle = try initialization.filesystem.readFile("/bundle", compressed_bundle_buffer); + assert(compressed_bundle.len > 0); + + if (bootloader_tag == .rise and protocol == .uefi) { + // Check if the memory map entry count matches here is not useful because probably it's going to be less as exiting boot services seems + // like making some deallocations + const new_memory_map_entry_count = @as(u32, @intCast(try bootloader_information.initializeMemoryMap(initialization))); + if (new_memory_map_entry_count > memory_map_entry_count) { + return Error.unexpected_memory_map_entry_count; + } + bootloader_information.configuration.memory_map_diff = @as(u8, @intCast(memory_map_entry_count - new_memory_map_entry_count)); + } + + // Check if the host entry still corresponds to the same index + const memory_map_entries = bootloader_information.getMemoryMapEntries(); + const expected_host_region = memory_map_entries[total_allocation.index].region; + assert(expected_host_region.address.value() == total_allocation.region.address.value()); + assert(expected_host_region.size == total_allocation.region.size); + + var compressed_bundle_stream = lib.fixedBufferStream(compressed_bundle); + const decompressed_bundle = bootloader_information.getSlice(.bundle); + assert(decompressed_bundle.len != 0); + var decompressor_state_allocator = lib.FixedBufferAllocator.init(decompressor_state_buffer); + var decompressor = try lib.deflate.decompressor(decompressor_state_allocator.allocator(), compressed_bundle_stream.reader(), null); + const bytes = try decompressor.reader().readAll(decompressed_bundle); + assert(bytes == bundle_uncompressed_size); + if (decompressor.close()) |err| { + return err; + } + + // Empty region as this is no longer needed. Region was not marked as allocated so no need + // to unmark it + const free_slice = extra_allocation_region.toIdentityMappedVirtualAddress().access(u8); + @memset(free_slice, 0); + + const page_allocator = PageAllocator{ + .allocate = Information.callbackAllocatePages, + .context = bootloader_information, + .context_type = .bootloader, + }; + bootloader_information.cpu_page_tables = try CPUPageTables.initialize(page_allocator); + + const minimal_paging = privileged.arch.paging.Specific.fromPageTables(bootloader_information.cpu_page_tables); + + const cpu_file_descriptor = try bootloader_information.getFileDescriptor("cpu_driver"); + var elf_parser = try lib.ELF(64).Parser.init(cpu_file_descriptor.content); + const program_headers = elf_parser.getProgramHeaders(); + + for (program_headers) |*ph| { + switch (ph.type) { + .load => { + if (ph.size_in_memory == 0) continue; + + if (!ph.flags.readable) { + @panic("ELF program segment is marked as non-readable"); + } + + if (ph.size_in_file != ph.size_in_memory) { + @panic("ELF program segment file size is smaller than memory size"); + } + + const aligned_size = lib.alignForward(u64, ph.size_in_memory, lib.arch.valid_page_sizes[0]); + const physical_allocation = try bootloader_information.allocatePages(aligned_size, lib.arch.valid_page_sizes[0], .{}); + const physical_address = physical_allocation.address; + const virtual_address = VirtualAddress.new(ph.virtual_address); + const flags = Mapping.Flags{ .write = ph.flags.writable, .execute = ph.flags.executable }; + + switch (ph.flags.executable) { + true => switch (ph.flags.writable) { + true => @panic("Text section is not supposed to be writable"), + false => { + bootloader_information.cpu_driver_mappings.text = .{ + .physical = physical_address, + .virtual = virtual_address, + .size = ph.size_in_memory, + .flags = flags, + }; + }, + }, + false => switch (ph.flags.writable) { + true => bootloader_information.cpu_driver_mappings.data = .{ + .physical = physical_address, + .virtual = virtual_address, + .size = ph.size_in_memory, + .flags = flags, + }, + false => bootloader_information.cpu_driver_mappings.rodata = .{ + .physical = physical_address, + .virtual = virtual_address, + .size = ph.size_in_memory, + .flags = flags, + }, + }, + } + + // log.debug("Started mapping kernel section", .{}); + try bootloader_information.cpu_page_tables.map(physical_address, virtual_address, aligned_size, flags); + // log.debug("Ended mapping kernel section", .{}); + + const dst_slice = physical_address.toIdentityMappedVirtualAddress().access([*]u8)[0..lib.safeArchitectureCast(ph.size_in_memory)]; + const src_slice = cpu_file_descriptor.content[lib.safeArchitectureCast(ph.offset)..][0..lib.safeArchitectureCast(ph.size_in_file)]; + // log.debug("Src slice: [0x{x}, 0x{x}]. Dst slice: [0x{x}, 0x{x}]", .{ @ptrToInt(src_slice.ptr), @ptrToInt(src_slice.ptr) + src_slice.len, @ptrToInt(dst_slice.ptr), @ptrToInt(dst_slice.ptr) + dst_slice.len }); + if (!(dst_slice.len >= src_slice.len)) { + @panic("bios: segment allocated memory must be equal or greater than especified"); + } + + lib.memcpy(dst_slice, src_slice); + }, + else => { + //log.warn("Unhandled PH {s}", .{@tagName(ph.type)}); + }, + } + } + + //for (bootloader_information.getMemoryMapEntries()[0..memory_map_entry_count]) |entry| { + for (bootloader_information.getMemoryMapEntries()) |entry| { + if (entry.type == .usable) { + try minimal_paging.map(entry.region.address, entry.region.address.toHigherHalfVirtualAddress(), lib.alignForward(u64, entry.region.size, lib.arch.valid_page_sizes[0]), .{ .write = true, .execute = false }, page_allocator); + } + } + + try minimal_paging.map(total_allocation.region.address, total_allocation.region.address.toIdentityMappedVirtualAddress(), bootloader_information.getAlignedTotalSize(), .{ .write = true, .execute = false }, page_allocator); + try initialization.ensureLoaderIsMapped(minimal_paging, page_allocator, bootloader_information); + + const framebuffer_physical_address = PhysicalAddress.new(if (bootloader_information.bootloader == .limine) bootloader_information.framebuffer.address - lib.config.cpu_driver_higher_half_address else bootloader_information.framebuffer.address); + try minimal_paging.map(framebuffer_physical_address, framebuffer_physical_address.toHigherHalfVirtualAddress(), lib.alignForward(u64, bootloader_information.framebuffer.getSize(), lib.arch.valid_page_sizes[0]), .{ .write = true, .execute = false }, page_allocator); + bootloader_information.framebuffer.address = framebuffer_physical_address.toHigherHalfVirtualAddress().value(); + + try initialization.ensureStackIsMapped(minimal_paging, page_allocator); + + switch (lib.cpu.arch) { + .x86, .x86_64 => { + const apic_base_physical_address = privileged.arch.x86_64.registers.IA32_APIC_BASE.read().getAddress(); + try minimal_paging.map(apic_base_physical_address, apic_base_physical_address.toHigherHalfVirtualAddress(), lib.arch.valid_page_sizes[0], .{ + .write = true, + .cache_disable = true, + .global = true, + }, page_allocator); + }, + else => @compileError("Not supported"), + } + + // bootloader_information.initializeSMP(madt); + + bootloader_information.entry_point = elf_parser.getEntryPoint(); + + if (bootloader_information.entry_point != 0) { + lib.log.info("Jumping to kernel...", .{}); + bootloader.arch.x86_64.jumpToKernel(bootloader_information, minimal_paging); + } else return Error.no_entry_point_found; + } + + pub const Error = error{ + file_not_found, + filesystem_initialization_failed, + unexpected_memory_map_entry_count, + no_entry_point_found, + }; + + pub fn getAlignedTotalSize(information: *Information) u32 { + if (information.total_size == 0) @panic("Information.getAlignedTotalSize"); + return lib.alignForward(u32, information.total_size, lib.arch.valid_page_sizes[0]); + } + + pub inline fn getSlice(information: *const Information, comptime offset_name: Slice.Name) []Slice.TypeMap[@intFromEnum(offset_name)] { + const slice_offset = information.slices.array.values[@intFromEnum(offset_name)]; + return slice_offset.dereference(offset_name, information); + } + + pub fn getMemoryMapEntryCount(information: *Information) usize { + return information.getSlice(.memory_map_entries).len - information.configuration.memory_map_diff; + } + + pub fn getMemoryMapEntries(information: *Information) []MemoryMapEntry { + return information.getSlice(.memory_map_entries)[0..information.getMemoryMapEntryCount()]; + } + + pub fn getPageCounters(information: *Information) []u32 { + return information.getSlice(.page_counters)[0..information.getMemoryMapEntryCount()]; + } + + pub const IntegrityError = error{ + bad_slice_alignment, + bad_slice_size, + bad_total_size, + bad_struct_offset, + }; + + pub fn checkIntegrity(information: *const Information) !void { + if (information.last_struct_offset != last_struct_offset) { + return IntegrityError.bad_struct_offset; + } + + const original_total_size = information.total_size; + var total_size: u32 = 0; + inline for (Information.Slice.TypeMap, 0..) |T, index| { + const slice = information.slices.array.values[index]; + + if (slice.alignment < @alignOf(T)) { + return IntegrityError.bad_slice_alignment; + } + + if (slice.len * @sizeOf(T) != slice.size) { + return IntegrityError.bad_slice_size; + } + + total_size = lib.alignForward(u32, total_size, slice.alignment); + total_size += lib.alignForward(u32, slice.size, slice.alignment); + } + + if (total_size != original_total_size) { + return IntegrityError.bad_total_size; + } + } + + pub fn allocatePages(bootloader_information: *Information, size: u64, alignment: u64, options: PageAllocator.AllocateOptions) Allocator.Allocate.Error!PhysicalMemoryRegion { + const allocation = blk: { + if (bootloader_information.stage != .cpu) { + if (size & lib.arch.page_mask(lib.arch.valid_page_sizes[0]) != 0) return Allocator.Allocate.Error.OutOfMemory; + if (alignment & lib.arch.page_mask(lib.arch.valid_page_sizes[0]) != 0) return Allocator.Allocate.Error.OutOfMemory; + + const four_kb_pages = @as(u32, @intCast(@divExact(size, lib.arch.valid_page_sizes[0]))); + + const entries = bootloader_information.getMemoryMapEntries(); + const page_counters = bootloader_information.getPageCounters(); + + for (entries, 0..) |entry, entry_index| { + const busy_size = @as(u64, page_counters[entry_index]) * lib.arch.valid_page_sizes[0]; + const size_left = entry.region.size - busy_size; + const target_address = entry.region.address.offset(busy_size); + + if (entry.type == .usable and target_address.value() <= lib.maxInt(usize) and size_left > size and entry.region.address.value() != 0) { + if (entry.region.address.isAligned(alignment)) { + const result = PhysicalMemoryRegion.new(.{ + .address = target_address, + .size = size, + }); + + @memset(@as([*]u8, @ptrFromInt(lib.safeArchitectureCast(result.address.value())))[0..lib.safeArchitectureCast(result.size)], 0); + + page_counters[entry_index] += four_kb_pages; + + break :blk result; + } + } + } + + if (options.space_waste_allowed_to_guarantee_alignment > 0) { + for (entries, 0..) |entry, entry_index| { + const busy_size = @as(u64, page_counters[entry_index]) * lib.arch.valid_page_sizes[0]; + const size_left = entry.region.size - busy_size; + const target_address = entry.region.address.offset(busy_size); + + if (entry.type == .usable and target_address.value() <= lib.maxInt(usize) and size_left > size and entry.region.address.value() != 0) { + const aligned_address = lib.alignForward(u64, target_address.value(), alignment); + const difference = aligned_address - target_address.value(); + const allowed_quota = alignment / options.space_waste_allowed_to_guarantee_alignment; + + if (aligned_address + size < entry.region.address.offset(entry.region.size).value() and difference <= allowed_quota) { + const result = PhysicalMemoryRegion.new(.{ + .address = PhysicalAddress.new(aligned_address), + .size = size, + }); + + @memset(@as([*]u8, @ptrFromInt(lib.safeArchitectureCast(result.address.value())))[0..lib.safeArchitectureCast(result.size)], 0); + page_counters[entry_index] += @as(u32, @intCast(difference + size)) >> lib.arch.page_shifter(lib.arch.valid_page_sizes[0]); + + break :blk result; + } + } + } + } + } + + return Allocator.Allocate.Error.OutOfMemory; + }; + + return allocation; + } + + pub fn callbackAllocatePages(context: ?*anyopaque, size: u64, alignment: u64, options: PageAllocator.AllocateOptions) Allocator.Allocate.Error!PhysicalMemoryRegion { + const bootloader_information = @as(*Information, @ptrCast(@alignCast(context))); + return try bootloader_information.allocatePages(size, alignment, options); + } + + pub fn heapAllocate(bootloader_information: *Information, size: u64, alignment: u64) !Allocator.Allocate.Result { + if (bootloader_information.stage != .cpu) { + for (&bootloader_information.heap.regions) |*region| { + if (region.size > size) { + const result = .{ + .address = region.address.value(), + .size = size, + }; + region.size -= size; + region.address.addOffset(size); + return result; + } + } + const size_to_page_allocate = lib.alignForward(u64, size, lib.arch.valid_page_sizes[0]); + for (&bootloader_information.heap.regions) |*region| { + if (region.size == 0) { + const allocated_region = try bootloader_information.page_allocator.allocateBytes(size_to_page_allocate, lib.arch.valid_page_sizes[0]); + region.* = .{ + .address = PhysicalAddress.new(allocated_region.address), + .size = allocated_region.size, + }; + const result = .{ + .address = region.address.value(), + .size = size, + }; + region.address.addOffset(size); + region.size -= size; + return result; + } + } + + _ = alignment; + } + + return Allocator.Allocate.Error.OutOfMemory; + } + + pub fn getFileDescriptor(bootloader_information: *bootloader.Information, wanted_file_name: []const u8) !FileDescriptor { + const file_list = bootloader_information.getSlice(.file_list); + + var index: usize = 0; + index += 2 * @sizeOf(u32); + const file_count = @as(*align(1) const u32, @ptrCast(&file_list[index])).*; + index += @sizeOf(u32); + var file_index: u32 = 0; + + while (file_index < file_count) : (file_index += 1) { + const file_name_len_offset = 2 * @sizeOf(u32); + const file_name_len = file_list[index + file_name_len_offset]; + const file_name_offset = file_name_len_offset + @sizeOf(u8); + const file_name = file_list[index + file_name_offset ..][0..file_name_len]; + + if (lib.equal(u8, wanted_file_name, file_name)) { + const file_offset = @as(*align(1) const u32, @ptrCast(&file_list[index + 0])).*; + const file_size = @as(*align(1) const u32, @ptrCast(&file_list[index + @sizeOf(u32)])).*; + const bundle = bootloader_information.getSlice(.bundle); + const file_content = bundle[file_offset..][0..file_size]; + + return FileDescriptor{ + .name = file_name, + .content = file_content, + }; + } + + const offset_to_add = file_name_offset + file_name.len; + index += offset_to_add; + } + + return Error.file_not_found; + } +}; + +pub const FileDescriptor = struct { + name: []const u8, + content: []const u8, +}; + +pub const CPUDriverMappings = extern struct { + text: Mapping = .{}, + data: Mapping = .{}, + rodata: Mapping = .{}, +}; + +const Mapping = privileged.Mapping; + +pub const MemoryMapEntry = extern struct { + region: PhysicalMemoryRegion align(8), + type: Type align(8), + + const Type = enum(u64) { + usable = 0, + reserved = 1, + bad_memory = 2, + }; + + pub fn getFreeRegion(mmap_entry: MemoryMapEntry, page_counter: u32) PhysicalMemoryRegion { + return mmap_entry.region.offset(page_counter << lib.arch.page_shifter(lib.arch.valid_page_sizes[0])); + } + + comptime { + assert(@sizeOf(MemoryMapEntry) == @sizeOf(u64) * 3); + } +}; + +pub const Framebuffer = extern struct { + address: u64, + pitch: u32, + width: u32, + height: u32, + bpp: u16, + red_mask: ColorMask, + green_mask: ColorMask, + blue_mask: ColorMask, + memory_model: u8, + reserved: u8 = 0, + + pub const ColorMask = extern struct { + size: u8 = 0, + shift: u8 = 0, + }; + + pub const VideoMode = extern struct { + foo: u32 = 0, + }; + + pub inline fn getSize(framebuffer: Framebuffer) u32 { + return framebuffer.pitch * framebuffer.height; + } +}; + +pub const LengthSizeTuples = extern struct { + tuples: Tuples, + total_size: u32 = 0, + + const Tuples = lib.EnumStruct(Information.Slice.Name, Tuple); + + const count = Information.Slice.count; + + pub const Tuple = extern struct { + length: u32, + alignment: u32, + size: u32 = 0, + reserved: u32 = 0, + }; + + pub fn new(fields: Tuples.Struct) LengthSizeTuples { + var tuples = LengthSizeTuples{ + .tuples = .{ + .fields = fields, + }, + }; + + var total_size: u32 = 0; + + inline for (Information.Slice.TypeMap, 0..) |T, index| { + const tuple = &tuples.tuples.array.values[index]; + const size = tuple.length * @sizeOf(T); + tuple.alignment = if (tuple.alignment < @alignOf(T)) @alignOf(T) else tuple.alignment; + total_size = lib.alignForward(u32, total_size, tuple.alignment); + total_size += lib.alignForward(u32, size, tuple.alignment); + tuple.size = size; + } + + tuples.total_size = total_size; + + return tuples; + } + + pub fn createSlices(tuples: LengthSizeTuples) lib.EnumStruct(Information.Slice.Name, Information.Slice) { + var slices = lib.zeroes(lib.EnumStruct(Information.Slice.Name, Information.Slice)); + var allocated_size: u32 = 0; + + for (&slices.array.values, 0..) |*slice, index| { + const tuple = tuples.tuples.array.values[index]; + const length = tuple.length; + const size = lib.alignForward(u32, tuple.size, tuple.alignment); + + allocated_size = lib.alignForward(u32, allocated_size, tuple.alignment); + slice.* = .{ + .offset = allocated_size, + .len = length, + .size = tuple.size, + .alignment = tuple.alignment, + }; + + allocated_size += size; + } + + if (allocated_size != tuples.total_size) @panic("Extra allocation size must match bootloader allocated extra size"); + + return slices; + } + + pub fn getAlignedTotalSize(tuples: LengthSizeTuples) u32 { + if (tuples.total_size == 0) @panic("LengthSizeTuples.getAlignedTotalSize"); + return lib.alignForward(u32, tuples.total_size, lib.arch.valid_page_sizes[0]); + } +}; + +pub const Stage = enum(u32) { + early = 0, + only_graphics = 1, + trampoline = 2, + cpu = 3, +}; diff --git a/src/bootloader/arch.zig b/src/bootloader/arch.zig new file mode 100644 index 0000000..a0630e2 --- /dev/null +++ b/src/bootloader/arch.zig @@ -0,0 +1,2 @@ +pub const x86 = @import("arch/x86.zig"); +pub const x86_64 = @import("arch/x86_64.zig"); diff --git a/src/bootloader/arch/x86.zig b/src/bootloader/arch/x86.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/bootloader/arch/x86/64/smp_trampoline.S b/src/bootloader/arch/x86/64/smp_trampoline.S new file mode 100644 index 0000000..d5cc010 --- /dev/null +++ b/src/bootloader/arch/x86/64/smp_trampoline.S @@ -0,0 +1,135 @@ +.section .smp_trampoline +.align 0x1000 + +.global smp_trampoline +.global smp_trampoline_arg_start +.global smp_trampoline_arg_end +.global smp_gdt_descriptor +.global smp_core_booted +.global smp_trampoline_end + +.code16 +smp_trampoline: +cli +cld + +mov %cs, %ebx +shl $0x4, %ebx + +lidtl %cs:(invalid_idt - smp_trampoline) +lgdtl %cs:(smp_gdt_descriptor - smp_trampoline) +leal (protected_mode - smp_trampoline)(%ebx), %eax +movl %eax, %cs:(far_jump_offset - smp_trampoline) +movl $0x11, %eax +movl %eax, %cr0 +mov %cs:(gdt32_ds - smp_trampoline), %eax +ljmpl *%cs:(far_jump - smp_trampoline) + +far_jump: +far_jump_offset: .long 0 +gdt32_cs: .long 0x18 +gdt32_ds: .long 0x20 + +.code32 +protected_mode: +movw %ax, %ds +movw %ax, %es +movw %ax, %fs +movw %ax, %gs +movw %ax, %ss +xorl %eax, %eax +lldtw %ax +xorl %eax, %eax +movl %eax, %cr4 + +// TODO: Change +// always no x2apic +leal (temporal_stack_top - smp_trampoline)(%ebx), %esp + +// Long mode activation + +// In CR4 +mov %cr4, %eax +bts $0x5, %eax +mov %eax, %cr4 + +// In EFER: +mov $0xc0000080, %ecx +mov $0x900, %eax +xor %edx, %edx +wrmsr + +// Setup CR3 +mov (arg_cr3 - smp_trampoline)(%ebx), %eax +mov %eax, %cr3 + +mov %cr0, %eax +bts $31, %eax +mov %eax, %cr0 + +leal (bits64 - smp_trampoline)(%ebx), %eax +push $0x28 +push %eax +lretl + +.code64 +bits64: +mov $0x30, %rax +mov %rax, %ds +mov %rax, %es +mov %rax, %fs +mov %rax, %gs +mov %rax, %ss + +mov %ebx, %ebx + +// Enable NXE +mov $0xc0000080, %ecx +rdmsr +bts $11, %eax +wrmsr + +// Enable write protect +mov %cr0, %rax +bts $16, %rax +mov %rax, %cr0 + +// TODO: before park +mov $1, %al +lock xchgb (smp_core_booted - smp_trampoline)(%rbx), %al +xor %rax, %rax +cli +hlt + +.align 16 +temporal_stack: +.fill 128, 1, 0 +temporal_stack_top: + +invalid_idt: +.quad 0 +.quad 0 + +.align 16 +smp_trampoline_arg_start: +arg_hhdm: +.quad 0 +arg_cr3: +.long 0 +reserved: .word 0 +smp_gdt_descriptor: +.limit: .word 0 +.address: .quad 0 +smp_gdt: +smp_gdt_null: .quad 0 +smp_gdt_code_16: .quad 0 +smp_gdt_data_16: .quad 0 +smp_gdt_code_32: .quad 0 +smp_gdt_data_32: .quad 0 +smp_gdt_code_64: .quad 0 +smp_gdt_data_64: .quad 0 +smp_trampoline_arg_end: + +smp_core_booted: .byte 0 + +smp_trampoline_end: diff --git a/src/bootloader/arch/x86_64.zig b/src/bootloader/arch/x86_64.zig new file mode 100644 index 0000000..bbe6e20 --- /dev/null +++ b/src/bootloader/arch/x86_64.zig @@ -0,0 +1,150 @@ +const lib = @import("lib"); +const assert = lib.assert; +const bootloader = @import("bootloader"); +const privileged = @import("privileged"); +const paging = privileged.arch.paging; +const x86_64 = privileged.arch.x86_64; + +pub const GDT = extern struct { + null_entry: Entry = Entry.null_entry, + // 0x08 + code_16: Entry = Entry.code_16, + // 0x10 + data_16: Entry = Entry.data_16, + // 0x18 + code_32: Entry = Entry.code_32, + // 0x20 + data_32: Entry = Entry.data_32, + // 0x28 + code_64: Entry = Entry.code_64, + // 0x30 + data_64: Entry = Entry.data_64, + + pub const Entry = x86_64.GDT.Entry; + pub const Descriptor = x86_64.GDT.Descriptor; + + pub fn getDescriptor(gdt: *const GDT) GDT.Descriptor { + return .{ + .limit = @sizeOf(GDT) - 1, + .address = @intFromPtr(gdt), + }; + } +}; + +const code_segment_selector = @offsetOf(GDT, "code_64"); +const data_segment_selector = @offsetOf(GDT, "data_64"); +const entry_point_offset = @offsetOf(bootloader.Information, "entry_point"); +const higher_half_offset = @offsetOf(bootloader.Information, "higher_half"); + +pub fn jumpToKernel(bootloader_information_arg: *bootloader.Information, minimal_paging: paging.Specific) noreturn { + if (@intFromPtr(bootloader_information_arg) >= lib.config.cpu_driver_higher_half_address) { + // Error + privileged.arch.stopCPU(); + } + + // Enable long mode and certain important bits + var efer = privileged.arch.x86_64.registers.IA32_EFER.read(); + efer.LME = true; + efer.NXE = true; + efer.SCE = true; + efer.write(); + + minimal_paging.cr3.write(); + + if (lib.cpu.arch == .x86) { + // Enable PAE + var cr4 = asm volatile ( + \\mov %cr4, %[cr4] + : [cr4] "=r" (-> u32), + : + : "memory" + ); + cr4 |= (1 << 5); + asm volatile ( + \\mov %[cr4], %cr4 + : + : [cr4] "r" (cr4), + : "memory" + ); + + // Enable paging + var cr0 = asm volatile ( + \\mov %cr0, %[cr0] + : [cr0] "=r" (-> u32), + : + : "memory" + ); + cr0 |= (1 << 31); + asm volatile ( + \\mov %[cr0], %cr0 + : + : [cr0] "r" (cr0), + : "memory" + ); + + asm volatile ( + \\jmp %[code_segment_selector], $bits64 + \\.code64 + \\bits64: + \\mov %[data_segment_selector], %ds + \\mov %[data_segment_selector], %es + \\mov %[data_segment_selector], %fs + \\mov %[data_segment_selector], %gs + \\mov %[data_segment_selector], %ss + : + : [code_segment_selector] "i" (code_segment_selector), + [data_segment_selector] "r" (data_segment_selector), + : "memory" + ); + } + + switch (lib.cpu.arch) { + .x86_64 => { + const bootloader_information = @as(*bootloader.Information, @ptrFromInt(@intFromPtr(bootloader_information_arg) + lib.config.cpu_driver_higher_half_address)); + const entry_point = bootloader_information.entry_point; + asm volatile ( + \\.code64 + \\jmp *%[entry_point] + \\cli + \\hlt + : + : [entry_point] "r" (entry_point), + [bootloader_information] "{rdi}" (bootloader_information), + : "memory" + ); + }, + .x86 => asm volatile ( + \\mov %edi, %eax + \\add %[higher_half_offset], %eax + \\.byte 0x48 + \\add (%eax), %edi + \\.byte 0x48 + \\mov %edi, %eax + \\.byte 0x48 + \\mov %edi, %eax + \\add %[entry_point_offset], %eax + \\.byte 0x48 + \\mov (%eax), %eax + \\jmp *%eax + \\cli + \\hlt + : + : [bootloader_information] "{edi}" (bootloader_information_arg), + [higher_half_offset] "i" (higher_half_offset), + [slice_offset] "i" (@offsetOf(bootloader.Information.Slice, "offset")), + [slice_size_slide] "i" (@offsetOf(bootloader.Information.Slice, "size") - @offsetOf(bootloader.Information.Slice, "offset")), + [entry_point_offset] "i" (entry_point_offset), + : "memory" + ), + else => @compileError("Architecture not supported"), + } + + unreachable; +} + +pub inline fn delay(cycles: u64) void { + const next_stop = lib.arch.x86_64.readTimestamp() + cycles; + while (lib.arch.x86_64.readTimestamp() < next_stop) {} +} + +pub extern fn smp_trampoline() align(0x1000) callconv(.Naked) noreturn; diff --git a/src/bootloader/bios.zig b/src/bootloader/bios.zig new file mode 100644 index 0000000..964f2d6 --- /dev/null +++ b/src/bootloader/bios.zig @@ -0,0 +1,649 @@ +const lib = @import("lib"); +const assert = lib.assert; +const bootloader = @import("bootloader"); + +const privileged = @import("privileged"); +const ACPI = privileged.ACPI; +const x86_64 = privileged.arch.x86_64; +const PhysicalAddress = lib.PhysicalAddress; +const VirtualAddress = lib.VirtualAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const VirtualMemoryRegion = lib.VirtualMemoryRegion; + +inline fn segment(value: u32) u16 { + return @as(u16, @intCast(value & 0xffff0)) >> 4; +} + +inline fn offset(value: u32) u16 { + return @as(u16, @truncate(value & 0xf >> 0)); +} + +pub const loader_stack_top: u32 = 0x20000; +pub const stack_top: u16 = mbr_offset; +pub const mbr_offset: u16 = 0xfe00; +pub const stack_size: u16 = 0x2000; + +pub const loader_start = 0x1000; + +pub const Disk = extern struct { + disk: lib.Disk = .{ + .disk_size = lib.default_disk_size, + .sector_size = lib.default_sector_size, + .callbacks = .{ + .read = read, + .write = write, + .readCache = readCache, + }, + .type = .bios, + .cache_size = buffer_len, + }, + + var buffer = [1]u8{0} ** buffer_len; + const buffer_len = lib.default_sector_size * 0x10; + + pub fn read(disk: *lib.Disk, sector_count: u64, sector_offset: u64, maybe_provided_buffer: ?[]u8) lib.Disk.ReadError!lib.Disk.ReadResult { + if (sector_count > lib.maxInt(u16)) @panic("too many sectors"); + + const buffer_sectors = @divExact(buffer.len, disk.sector_size); + if (maybe_provided_buffer == null) { + if (sector_count > buffer_sectors) { + return error.read_error; + } + } + + const disk_buffer_address = @intFromPtr(&buffer); + if (disk_buffer_address > lib.maxInt(u16)) @panic("address too high"); + + var sectors_left = sector_count; + while (sectors_left > 0) { + const sectors_to_read = @as(u16, @intCast(@min(sectors_left, buffer_sectors))); + + const lba_offset = sector_count - sectors_left; + sectors_left -= sectors_to_read; + const lba = sector_offset + lba_offset; + + const dap = DAP{ + .sector_count = sectors_to_read, + .offset = @as(u16, @intCast(disk_buffer_address)), + .segment = 0, + .lba = lba, + }; + lib.log.debug("DAP: {}", .{dap}); + + const dap_address = @intFromPtr(&dap); + lib.log.debug("DAP address: 0x{x}", .{dap_address}); + const dap_offset = offset(dap_address); + const dap_segment = segment(dap_address); + var registers = Registers{ + .eax = 0x4200, + .edx = 0x80, + .esi = dap_offset, + .ds = dap_segment, + }; + + lib.log.debug("Start int", .{}); + interrupt(0x13, ®isters, ®isters); + lib.log.debug("End int", .{}); + + if (registers.eflags.flags.carry_flag) return error.read_error; + + const provided_buffer_offset = lba_offset * disk.sector_size; + const bytes_to_copy = sectors_to_read * disk.sector_size; + const src_slice = buffer[0..bytes_to_copy]; + + if (maybe_provided_buffer) |provided_buffer| { + lib.log.debug("A", .{}); + const dst_slice = provided_buffer[@as(usize, @intCast(provided_buffer_offset))..][0..bytes_to_copy]; + + // TODO: report Zig that this codegen is so bad that we have to use rep movsb instead to make it go fast + // Tasks: + // - Find out the root issue: is it only soft float? is it 32-bit soft_float? is it 32-bit soft_float ReleaseSmall? + // - Report the issue with data to back the facts + const use_rep_movsb = true; + if (use_rep_movsb) { + lib.memcpy(dst_slice, src_slice); + const bytes_left = asm volatile ( + \\rep movsb + : [ret] "={ecx}" (-> usize), + : [dest] "{edi}" (dst_slice.ptr), + [src] "{esi}" (src_slice.ptr), + [len] "{ecx}" (src_slice.len), + ); + assert(bytes_left == 0); + } else { + @memcpy(dst_slice, src_slice); + } + } else { + lib.log.debug("B", .{}); + } + } + + const result = lib.Disk.ReadResult{ + .sector_count = sector_count, + .buffer = (maybe_provided_buffer orelse &buffer).ptr, + }; + + return result; + } + + pub fn readCache(disk: *lib.Disk, asked_sector_count: u64, sector_offset: u64) lib.Disk.ReadError!lib.Disk.ReadResult { + const max_sector_count = @divExact(disk.cache_size, disk.sector_size); + const sector_count = if (asked_sector_count > max_sector_count) max_sector_count else asked_sector_count; + return try read(disk, sector_count, sector_offset, null); + } + + pub fn write(disk: *lib.Disk, bytes: []const u8, sector_offset: u64, commit_memory_to_disk: bool) lib.Disk.WriteError!void { + _ = disk; + _ = bytes; + _ = sector_offset; + _ = commit_memory_to_disk; + return lib.Disk.WriteError.not_supported; + } +}; + +extern fn interrupt(number: u8, out_regs: *Registers, in_regs: *const Registers) linksection(".realmode") callconv(.C) void; + +const DAP = lib.PartitionTable.MBR.DAP; + +const Registers = extern struct { + gs: u16 = 0, + fs: u16 = 0, + es: u16 = 0, + ds: u16 = 0, + eflags: packed struct(u32) { + flags: packed struct(u16) { + carry_flag: bool = false, + reserved: u1 = 1, + parity_flag: bool = false, + reserved1: u1 = 0, + adjust_flag: bool = false, + reserved2: u1 = 0, + zero_flag: bool = false, + sign_flag: bool = false, + trap_flag: bool = false, + interrupt_enabled_flag: bool = false, + direction_flag: bool = false, + overflow_flag: bool = false, + io_privilege_level: u2 = 0, + nested_task_flag: bool = false, + mode_flag: bool = false, + } = .{}, + extended: packed struct(u16) { + resume_flag: bool = false, + virtual_8086_mode: bool = false, + alignment_smap_check: bool = false, + virtual_interrupt_flag: bool = false, + virtual_interrupt_pending: bool = false, + cpuid: bool = false, + reserved: u8 = 0, + aes_key_schedule: bool = false, + reserved1: bool = false, + } = .{}, + } = .{}, + ebp: u32 = 0, + edi: u32 = 0, + esi: u32 = 0, + edx: u32 = 0, + ecx: u32 = 0, + ebx: u32 = 0, + eax: u32 = 0, +}; + +fn A20IsEnabled() bool { + const address = 0x7dfe; + const address_with_offset = address + 0x100000; + if (@as(*volatile u16, @ptrFromInt(address)).* != @as(*volatile u16, @ptrFromInt(address_with_offset)).*) { + return true; + } + + @as(*volatile u16, @ptrFromInt(address)).* = ~(@as(*volatile u16, @ptrFromInt(address)).*); + + if (@as(*volatile u16, @ptrFromInt(address)).* != @as(*volatile u16, @ptrFromInt(address_with_offset)).*) { + return true; + } + + return false; +} + +const A20Error = error{a20_not_enabled}; + +pub fn A20Enable() A20Error!void { + if (!A20IsEnabled()) { + return A20Error.a20_not_enabled; + } +} + +pub const MemoryMapEntry = extern struct { + address: PhysicalAddress, + size: u64, + type: Type, + unused: u32 = 0, + + pub inline fn isUsable(entry: MemoryMapEntry) bool { + return entry.type == .usable and entry.address.value() >= lib.mb; + } + + pub inline fn toPhysicalMemoryRegion(entry: MemoryMapEntry) PhysicalMemoryRegion { + return PhysicalMemoryRegion.new(.{ + .address = entry.address, + .size = entry.size, + }); + } + + const Type = enum(u32) { + usable = 1, + reserved = 2, + acpi_reclaimable = 3, + acpi_nvs = 4, + bad_memory = 5, + }; +}; + +var memory_map_entries: [max_memory_entry_count]MemoryMapEntry = undefined; +const max_memory_entry_count = 32; + +pub const E820Iterator = extern struct { + registers: Registers = Registers{}, + index: usize = 0, + + pub fn next(iterator: *E820Iterator) ?MemoryMapEntry { + var memory_map_entry: MemoryMapEntry = undefined; + + comptime assert(@sizeOf(MemoryMapEntry) == 24); + iterator.registers.eax = 0xe820; + iterator.registers.ecx = @sizeOf(MemoryMapEntry); + iterator.registers.edx = 0x534d4150; + iterator.registers.edi = @intFromPtr(&memory_map_entry); + + interrupt(0x15, &iterator.registers, &iterator.registers); + + if (!iterator.registers.eflags.flags.carry_flag and iterator.registers.ebx != 0) { + iterator.index += 1; + return memory_map_entry; + } else { + return null; + } + } +}; + +pub fn getMemoryMapEntryCount() u32 { + var entry_count: u32 = 0; + var iterator = E820Iterator{}; + + while (iterator.next()) |_| { + entry_count += 1; + } + + return entry_count; +} + +const SuitableEntry = extern struct { + region: PhysicalMemoryRegion(.local), + index: u32, +}; + +pub fn fetchMemoryEntries(memory_map: []bootloader.MemoryMapEntry) void { + var iterator = E820Iterator{}; + while (iterator.next()) |entry| { + memory_map[entry.index] = .{ + .region = entry.descriptor.region, + .type = switch (entry.descriptor.type) { + .usable => if (entry.descriptor.isUsable()) .usable else .reserved, + .bad_memory => .bad_memory, + else => .reserved, + }, + }; + } + + if (iterator.index != memory_map.len) { + @panic("memory map entry mismatch"); + //privileged.panic("Memory map entries don't match. Got {}. Expected: {}", .{ iterator.index, memory_map.len }); + } +} + +const FindRSDPResult = union(enum) { + descriptor1: *ACPI.RSDP.Descriptor1, + descriptor2: *ACPI.RSDP.Descriptor2, +}; + +fn wrapSumBytes(bytes: []const u8) u8 { + var result: u8 = 0; + for (bytes) |byte| { + result +%= byte; + } + return result; +} + +pub fn getEBDAAddress() u32 { + const expected_EBDA_base = 0x80000; + const expected_EBDA_top = 0xa0000; + + const base = @as(u32, @as(*u16, @ptrFromInt(0x40e)).*) << 4; + + if (base < expected_EBDA_base or base > expected_EBDA_top) { + return expected_EBDA_base; + } else { + return base; + } +} + +const FindRSDP = error{ + not_found, + checksum_failed, +}; + +pub fn findRSDP() FindRSDP!*ACPI.RSDP.Descriptor1 { + const ebda_address = getEBDAAddress(); + const main_bios_area_base_address = 0xe0000; + const RSDP_PTR = "RSD PTR ".*; + + const pointers = [2]u32{ ebda_address, main_bios_area_base_address }; + const limits = [2]u32{ ebda_address + @as(u32, @intCast(@intFromEnum(lib.SizeUnit.kilobyte))), @as(u32, @intCast(@intFromEnum(lib.SizeUnit.megabyte))) }; + + for (pointers, 0..) |pointer, index| { + var ptr = pointer; + const limit = limits[index]; + + while (ptr < limit) : (ptr += 16) { + const rsdp_descriptor = @as(*ACPI.RSDP.Descriptor1, @ptrFromInt(ptr)); + + if (lib.equal(u8, &rsdp_descriptor.signature, &RSDP_PTR)) { + switch (rsdp_descriptor.revision) { + 0 => { + if (wrapSumBytes(lib.asBytes(rsdp_descriptor)) == 0) { + return rsdp_descriptor; + } else { + return FindRSDP.checksum_failed; + } + }, + 2 => { + const rsdp_descriptor2 = @fieldParentPtr(ACPI.RSDP.Descriptor2, "descriptor1", rsdp_descriptor); + if (wrapSumBytes(lib.asBytes(rsdp_descriptor2)) == 0) { + return &rsdp_descriptor2.descriptor1; + } else { + return FindRSDP.checksum_failed; + } + }, + else => unreachable, + } + } + } + } + + return FindRSDP.not_found; +} + +pub const RealModePointer = extern struct { + offset: u16, + segment: u16, + + pub inline fn desegment(real_mode_pointer: RealModePointer, comptime Ptr: type) Ptr { + return @as(Ptr, @ptrFromInt((@as(u32, real_mode_pointer.segment) << 4) + real_mode_pointer.offset)); + } +}; + +pub const VBE = extern struct { + pub const Information = extern struct { + signature: [4]u8, + version_minor: u8, + version_major: u8, + OEM: RealModePointer, + capabitilies: [4]u8, + video_modes: RealModePointer, + video_memory_blocks: u16, + OEM_software_revision: u16, + OEM_vendor: RealModePointer, + OEM_product_name: RealModePointer, + OEM_product_revision: RealModePointer, + reserved: [222]u8, + OEM_data: [256]u8, + + pub const Capabilities = packed struct(u32) { + dac_switchable: bool, + controller_not_vga_compatible: bool, + ramdac_blank: bool, + hardware_stereoscopic_signaling: bool, + VESA_EVC_stereo_signaling: bool, + reserved: u27 = 0, + }; + + comptime { + assert(@sizeOf(Information) == lib.default_sector_size); + } + + pub fn getVideoMode(vbe_info: *const VBE.Information, comptime isValidVideoMode: fn (mode: *const Mode) bool, desired_width: u16, desired_height: u16, edid_bpp: u8) ?Mode { + const video_modes = vbe_info.video_modes.desegment([*]const u16); + for (video_modes[0..lib.maxInt(usize)]) |video_mode_number| { + if (video_mode_number == 0xffff) break; + var registers = Registers{}; + var mode: VBE.Mode = undefined; + + registers.ecx = video_mode_number; + registers.edi = @intFromPtr(&mode); + + VBEinterrupt(.get_mode_information, ®isters) catch continue; + + if (isValidVideoMode(&mode) and mode.resolution_x == desired_width and mode.resolution_y == desired_height and mode.bpp == edid_bpp) { + // lib.log.debug("Video mode setting", .{}); + setVideoMode(video_mode_number) catch continue; + // lib.log.debug("Video mode set", .{}); + return mode; + } + } + + return null; + } + }; + + pub const Mode = extern struct { + mode_attributes: Attributes, + wina_attributes: u8, + winb_attributes: u8, + win_granularity: u16, + win_size: u16, + wina_segment: u16, + winb_segment: u16, + win_far_pointer: u32 align(2), + bytes_per_scanline: u16, + + resolution_x: u16, + resolution_y: u16, + character_size_x: u8, + character_size_y: u8, + plane_count: u8, + bpp: u8, + bank_count: u8, + memory_model: MemoryModel, + bank_size: u8, + image_count: u8, + reserved: u8 = 0, + + 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_mask_size: u8, + reserved_mask_shift: u8, + direct_color_info: u8, + + framebuffer_address: u32 align(2), + reserved_arr: [6]u8, + + linear_bytes_per_scanline: u16, + banked_image_count: u8, + linear_image_count: u8, + linear_red_mask_size: u8, + linear_red_mask_shift: u8, + linear_green_mask_size: u8, + linear_green_mask_shift: u8, + linear_blue_mask_size: u8, + linear_blue_mask_shift: u8, + linear_reserved_mask_size: u8, + linear_reserved_mask_shift: u8, + max_pixel_clock: u32 align(2), + + reserved0: [189]u8, + + comptime { + assert(@sizeOf(Mode) == 0x100); + } + + pub const MemoryModel = enum(u8) { + text_mode = 0x00, + cga_graphics = 0x01, + hercules_graphics = 0x02, + planar = 0x03, + packed_pixel = 0x04, + non_chain_4_256_color = 0x05, + direct_color = 0x06, + yuv = 0x07, + _, + }; + + pub const Attributes = packed struct(u16) { + mode_supported_by_hardware: bool, + reserved: u1 = 0, + TTY_output_function_supported_by_BIOS: bool, + color: bool, + graphics: bool, + vga_incompatible: bool, + vga_incompatible_window_mode: bool, + linear_framebuffer: bool, + double_scan_mode: bool, + interlaced_mode: bool, + hardware_triple_buffering: bool, + hardware_stereoscopic_display: bool, + dual_display_start_address: bool, + reserved0: u3 = 0, + }; + + pub const Number = packed struct(u16) { + number: u8, + is_VESA: bool, + reserved: u2 = 0, + refresh_rate_control_select: bool, + reserved0: u2 = 0, + linear_flat_frame_buffer_select: bool, + preserve_display_memory_select: bool, + }; + + pub fn defaultIsValid(mode: *const VBE.Mode) bool { + return mode.memory_model == .direct_color and mode.mode_attributes.linear_framebuffer; + } + }; + + const ReturnValue = enum(u8) { + successful = 0, + failure = 1, + not_supported_in_hardware = 2, + invalid_in_current_video_mode = 3, + }; + + const Call = enum(u8) { + get_controller_information = 0x00, + get_mode_information = 0x01, + set_mode_information = 0x02, + get_edid_information = 0x15, + }; + + const interrupt_number = 0x10; + const vbe_code = 0x4f; + + pub fn VBEinterrupt(call: Call, registers: *Registers) !void { + const source_ax = @as(u16, vbe_code << 8) | @intFromEnum(call); + registers.eax = source_ax; + interrupt(interrupt_number, registers, registers); + + const ax = @as(u16, @truncate(registers.eax)); + const al = @as(u8, @truncate(ax)); + const is_supported = al == vbe_code; + if (!is_supported) return Error.not_supported; + + const ah = @as(u8, @truncate(ax >> 8)); + if (ah > 3) @panic("Return value too high"); + const return_value = @as(ReturnValue, @enumFromInt(ah)); + return switch (return_value) { + .failure => Error.failure, + .not_supported_in_hardware => Error.not_supported_in_hardware, + .invalid_in_current_video_mode => Error.invalid_in_current_video_mode, + .successful => {}, + }; + } + + pub fn getControllerInformation(vbe_info: *VBE.Information) VBE.Error!void { + var registers = Registers{}; + + registers.edi = @intFromPtr(vbe_info); + try VBEinterrupt(.get_controller_information, ®isters); + } + + pub const Error = error{ + bad_signature, + unsupported_version, + not_supported, + failure, + not_supported_in_hardware, + invalid_in_current_video_mode, + }; + + const EDID = extern struct { + padding: [8]u8, + manufacturer_id_be: u16 align(1), + edid_id_code: u16 align(1), + serial_number: u32 align(1), + man_week: u8, + man_year: u8, + edid_version: u8, + edid_revision: u8, + video_input_type: u8, + max_horizontal_size: u8, + max_vertical_size: u8, + gamma_factor: u8, + dpms_flags: u8, + chroma_info: [10]u8, + est_timings1: u8, + est_timings2: u8, + man_res_timing: u8, + std_timing_id: [8]u16 align(1), + det_timing_desc1: [18]u8, + det_timing_desc2: [18]u8, + det_timing_desc3: [18]u8, + det_timing_desc4: [18]u8, + unused: u8, + checksum: u8, + + comptime { + assert(@sizeOf(EDID) == 0x80); + } + + pub fn getWidth(edid: *const EDID) u16 { + return edid.det_timing_desc1[2] + (@as(u16, edid.det_timing_desc1[4] & 0xf0) << 4); + } + + pub fn getHeight(edid: *const EDID) u16 { + return edid.det_timing_desc1[5] + (@as(u16, edid.det_timing_desc1[7] & 0xf0) << 4); + } + }; + + pub fn getEDIDInfo() VBE.Error!EDID { + var edid_info: EDID = undefined; + + var registers = Registers{}; + registers.ds = segment(@intFromPtr(&edid_info)); + registers.es = registers.ds; + registers.edi = offset(@intFromPtr(&edid_info)); + registers.ebx = 1; + + try VBEinterrupt(.get_edid_information, ®isters); + + return edid_info; + } + + pub fn setVideoMode(video_mode_number: u16) VBE.Error!void { + var registers = Registers{}; + registers.ebx = @as(u32, video_mode_number) | (1 << 14); + try VBEinterrupt(.set_mode_information, ®isters); + } +}; diff --git a/src/bootloader/limine.zig b/src/bootloader/limine.zig new file mode 100644 index 0000000..08e29d0 --- /dev/null +++ b/src/bootloader/limine.zig @@ -0,0 +1,369 @@ +const lib = @import("lib"); +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; + +const ID = [4]u64; + +fn requestID(c: u64, d: u64) ID { + return .{ 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, c, d }; +} + +pub const UUID = extern struct { + a: u32, + b: u16, + c: u16, + d: [8]u8, +}; + +pub const File = extern struct { + revision: u64, + address: u64, + size: u64, + path: [*:0]const u8, + command_line: [*:0]const u8, + media_type: MediaType, + unused: u32, + tftp_ip: u32, + tftp_port: u32, + partition_index: u32, + mbr_disk_id: u32, + gpt_disk_uuid: UUID, + gpt_part_uuid: UUID, + part_uuid: UUID, + + pub const MediaType = enum(u32) { + generic = 0, + optical = 1, + tftp = 2, + }; + + pub inline fn getPath(file: *const File) []const u8 { + const path = file.path[0..lib.length(file.path)]; + return path; + } + + pub inline fn getContent(file: *const File) []const u8 { + const content = @as([*]const u8, @ptrFromInt(file.address))[0..file.size]; + return content; + } +}; + +pub const BootloaderInfo = extern struct { + pub const Request = extern struct { + id: ID = requestID(0xf55038d8e2a1202f, 0x279426fcf5f59740), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + name: [*:0]const u8, + version: [*:0]const u8, + }; +}; + +pub const StackSize = extern struct { + pub const Request = extern struct { + id: ID = requestID(0x224ef0460a8e8926, 0xe1cb0fc25f46ea3d), + revision: u64, + response: ?*const Response = null, + stack_size: u64, + }; + + pub const Response = extern struct { + revision: u64, + }; +}; + +pub const HHDM = extern struct { + pub const Request = extern struct { + id: ID = requestID(0x48dcf1cb8ad2b852, 0x63984e959a98244b), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + offset: u64, + }; +}; + +pub const VideoMode = extern struct { + pitch: u64, + width: u64, + height: u64, + bpp: u16, + memory_model: u8, + red_mask_size: u8, + red_mask_shift: u8, + green_mask_size: u8, + green_mask_shift: u8, + blue_mask_size: u8, + blue_mask_shift: u8, +}; + +pub const Framebuffer = extern struct { + address: u64, + width: u64, + height: u64, + pitch: u64, + bpp: u16, + memory_model: u8, + red_mask_size: u8, + red_mask_shift: u8, + green_mask_size: u8, + green_mask_shift: u8, + blue_mask_size: u8, + blue_mask_shift: u8, + unused: [7]u8, + edid_size: u64, + edid: u64, + mode_count: u64, + modes: [*]const *const VideoMode, + + pub const Request = extern struct { + id: ID = requestID(0x9d5827dcd881dd75, 0xa3148604f6fab11b), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + framebuffer_count: u64, + framebuffers: *const [*]const Framebuffer, + }; +}; + +pub const Terminal = extern struct { + columns: u64, + rows: u64, + framebuffer: ?*Framebuffer, + + pub const Request = extern struct { + id: ID = requestID(0xc8ac59310c2b0844, 0xa68d0c7265d38878), + revision: u64, + response: ?*const Response = null, + callback: ?*const Callback, + }; + + pub const Response = extern struct { + revision: u64, + terminal_count: u64, + terminals: ?*const [*]Terminal, + write: ?*const Write, + }; + + pub const Write = fn (*Terminal, [*:0]const u8, u64) callconv(.C) void; + pub const Callback = fn (*Terminal, u64, u64, u64, u64) callconv(.C) void; +}; + +pub const Paging5Level = extern struct { + pub const Request = extern struct { + id: ID = requestID(0x94469551da9b3192, 0xebe5e86db7382888), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + }; +}; + +const SMPInfoGoToAddress = fn (*SMPInfo) callconv(.C) noreturn; + +pub const SMPInfoRequest = extern struct { + id: ID = requestID(0x95a67b819a1b857e, 0xa0b61b723b6a73e0), + revision: u64, + response: ?*const SMPInfo.Response = null, + flags: packed struct(u64) { + x2apic: bool, + reserved: u63 = 0, + }, +}; + +pub const SMPInfo = switch (@import("builtin").cpu.arch) { + .x86_64 => extern struct { + processor_id: u32, + lapic_id: u32, + reserved: u64, + goto_address: ?*const SMPInfoGoToAddress, + extra_argument: u64, + + pub const Response = extern struct { + revision: u64, + flags: u32, + bsp_lapic_id: u32, + cpu_count: u64, + cpus: ?*const [*]SMPInfo, + }; + }, + .aarch64 => extern struct { + processor_id: u32, + gic_iface_no: u32, + mpidr: u64, + reserved: u64, + goto_address: ?*const SMPInfoGoToAddress, + extra_argument: u64, + + pub const Request = SMPInfoRequest; + + pub const Response = extern struct { + revision: u64, + flags: u32, + bsp_mpidr: u64, + cpu_count: u64, + cpus: ?*const [*]const SMPInfo, + }; + }, + else => @compileError("Architecture not supported"), +}; + +pub const MemoryMap = extern struct { + pub const Entry = extern struct { + region: PhysicalMemoryRegion, + type: Type, + + const Type = enum(u64) { + usable = 0, + reserved = 1, + acpi_reclaimable = 2, + acpi_nvs = 3, + bad_memory = 4, + bootloader_reclaimable = 5, + kernel_and_modules = 6, + framebuffer = 7, + }; + }; + + pub const Request = extern struct { + id: ID = requestID(0x67cf3d9d378a806f, 0xe304acdfc50c3c62), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + entry_count: u64, + entries: *const [*]const Entry, + }; +}; + +pub const EntryPoint = extern struct { + pub const Function = fn () callconv(.C) noreturn; + + pub const Request = extern struct { + id: ID = requestID(0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a), + revision: u64, + response: ?*const Response = null, + entry_point: *const Function, + }; + + pub const Response = extern struct { + revision: u64, + }; +}; + +pub const KernelFile = extern struct { + pub const Request = extern struct { + id: ID = requestID(0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + file: ?*const File, + }; +}; + +pub const Module = extern struct { + pub const Request = extern struct { + id: ID = requestID(0x3e7e279702be32af, 0xca1c4f3bd1280cee), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + module_count: u64, + modules: *const [*]const File, + }; +}; + +pub const RSDP = extern struct { + pub const Request = extern struct { + id: ID = requestID(0xc5e77b6b397e7b43, 0x27637845accdcf3c), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + address: u64, + }; +}; + +pub const SMBIOS = extern struct { + pub const Request = extern struct { + id: ID = requestID(0x9e9046f11e095391, 0xaa4a520fefbde5ee), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + entry_32: u64, + entry_64: u64, + }; +}; + +pub const EFISystemTable = extern struct { + pub const Request = extern struct { + id: ID = requestID(0x5ceba5163eaaf6d6, 0x0a6981610cf65fcc), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + address: u64, + }; +}; + +pub const BootTime = extern struct { + pub const Request = extern struct { + id: ID = requestID(0x502746e184c088aa, 0xfbc5ec83e6327893), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + boot_time: i64, + }; +}; + +pub const KernelAddress = extern struct { + pub const Request = extern struct { + id: ID = requestID(0x71ba76863cc55f63, 0xb2644a48c516a487), + revision: u64, + response: ?*const Response = null, + }; + + pub const Response = extern struct { + revision: u64, + physical_address: u64, + virtual_address: u64, + }; +}; + +pub const DTB = extern struct { + pub const Request = extern struct { + id: ID = requestID(0xb40ddb48fb54bac7, 0x545081493f81ffb7), + revision: u64, + response: ?*const Response = null, + }; + pub const Response = extern struct { + revision: u64, + address: u64, + }; +}; diff --git a/src/bootloader/limine/LICENSE.md b/src/bootloader/limine/LICENSE.md new file mode 100644 index 0000000..f971aed --- /dev/null +++ b/src/bootloader/limine/LICENSE.md @@ -0,0 +1,9 @@ +Copyright 2019, 2020, 2021, 2022 mintsuki and contributors. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/bootloader/limine/arch/x86_64/linker_script.ld b/src/bootloader/limine/arch/x86_64/linker_script.ld new file mode 100644 index 0000000..f92a6ba --- /dev/null +++ b/src/bootloader/limine/arch/x86_64/linker_script.ld @@ -0,0 +1,37 @@ +PHDRS { + none PT_NULL FLAGS(0); + text PT_LOAD FLAGS((1 << 2) | (1 << 0) /* Readable | Executable */); + rodata PT_LOAD FLAGS((1 << 2) /* Readable */); + data PT_LOAD FLAGS((1 << 2) | (1 << 1) /* Readable | Writeable */); +} + +SECTIONS { + /* Start here so there is no conflict with the CPU driver */ + . = 0xFFFFFFFFF0000000; + + PROVIDE(text_section_start = .); + .text . : { + *(.text*) + }:text + + . = ALIGN(4K); + PROVIDE(text_section_end = .); + + PROVIDE(rodata_section_start = .); + .rodata . : { + *(.rodata*) + }:rodata + + . = ALIGN(4K); + PROVIDE(rodata_section_end = .); + + PROVIDE(data_section_start = .); + .data . : { + *(.data*) + *(.bss*) + *(.got*) + }:data + + . = ALIGN(4K); + PROVIDE(data_section_end = .); +} diff --git a/src/bootloader/limine/installables/BOOTAA64.EFI b/src/bootloader/limine/installables/BOOTAA64.EFI new file mode 100755 index 0000000..5bae1c8 Binary files /dev/null and b/src/bootloader/limine/installables/BOOTAA64.EFI differ diff --git a/src/bootloader/limine/installables/BOOTIA32.EFI b/src/bootloader/limine/installables/BOOTIA32.EFI new file mode 100755 index 0000000..6ff719f Binary files /dev/null and b/src/bootloader/limine/installables/BOOTIA32.EFI differ diff --git a/src/bootloader/limine/installables/BOOTX64.EFI b/src/bootloader/limine/installables/BOOTX64.EFI new file mode 100755 index 0000000..a2d161e Binary files /dev/null and b/src/bootloader/limine/installables/BOOTX64.EFI differ diff --git a/src/bootloader/limine/installables/LICENSE.md b/src/bootloader/limine/installables/LICENSE.md new file mode 100644 index 0000000..f970816 --- /dev/null +++ b/src/bootloader/limine/installables/LICENSE.md @@ -0,0 +1,9 @@ +Copyright (C) 2019-2023 mintsuki and contributors. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/bootloader/limine/installables/Makefile b/src/bootloader/limine/installables/Makefile new file mode 100644 index 0000000..1ee8b78 --- /dev/null +++ b/src/bootloader/limine/installables/Makefile @@ -0,0 +1,45 @@ +CC ?= cc +INSTALL ?= ./install-sh + +PREFIX ?= /usr/local + +CFLAGS ?= -g -O2 -pipe -Wall -Wextra + +.PHONY: all +all: limine-deploy limine-version + +.PHONY: install-data +install-data: all + $(INSTALL) -d '$(DESTDIR)$(PREFIX)/share' + $(INSTALL) -d '$(DESTDIR)$(PREFIX)/share/limine' + $(INSTALL) -m 644 limine.sys '$(DESTDIR)$(PREFIX)/share/limine/' + $(INSTALL) -m 644 limine-cd.bin '$(DESTDIR)$(PREFIX)/share/limine/' + $(INSTALL) -m 644 limine-cd-efi.bin '$(DESTDIR)$(PREFIX)/share/limine/' + $(INSTALL) -m 644 limine-pxe.bin '$(DESTDIR)$(PREFIX)/share/limine/' + $(INSTALL) -m 644 BOOTX64.EFI '$(DESTDIR)$(PREFIX)/share/limine/' + $(INSTALL) -m 644 BOOTIA32.EFI '$(DESTDIR)$(PREFIX)/share/limine/' + $(INSTALL) -d '$(DESTDIR)$(PREFIX)/include' + $(INSTALL) -m 644 limine.h '$(DESTDIR)$(PREFIX)/include/' + +.PHONY: install +install: install-data + $(INSTALL) -d '$(DESTDIR)$(PREFIX)/bin' + $(INSTALL) limine-deploy '$(DESTDIR)$(PREFIX)/bin/' + $(INSTALL) limine-version '$(DESTDIR)$(PREFIX)/bin/' + +.PHONY: install-strip +install-strip: install-data + $(INSTALL) -d '$(DESTDIR)$(PREFIX)/bin' + $(INSTALL) -s limine-deploy '$(DESTDIR)$(PREFIX)/bin/' + $(INSTALL) -s limine-version '$(DESTDIR)$(PREFIX)/bin/' + +.PHONY: clean +clean: + rm -f limine-deploy limine-deploy.exe + rm -f limine-version limine-version.exe + +limine-deploy: limine-deploy.c limine-hdd.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -std=c99 -D__USE_MINGW_ANSI_STDIO limine-deploy.c $(LIBS) -o $@ + +limine-version: limine-version.c + $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -std=c99 -D__USE_MINGW_ANSI_STDIO limine-version.c $(LIBS) -o $@ diff --git a/src/bootloader/limine/installables/install-sh b/src/bootloader/limine/installables/install-sh new file mode 100755 index 0000000..ec298b5 --- /dev/null +++ b/src/bootloader/limine/installables/install-sh @@ -0,0 +1,541 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2020-11-14.01; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# 'make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +tab=' ' +nl=' +' +IFS=" $tab$nl" + +# Set DOITPROG to "echo" to test this script. + +doit=${DOITPROG-} +doit_exec=${doit:-exec} + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +# Create dirs (including intermediate dirs) using mode 755. +# This is like GNU 'install' as of coreutils 8.32 (2020). +mkdir_umask=22 + +backupsuffix= +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +is_target_a_directory=possibly + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -p pass -p to $cpprog. + -s $stripprog installed files. + -S SUFFIX attempt to back up existing files, with suffix SUFFIX. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG + +By default, rm is invoked with -f; when overridden with RMPROG, +it's up to you to specify -f if you want it. + +If -S is not specified, no backups are attempted. + +Email bug reports to bug-automake@gnu.org. +Automake home page: https://www.gnu.org/software/automake/ +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -p) cpprog="$cpprog -p";; + + -s) stripcmd=$stripprog;; + + -S) backupsuffix="$2" + shift;; + + -t) + is_target_a_directory=always + dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; + + -T) is_target_a_directory=never;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +# We allow the use of options -d and -T together, by making -d +# take the precedence; this is for compatibility with GNU install. + +if test -n "$dir_arg"; then + if test -n "$dst_arg"; then + echo "$0: target directory not allowed when installing a directory." >&2 + exit 1 + fi +fi + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call 'install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + if test $# -gt 1 || test "$is_target_a_directory" = always; then + if test ! -d "$dst_arg"; then + echo "$0: $dst_arg: Is not a directory." >&2 + exit 1 + fi + fi +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names problematic for 'test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + # Don't chown directories that already exist. + if test $dstdir_status = 0; then + chowncmd="" + fi + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename. + if test -d "$dst"; then + if test "$is_target_a_directory" = never; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dstbase=`basename "$src"` + case $dst in + */) dst=$dst$dstbase;; + *) dst=$dst/$dstbase;; + esac + dstdir_status=0 + else + dstdir=`dirname "$dst"` + test -d "$dstdir" + dstdir_status=$? + fi + fi + + case $dstdir in + */) dstdirslash=$dstdir;; + *) dstdirslash=$dstdir/;; + esac + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + # The $RANDOM variable is not portable (e.g., dash). Use it + # here however when possible just to lower collision chance. + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + + trap ' + ret=$? + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null + exit $ret + ' 0 + + # Because "mkdir -p" follows existing symlinks and we likely work + # directly in world-writeable /tmp, make sure that the '$tmpdir' + # directory is successfully created first before we actually test + # 'mkdir -p'. + if (umask $mkdir_umask && + $mkdirprog $mkdir_mode "$tmpdir" && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + test_tmpdir="$tmpdir/a" + ls_ld_tmpdir=`ls -ld "$test_tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null + fi + trap '' 0;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + oIFS=$IFS + IFS=/ + set -f + set fnord $dstdir + shift + set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=${dstdirslash}_inst.$$_ + rmtmp=${dstdirslash}_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && + { test -z "$stripcmd" || { + # Create $dsttmp read-write so that cp doesn't create it read-only, + # which would cause strip to fail. + if test -z "$doit"; then + : >"$dsttmp" # No need to fork-exec 'touch'. + else + $doit touch "$dsttmp" + fi + } + } && + $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + set +f && + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # If $backupsuffix is set, and the file being installed + # already exists, attempt a backup. Don't worry if it fails, + # e.g., if mv doesn't support -f. + if test -n "$backupsuffix" && test -f "$dst"; then + $doit $mvcmd -f "$dst" "$dst$backupsuffix" 2>/dev/null + fi + + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/src/bootloader/limine/installables/limine-cd-efi.bin b/src/bootloader/limine/installables/limine-cd-efi.bin new file mode 100644 index 0000000..3595310 Binary files /dev/null and b/src/bootloader/limine/installables/limine-cd-efi.bin differ diff --git a/src/bootloader/limine/installables/limine-cd.bin b/src/bootloader/limine/installables/limine-cd.bin new file mode 100644 index 0000000..2de50bf Binary files /dev/null and b/src/bootloader/limine/installables/limine-cd.bin differ diff --git a/src/bootloader/limine/installables/limine-deploy.c b/src/bootloader/limine/installables/limine-deploy.c new file mode 100644 index 0000000..72edfd8 --- /dev/null +++ b/src/bootloader/limine/installables/limine-deploy.c @@ -0,0 +1,920 @@ +#undef IS_WINDOWS +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#define IS_WINDOWS 1 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +static bool quiet = false; + +static int set_pos(FILE *stream, uint64_t pos) { + if (sizeof(long) >= 8) { + return fseek(stream, (long)pos, SEEK_SET); + } + + long jump_size = (LONG_MAX / 2) + 1; + long last_jump = pos % jump_size; + uint64_t jumps = pos / jump_size; + + rewind(stream); + + for (uint64_t i = 0; i < jumps; i++) { + if (fseek(stream, jump_size, SEEK_CUR) != 0) { + return -1; + } + } + if (fseek(stream, last_jump, SEEK_CUR) != 0) { + return -1; + } + + return 0; +} + +#define DIV_ROUNDUP(a, b) (((a) + ((b) - 1)) / (b)) + +struct gpt_table_header { + // the head + char signature[8]; + uint32_t revision; + uint32_t header_size; + uint32_t crc32; + uint32_t _reserved0; + + // the partitioning info + uint64_t my_lba; + uint64_t alternate_lba; + uint64_t first_usable_lba; + uint64_t last_usable_lba; + + // the guid + uint64_t disk_guid[2]; + + // entries related + uint64_t partition_entry_lba; + uint32_t number_of_partition_entries; + uint32_t size_of_partition_entry; + uint32_t partition_entry_array_crc32; +}; + +struct gpt_entry { + uint64_t partition_type_guid[2]; + + uint64_t unique_partition_guid[2]; + + uint64_t starting_lba; + uint64_t ending_lba; + + uint64_t attributes; + + uint16_t partition_name[36]; +}; + +// This table from https://web.mit.edu/freebsd/head/sys/libkern/crc32.c +static const uint32_t crc32_table[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +static uint32_t crc32(void *_stream, size_t len) { + uint8_t *stream = _stream; + uint32_t ret = 0xffffffff; + + for (size_t i = 0; i < len; i++) { + ret = (ret >> 8) ^ crc32_table[(ret ^ stream[i]) & 0xff]; + } + + ret ^= 0xffffffff; + return ret; +} + +static bool bigendian = false; + +static uint16_t endswap16(uint16_t value) { + uint16_t ret = 0; + ret |= (value >> 8) & 0x00ff; + ret |= (value << 8) & 0xff00; + return ret; +} + +static uint32_t endswap32(uint32_t value) { + uint32_t ret = 0; + ret |= (value >> 24) & 0x000000ff; + ret |= (value >> 8) & 0x0000ff00; + ret |= (value << 8) & 0x00ff0000; + ret |= (value << 24) & 0xff000000; + return ret; +} + +static uint64_t endswap64(uint64_t value) { + uint64_t ret = 0; + ret |= (value >> 56) & 0x00000000000000ff; + ret |= (value >> 40) & 0x000000000000ff00; + ret |= (value >> 24) & 0x0000000000ff0000; + ret |= (value >> 8) & 0x00000000ff000000; + ret |= (value << 8) & 0x000000ff00000000; + ret |= (value << 24) & 0x0000ff0000000000; + ret |= (value << 40) & 0x00ff000000000000; + ret |= (value << 56) & 0xff00000000000000; + return ret; +} + +#define ENDSWAP(VALUE) (bigendian ? ( \ + sizeof(VALUE) == 1 ? (VALUE) : \ + sizeof(VALUE) == 2 ? endswap16(VALUE) : \ + sizeof(VALUE) == 4 ? endswap32(VALUE) : \ + sizeof(VALUE) == 8 ? endswap64(VALUE) : (abort(), 1) \ +) : (VALUE)) + +static enum { + CACHE_CLEAN, + CACHE_DIRTY +} cache_state; +static uint64_t cached_block; +static uint8_t *cache = NULL; +static FILE *device = NULL; +static size_t block_size; + +static bool device_init(void) { + size_t guesses[] = { 512, 2048, 4096 }; + + for (size_t i = 0; i < sizeof(guesses) / sizeof(size_t); i++) { + void *tmp = realloc(cache, guesses[i]); + if (tmp == NULL) { + perror("ERROR"); + return false; + } + cache = tmp; + + rewind(device); + + size_t ret = fread(cache, guesses[i], 1, device); + if (ret != 1) { + continue; + } + + block_size = guesses[i]; + + if (!quiet) { + fprintf(stderr, "Physical block size of %zu bytes.\n", block_size); + } + + cache_state = CACHE_CLEAN; + cached_block = 0; + return true; + } + + fprintf(stderr, "ERROR: Couldn't determine block size of device.\n"); + return false; +} + +static bool device_flush_cache(void) { + if (cache_state == CACHE_CLEAN) + return true; + + if (set_pos(device, cached_block * block_size) != 0) { + perror("ERROR"); + return false; + } + + size_t ret = fwrite(cache, block_size, 1, device); + if (ret != 1) { + perror("ERROR"); + return false; + } + + cache_state = CACHE_CLEAN; + return true; +} + +static bool device_cache_block(uint64_t block) { + if (cached_block == block) + return true; + + if (cache_state == CACHE_DIRTY) { + if (!device_flush_cache()) + return false; + } + + if (set_pos(device, block * block_size) != 0) { + perror("ERROR"); + return false; + } + + size_t ret = fread(cache, block_size, 1, device); + if (ret != 1) { + perror("ERROR"); + return false; + } + + cached_block = block; + + return true; +} + +struct undeploy_data { + void *data; + uint64_t loc; + uint64_t count; +}; + +#define UNDEPLOY_DATA_MAX 256 + +static bool undeploying = false; +static struct undeploy_data undeploy_data[UNDEPLOY_DATA_MAX]; +static struct undeploy_data undeploy_data_rev[UNDEPLOY_DATA_MAX]; +static uint64_t undeploy_data_i = 0; +static const char *undeploy_file = NULL; + +static void reverse_undeploy_data(void) { + for (size_t i = 0, j = undeploy_data_i - 1; i < undeploy_data_i; i++, j--) { + undeploy_data_rev[j] = undeploy_data[i]; + } + + memcpy(undeploy_data, undeploy_data_rev, undeploy_data_i * sizeof(struct undeploy_data)); +} + +static void free_undeploy_data(void) { + for (size_t i = 0; i < undeploy_data_i; i++) { + free(undeploy_data[i].data); + } +} + +static bool store_undeploy_data(const char *filename) { + if (!quiet) { + fprintf(stderr, "Storing undeploy data to file: `%s`...\n", filename); + } + + FILE *udfile = fopen(filename, "wb"); + if (udfile == NULL) { + goto error; + } + + if (fwrite(&undeploy_data_i, sizeof(uint64_t), 1, udfile) != 1) { + goto error; + } + + for (size_t i = 0; i < undeploy_data_i; i++) { + if (fwrite(&undeploy_data[i].loc, sizeof(uint64_t), 1, udfile) != 1) { + goto error; + } + if (fwrite(&undeploy_data[i].count, sizeof(uint64_t), 1, udfile) != 1) { + goto error; + } + if (fwrite(undeploy_data[i].data, undeploy_data[i].count, 1, udfile) != 1) { + goto error; + } + } + + fclose(udfile); + return true; + +error: + perror("ERROR"); + if (udfile != NULL) { + fclose(udfile); + } + return false; +} + +static bool load_undeploy_data(const char *filename) { + if (!quiet) { + fprintf(stderr, "Loading undeploy data from file: `%s`...\n", filename); + } + + FILE *udfile = fopen(filename, "rb"); + if (udfile == NULL) { + goto error; + } + + if (fread(&undeploy_data_i, sizeof(uint64_t), 1, udfile) != 1) { + goto error; + } + + for (size_t i = 0; i < undeploy_data_i; i++) { + if (fread(&undeploy_data[i].loc, sizeof(uint64_t), 1, udfile) != 1) { + goto error; + } + if (fread(&undeploy_data[i].count, sizeof(uint64_t), 1, udfile) != 1) { + goto error; + } + undeploy_data[i].data = malloc(undeploy_data[i].count); + if (undeploy_data[i].data == NULL) { + goto error; + } + if (fread(undeploy_data[i].data, undeploy_data[i].count, 1, udfile) != 1) { + goto error; + } + } + + fclose(udfile); + return true; + +error: + perror("ERROR"); + if (udfile != NULL) { + fclose(udfile); + } + return false; +} + +static bool _device_read(void *_buffer, uint64_t loc, size_t count) { + uint8_t *buffer = _buffer; + uint64_t progress = 0; + while (progress < count) { + uint64_t block = (loc + progress) / block_size; + + if (!device_cache_block(block)) { + fprintf(stderr, "ERROR: Read error.\n"); + return false; + } + + uint64_t chunk = count - progress; + uint64_t offset = (loc + progress) % block_size; + if (chunk > block_size - offset) + chunk = block_size - offset; + + memcpy(buffer + progress, &cache[offset], chunk); + progress += chunk; + } + + return true; +} + +static bool _device_write(const void *_buffer, uint64_t loc, size_t count) { + if (undeploying) { + goto skip_save; + } + + if (undeploy_data_i >= UNDEPLOY_DATA_MAX) { + fprintf(stderr, "Internal error: Too many undeploy data entries!\n"); + return false; + } + + struct undeploy_data *ud = &undeploy_data[undeploy_data_i]; + + ud->data = malloc(count); + if (ud->data == NULL) { + fprintf(stderr, "ERROR: Memory allocation failure.\n"); + return false; + } + + if (!_device_read(ud->data, loc, count)) { + fprintf(stderr, "ERROR: Device read failure.\n"); + return false; + } + + ud->loc = loc; + ud->count = count; + +skip_save:; + const uint8_t *buffer = _buffer; + uint64_t progress = 0; + while (progress < count) { + uint64_t block = (loc + progress) / block_size; + + if (!device_cache_block(block)) { + fprintf(stderr, "ERROR: Write error.\n"); + return false; + } + + uint64_t chunk = count - progress; + uint64_t offset = (loc + progress) % block_size; + if (chunk > block_size - offset) + chunk = block_size - offset; + + memcpy(&cache[offset], buffer + progress, chunk); + cache_state = CACHE_DIRTY; + progress += chunk; + } + + if (!undeploying) { + undeploy_data_i++; + } + return true; +} + +static void undeploy(void) { + undeploying = true; + + cache_state = CACHE_CLEAN; + cached_block = (uint64_t)-1; + + for (size_t i = 0; i < undeploy_data_i; i++) { + struct undeploy_data *ud = &undeploy_data[i]; + bool retry = false; + while (!_device_write(ud->data, ud->loc, ud->count)) { + if (retry) { + fprintf(stderr, "ERROR: Undeploy data index %zu failed to write. Undeploy may be incomplete!\n", i); + break; + } + if (!quiet) { + fprintf(stderr, "Warning: Undeploy data index %zu failed to write, retrying...\n", i); + } + if (!device_flush_cache()) { + fprintf(stderr, "ERROR: Device cache flush failure. Undeploy may be incomplete!\n"); + } + cache_state = CACHE_CLEAN; + cached_block = (uint64_t)-1; + retry = true; + } + } + + if (!device_flush_cache()) { + fprintf(stderr, "ERROR: Device cache flush failure. Undeploy may be incomplete!\n"); + } + + if (!quiet) { + fprintf(stderr, "Undeploy data restored successfully. Limine undeployed!\n"); + } +} + +#define device_read(BUFFER, LOC, COUNT) \ + do { \ + if (!_device_read(BUFFER, LOC, COUNT)) \ + goto cleanup; \ + } while (0) + +#define device_write(BUFFER, LOC, COUNT) \ + do { \ + if (!_device_write(BUFFER, LOC, COUNT)) \ + goto cleanup; \ + } while (0) + +static void usage(const char *name) { + printf("Usage: %s [GPT partition index]\n", name); + printf("\n"); + printf(" --force-mbr Force MBR detection to work even if the\n"); + printf(" safety checks fail (DANGEROUS!)\n"); + printf("\n"); + printf(" --undeploy Reverse the entire deployment procedure\n"); + printf("\n"); + printf(" --undeploy-data-file=\n"); + printf(" Set the input (for --undeploy) or output file\n"); + printf(" name of the file which contains undeploy data\n"); + printf("\n"); + printf(" --quiet Do not print verbose diagnostic messages\n"); + printf("\n"); + printf(" --help | -h Display this help message\n"); + printf("\n"); +#ifdef IS_WINDOWS + system("pause"); +#endif +} + +int deploy(const char* device_path, const uint8_t* bootloader_img, size_t bootloader_file_size) { + int ok = EXIT_FAILURE; + int force_mbr = 0; + bool undeploy_mode = false; + uint8_t orig_mbr[70], timestamp[6]; + const char *part_ndx = NULL; + + uint32_t endcheck = 0x12345678; + uint8_t endbyte = *((uint8_t *)&endcheck); + bigendian = endbyte == 0x12; + + if ((device = fopen(device_path, "r+b")) == NULL) { // + fprintf(stderr, "ERROR: No device specified\n"); + return EXIT_FAILURE; + } + + if (!device_init()) { + goto undeploy_mode_cleanup; + } + + if (undeploy_mode) { + if (undeploy_file == NULL) { + fprintf(stderr, "ERROR: Undeploy mode set but no --undeploy-data-file=... passed.\n"); + goto undeploy_mode_cleanup; + } + + if (!load_undeploy_data(undeploy_file)) { + goto undeploy_mode_cleanup; + } + + undeploy(); + + ok = EXIT_SUCCESS; + goto undeploy_mode_cleanup; + } + + // Probe for GPT and logical block size + int gpt = 0; + struct gpt_table_header gpt_header; + uint64_t lb_guesses[] = { 512, 4096 }; + uint64_t lb_size = 0; + for (size_t i = 0; i < sizeof(lb_guesses) / sizeof(uint64_t); i++) { + device_read(&gpt_header, lb_guesses[i], sizeof(struct gpt_table_header)); + if (!strncmp(gpt_header.signature, "EFI PART", 8)) { + lb_size = lb_guesses[i]; + if (!force_mbr) { + gpt = 1; + if (!quiet) { + fprintf(stderr, "Deploying to GPT. Logical block size of %" PRIu64 " bytes.\n", + lb_guesses[i]); + } + } else { + fprintf(stderr, "ERROR: Device has a valid GPT, refusing to force MBR.\n"); + goto cleanup; + } + break; + } + } + + struct gpt_table_header secondary_gpt_header; + if (gpt) { + if (!quiet) { + fprintf(stderr, "Secondary header at LBA 0x%" PRIx64 ".\n", + ENDSWAP(gpt_header.alternate_lba)); + } + device_read(&secondary_gpt_header, lb_size * ENDSWAP(gpt_header.alternate_lba), + sizeof(struct gpt_table_header)); + if (!strncmp(secondary_gpt_header.signature, "EFI PART", 8)) { + if (!quiet) { + fprintf(stderr, "Secondary header valid.\n"); + } + } else { + fprintf(stderr, "ERROR: Secondary header not valid, aborting.\n"); + goto cleanup; + } + } + + int mbr = 0; + if (gpt == 0) { + // Do all sanity checks on MBR + mbr = 1; + + uint8_t hint8 = 0; + uint16_t hint16 = 0; + + bool any_active = false; + + device_read(&hint8, 446, sizeof(uint8_t)); + if (hint8 != 0x00 && hint8 != 0x80) { + if (!force_mbr) { + mbr = 0; + } else { + hint8 = hint8 & 0x80 ? 0x80 : 0x00; + device_write(&hint8, 446, sizeof(uint8_t)); + } + } + any_active = any_active ? any_active : (hint8 & 0x80) != 0; + device_read(&hint8, 462, sizeof(uint8_t)); + if (hint8 != 0x00 && hint8 != 0x80) { + if (!force_mbr) { + mbr = 0; + } else { + hint8 = hint8 & 0x80 ? 0x80 : 0x00; + device_write(&hint8, 462, sizeof(uint8_t)); + } + } + any_active = any_active ? any_active : (hint8 & 0x80) != 0; + device_read(&hint8, 478, sizeof(uint8_t)); + if (hint8 != 0x00 && hint8 != 0x80) { + if (!force_mbr) { + mbr = 0; + } else { + hint8 = hint8 & 0x80 ? 0x80 : 0x00; + device_write(&hint8, 478, sizeof(uint8_t)); + } + } + any_active = any_active ? any_active : (hint8 & 0x80) != 0; + device_read(&hint8, 494, sizeof(uint8_t)); + if (hint8 != 0x00 && hint8 != 0x80) { + if (!force_mbr) { + mbr = 0; + } else { + hint8 = hint8 & 0x80 ? 0x80 : 0x00; + device_write(&hint8, 494, sizeof(uint8_t)); + } + } + any_active = any_active ? any_active : (hint8 & 0x80) != 0; + + char hintc[64]; + device_read(hintc, 4, 8); + if (memcmp(hintc, "_ECH_FS_", 8) == 0) { + if (!force_mbr) { + mbr = 0; + } else { + memset(hintc, 0, 8); + device_write(hintc, 4, 8); + } + } + device_read(hintc, 3, 4); + if (memcmp(hintc, "NTFS", 4) == 0) { + if (!force_mbr) { + mbr = 0; + } else { + memset(hintc, 0, 4); + device_write(hintc, 3, 4); + } + } + device_read(hintc, 54, 3); + if (memcmp(hintc, "FAT", 3) == 0) { + if (!force_mbr) { + mbr = 0; + } else { + memset(hintc, 0, 5); + device_write(hintc, 54, 5); + } + } + device_read(hintc, 82, 3); + if (memcmp(hintc, "FAT", 3) == 0) { + if (!force_mbr) { + mbr = 0; + } else { + memset(hintc, 0, 5); + device_write(hintc, 82, 5); + } + } + device_read(hintc, 3, 5); + if (memcmp(hintc, "FAT32", 5) == 0) { + if (!force_mbr) { + mbr = 0; + } else { + memset(hintc, 0, 5); + device_write(hintc, 3, 5); + } + } + device_read(&hint16, 1080, sizeof(uint16_t)); + hint16 = ENDSWAP(hint16); + if (hint16 == 0xef53) { + if (!force_mbr) { + mbr = 0; + } else { + hint16 = 0; + hint16 = ENDSWAP(hint16); + device_write(&hint16, 1080, sizeof(uint16_t)); + } + } + + if (mbr && !any_active) { + if (!quiet) { + fprintf(stderr, "No active partition found, some systems may not boot.\n"); + fprintf(stderr, "Setting partition 1 as active to work around the issue...\n"); + } + hint8 = 0x80; + device_write(&hint8, 446, sizeof(uint8_t)); + } + } + + if (gpt == 0 && mbr == 0) { + fprintf(stderr, "ERROR: Could not determine if the device has a valid partition table.\n"); + fprintf(stderr, " Please ensure the device has a valid MBR or GPT.\n"); + fprintf(stderr, " Alternatively, pass `--force-mbr` to override these checks.\n"); + fprintf(stderr, " **ONLY DO THIS AT YOUR OWN RISK, DATA LOSS MAY OCCUR!**\n"); + goto cleanup; + } + + size_t stage2_size = bootloader_file_size - 512; + + size_t stage2_sects = DIV_ROUNDUP(stage2_size, 512); + + uint16_t stage2_size_a = (stage2_sects / 2) * 512 + (stage2_sects % 2 ? 512 : 0); + uint16_t stage2_size_b = (stage2_sects / 2) * 512; + + // Default split of stage2 for MBR (consecutive in post MBR gap) + uint64_t stage2_loc_a = 512; + uint64_t stage2_loc_b = stage2_loc_a + stage2_size_a; + + if (gpt) { + if (part_ndx != NULL) { + uint32_t partition_num; + sscanf(part_ndx, "%" SCNu32, &partition_num); + partition_num--; + if (partition_num > ENDSWAP(gpt_header.number_of_partition_entries)) { + fprintf(stderr, "ERROR: Partition number is too large.\n"); + goto cleanup; + } + + struct gpt_entry gpt_entry; + device_read(&gpt_entry, + (ENDSWAP(gpt_header.partition_entry_lba) * lb_size) + + (partition_num * ENDSWAP(gpt_header.size_of_partition_entry)), + sizeof(struct gpt_entry)); + + if (gpt_entry.unique_partition_guid[0] == 0 && + gpt_entry.unique_partition_guid[1] == 0) { + fprintf(stderr, "ERROR: No such partition.\n"); + goto cleanup; + } + + if (!quiet) { + fprintf(stderr, "GPT partition specified. Deploying there instead of embedding.\n"); + } + + stage2_loc_a = ENDSWAP(gpt_entry.starting_lba) * lb_size; + stage2_loc_b = stage2_loc_a + stage2_size_a; + if (stage2_loc_b & (lb_size - 1)) + stage2_loc_b = (stage2_loc_b + lb_size) & ~(lb_size - 1); + } else { + if (!quiet) { + fprintf(stderr, "GPT partition NOT specified. Attempting GPT embedding.\n"); + } + + int64_t max_partition_entry_used = -1; + for (int64_t i = 0; i < (int64_t)ENDSWAP(gpt_header.number_of_partition_entries); i++) { + struct gpt_entry gpt_entry; + device_read(&gpt_entry, + (ENDSWAP(gpt_header.partition_entry_lba) * lb_size) + + (i * ENDSWAP(gpt_header.size_of_partition_entry)), + sizeof(struct gpt_entry)); + + if (gpt_entry.unique_partition_guid[0] != 0 || + gpt_entry.unique_partition_guid[1] != 0) { + if (i > max_partition_entry_used) + max_partition_entry_used = i; + } + } + + stage2_loc_a = (ENDSWAP(gpt_header.partition_entry_lba) + 32) * lb_size; + stage2_loc_a -= stage2_size_a; + stage2_loc_a &= ~(lb_size - 1); + stage2_loc_b = (ENDSWAP(secondary_gpt_header.partition_entry_lba) + 32) * lb_size; + stage2_loc_b -= stage2_size_b; + stage2_loc_b &= ~(lb_size - 1); + + size_t partition_entries_per_lb = + lb_size / ENDSWAP(gpt_header.size_of_partition_entry); + size_t new_partition_array_lba_size = + stage2_loc_a / lb_size - ENDSWAP(gpt_header.partition_entry_lba); + size_t new_partition_entry_count = + new_partition_array_lba_size * partition_entries_per_lb; + + if ((int64_t)new_partition_entry_count <= max_partition_entry_used) { + fprintf(stderr, "ERROR: Cannot embed because there are too many used partition entries.\n"); + goto cleanup; + } + + if (!quiet) { + fprintf(stderr, "New maximum count of partition entries: %zu.\n", new_partition_entry_count); + } + + // Zero out unused partitions + void *empty = calloc(1, ENDSWAP(gpt_header.size_of_partition_entry)); + for (size_t i = max_partition_entry_used + 1; i < new_partition_entry_count; i++) { + device_write(empty, + ENDSWAP(gpt_header.partition_entry_lba) * lb_size + i * ENDSWAP(gpt_header.size_of_partition_entry), + ENDSWAP(gpt_header.size_of_partition_entry)); + } + for (size_t i = max_partition_entry_used + 1; i < new_partition_entry_count; i++) { + device_write(empty, + ENDSWAP(secondary_gpt_header.partition_entry_lba) * lb_size + i * ENDSWAP(secondary_gpt_header.size_of_partition_entry), + ENDSWAP(secondary_gpt_header.size_of_partition_entry)); + } + free(empty); + + uint8_t *partition_array = + malloc(new_partition_entry_count * ENDSWAP(gpt_header.size_of_partition_entry)); + if (partition_array == NULL) { + perror("ERROR"); + goto cleanup; + } + + device_read(partition_array, + ENDSWAP(gpt_header.partition_entry_lba) * lb_size, + new_partition_entry_count * ENDSWAP(gpt_header.size_of_partition_entry)); + + uint32_t crc32_partition_array = + crc32(partition_array, + new_partition_entry_count * ENDSWAP(gpt_header.size_of_partition_entry)); + + free(partition_array); + + gpt_header.partition_entry_array_crc32 = ENDSWAP(crc32_partition_array); + gpt_header.number_of_partition_entries = ENDSWAP(new_partition_entry_count); + gpt_header.crc32 = 0; + gpt_header.crc32 = crc32(&gpt_header, 92); + gpt_header.crc32 = ENDSWAP(gpt_header.crc32); + device_write(&gpt_header, + lb_size, + sizeof(struct gpt_table_header)); + + secondary_gpt_header.partition_entry_array_crc32 = ENDSWAP(crc32_partition_array); + secondary_gpt_header.number_of_partition_entries = + ENDSWAP(new_partition_entry_count); + secondary_gpt_header.crc32 = 0; + secondary_gpt_header.crc32 = crc32(&secondary_gpt_header, 92); + secondary_gpt_header.crc32 = ENDSWAP(secondary_gpt_header.crc32); + device_write(&secondary_gpt_header, + lb_size * ENDSWAP(gpt_header.alternate_lba), + sizeof(struct gpt_table_header)); + } + } else { + if (!quiet) { + fprintf(stderr, "Deploying to MBR.\n"); + } + } + + if (!quiet) { + fprintf(stderr, "Stage 2 to be located at 0x%" PRIx64 " and 0x%" PRIx64 ".\n", + stage2_loc_a, stage2_loc_b); + } + + // Save original timestamp + device_read(timestamp, 218, 6); + + // Save the original partition table of the device + device_read(orig_mbr, 440, 70); + + // Write the bootsector from the bootloader to the device + device_write(&bootloader_img[0], 0, 512); + + // Write the rest of stage 2 to the device + device_write(&bootloader_img[512], stage2_loc_a, stage2_size_a); + device_write(&bootloader_img[512 + stage2_size_a], + stage2_loc_b, stage2_size - stage2_size_a); + + // Hardcode in the bootsector the location of stage 2 halves + stage2_size_a = ENDSWAP(stage2_size_a); + device_write(&stage2_size_a, 0x1a4 + 0, sizeof(uint16_t)); + stage2_size_b = ENDSWAP(stage2_size_b); + device_write(&stage2_size_b, 0x1a4 + 2, sizeof(uint16_t)); + stage2_loc_a = ENDSWAP(stage2_loc_a); + device_write(&stage2_loc_a, 0x1a4 + 4, sizeof(uint64_t)); + stage2_loc_b = ENDSWAP(stage2_loc_b); + device_write(&stage2_loc_b, 0x1a4 + 12, sizeof(uint64_t)); + + // Write back timestamp + device_write(timestamp, 218, 6); + + // Write back the saved partition table to the device + device_write(orig_mbr, 440, 70); + + if (!device_flush_cache()) + goto cleanup; + + if (!quiet) { + fprintf(stderr, "Reminder: Remember to copy the limine.sys file in either\n" + " the root, /boot, /limine, or /boot/limine directories of\n" + " one of the partitions on the device, or boot will fail!\n"); + + fprintf(stderr, "Limine deployed successfully!\n"); + } + + ok = EXIT_SUCCESS; + +cleanup: + reverse_undeploy_data(); + if (ok != EXIT_SUCCESS) { + // If we failed, attempt to reverse deploy process + undeploy(); + } else if (undeploy_file != NULL) { + store_undeploy_data(undeploy_file); + } +undeploy_mode_cleanup: + free_undeploy_data(); + if (cache) + free(cache); + if (device != NULL) + fclose(device); + + return ok; +} diff --git a/src/bootloader/limine/installables/limine-pxe.bin b/src/bootloader/limine/installables/limine-pxe.bin new file mode 100644 index 0000000..8d9dae2 Binary files /dev/null and b/src/bootloader/limine/installables/limine-pxe.bin differ diff --git a/src/bootloader/limine/installables/limine-version.c b/src/bootloader/limine/installables/limine-version.c new file mode 100644 index 0000000..9980fad --- /dev/null +++ b/src/bootloader/limine/installables/limine-version.c @@ -0,0 +1,7 @@ +#include + +#define LIMINE_VERSION "4.20230120.0" + +int main(void) { + puts(LIMINE_VERSION); +} diff --git a/src/bootloader/limine/installables/limine.cfg b/src/bootloader/limine/installables/limine.cfg new file mode 100644 index 0000000..e28f06d --- /dev/null +++ b/src/bootloader/limine/installables/limine.cfg @@ -0,0 +1,13 @@ +# Timeout in seconds that Limine will use before automatically booting. +TIMEOUT=0 + +# The entry name that will be displayed in the boot menu +:Rise + +# Change the protocol line depending on the used protocol. +PROTOCOL=limine + +# Path to the kernel to boot. boot:/// represents the partition on which limine.cfg is located. +KERNEL_PATH=boot:///CPUDRIV + +DEFAULT_ENTRY=0 diff --git a/src/bootloader/limine/installables/limine.h b/src/bootloader/limine/installables/limine.h new file mode 100644 index 0000000..365de87 --- /dev/null +++ b/src/bootloader/limine/installables/limine.h @@ -0,0 +1,466 @@ +/* BSD Zero Clause License */ + +/* Copyright (C) 2022 mintsuki and contributors. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _LIMINE_H +#define _LIMINE_H 1 + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Misc */ + +#ifdef LIMINE_NO_POINTERS +# define LIMINE_PTR(TYPE) uint64_t +#else +# define LIMINE_PTR(TYPE) TYPE +#endif + +#define LIMINE_COMMON_MAGIC 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b + +struct limine_uuid { + uint32_t a; + uint16_t b; + uint16_t c; + uint8_t d[8]; +}; + +#define LIMINE_MEDIA_TYPE_GENERIC 0 +#define LIMINE_MEDIA_TYPE_OPTICAL 1 +#define LIMINE_MEDIA_TYPE_TFTP 2 + +struct limine_file { + uint64_t revision; + LIMINE_PTR(void *) address; + uint64_t size; + LIMINE_PTR(char *) path; + LIMINE_PTR(char *) cmdline; + uint32_t media_type; + uint32_t unused; + uint32_t tftp_ip; + uint32_t tftp_port; + uint32_t partition_index; + uint32_t mbr_disk_id; + struct limine_uuid gpt_disk_uuid; + struct limine_uuid gpt_part_uuid; + struct limine_uuid part_uuid; +}; + +/* Boot info */ + +#define LIMINE_BOOTLOADER_INFO_REQUEST { LIMINE_COMMON_MAGIC, 0xf55038d8e2a1202f, 0x279426fcf5f59740 } + +struct limine_bootloader_info_response { + uint64_t revision; + LIMINE_PTR(char *) name; + LIMINE_PTR(char *) version; +}; + +struct limine_bootloader_info_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_bootloader_info_response *) response; +}; + +/* Stack size */ + +#define LIMINE_STACK_SIZE_REQUEST { LIMINE_COMMON_MAGIC, 0x224ef0460a8e8926, 0xe1cb0fc25f46ea3d } + +struct limine_stack_size_response { + uint64_t revision; +}; + +struct limine_stack_size_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_stack_size_response *) response; + uint64_t stack_size; +}; + +/* HHDM */ + +#define LIMINE_HHDM_REQUEST { LIMINE_COMMON_MAGIC, 0x48dcf1cb8ad2b852, 0x63984e959a98244b } + +struct limine_hhdm_response { + uint64_t revision; + uint64_t offset; +}; + +struct limine_hhdm_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_hhdm_response *) response; +}; + +/* Framebuffer */ + +#define LIMINE_FRAMEBUFFER_REQUEST { LIMINE_COMMON_MAGIC, 0x9d5827dcd881dd75, 0xa3148604f6fab11b } + +#define LIMINE_FRAMEBUFFER_RGB 1 + +struct limine_video_mode { + uint64_t pitch; + uint64_t width; + uint64_t height; + uint16_t bpp; + uint8_t memory_model; + uint8_t red_mask_size; + uint8_t red_mask_shift; + uint8_t green_mask_size; + uint8_t green_mask_shift; + uint8_t blue_mask_size; + uint8_t blue_mask_shift; +}; + +struct limine_framebuffer { + LIMINE_PTR(void *) address; + uint64_t width; + uint64_t height; + uint64_t pitch; + uint16_t bpp; + uint8_t memory_model; + uint8_t red_mask_size; + uint8_t red_mask_shift; + uint8_t green_mask_size; + uint8_t green_mask_shift; + uint8_t blue_mask_size; + uint8_t blue_mask_shift; + uint8_t unused[7]; + uint64_t edid_size; + LIMINE_PTR(void *) edid; + /* Response revision 1 */ + uint64_t mode_count; + LIMINE_PTR(struct limine_video_mode **) modes; +}; + +struct limine_framebuffer_response { + uint64_t revision; + uint64_t framebuffer_count; + LIMINE_PTR(struct limine_framebuffer **) framebuffers; +}; + +struct limine_framebuffer_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_framebuffer_response *) response; +}; + +/* Terminal */ + +#define LIMINE_TERMINAL_REQUEST { LIMINE_COMMON_MAGIC, 0xc8ac59310c2b0844, 0xa68d0c7265d38878 } + +#define LIMINE_TERMINAL_CB_DEC 10 +#define LIMINE_TERMINAL_CB_BELL 20 +#define LIMINE_TERMINAL_CB_PRIVATE_ID 30 +#define LIMINE_TERMINAL_CB_STATUS_REPORT 40 +#define LIMINE_TERMINAL_CB_POS_REPORT 50 +#define LIMINE_TERMINAL_CB_KBD_LEDS 60 +#define LIMINE_TERMINAL_CB_MODE 70 +#define LIMINE_TERMINAL_CB_LINUX 80 + +#define LIMINE_TERMINAL_CTX_SIZE ((uint64_t)(-1)) +#define LIMINE_TERMINAL_CTX_SAVE ((uint64_t)(-2)) +#define LIMINE_TERMINAL_CTX_RESTORE ((uint64_t)(-3)) +#define LIMINE_TERMINAL_FULL_REFRESH ((uint64_t)(-4)) + +/* Response revision 1 */ +#define LIMINE_TERMINAL_OOB_OUTPUT_GET ((uint64_t)(-10)) +#define LIMINE_TERMINAL_OOB_OUTPUT_SET ((uint64_t)(-11)) + +#define LIMINE_TERMINAL_OOB_OUTPUT_OCRNL (1 << 0) +#define LIMINE_TERMINAL_OOB_OUTPUT_OFDEL (1 << 1) +#define LIMINE_TERMINAL_OOB_OUTPUT_OFILL (1 << 2) +#define LIMINE_TERMINAL_OOB_OUTPUT_OLCUC (1 << 3) +#define LIMINE_TERMINAL_OOB_OUTPUT_ONLCR (1 << 4) +#define LIMINE_TERMINAL_OOB_OUTPUT_ONLRET (1 << 5) +#define LIMINE_TERMINAL_OOB_OUTPUT_ONOCR (1 << 6) +#define LIMINE_TERMINAL_OOB_OUTPUT_OPOST (1 << 7) + +struct limine_terminal; + +typedef void (*limine_terminal_write)(struct limine_terminal *, const char *, uint64_t); +typedef void (*limine_terminal_callback)(struct limine_terminal *, uint64_t, uint64_t, uint64_t, uint64_t); + +struct limine_terminal { + uint64_t columns; + uint64_t rows; + LIMINE_PTR(struct limine_framebuffer *) framebuffer; +}; + +struct limine_terminal_response { + uint64_t revision; + uint64_t terminal_count; + LIMINE_PTR(struct limine_terminal **) terminals; + LIMINE_PTR(limine_terminal_write) write; +}; + +struct limine_terminal_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_terminal_response *) response; + LIMINE_PTR(limine_terminal_callback) callback; +}; + +/* 5-level paging */ + +#define LIMINE_5_LEVEL_PAGING_REQUEST { LIMINE_COMMON_MAGIC, 0x94469551da9b3192, 0xebe5e86db7382888 } + +struct limine_5_level_paging_response { + uint64_t revision; +}; + +struct limine_5_level_paging_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_5_level_paging_response *) response; +}; + +/* SMP */ + +#define LIMINE_SMP_REQUEST { LIMINE_COMMON_MAGIC, 0x95a67b819a1b857e, 0xa0b61b723b6a73e0 } + +struct limine_smp_info; + +typedef void (*limine_goto_address)(struct limine_smp_info *); + +#if defined (__x86_64__) || defined (__i386__) + +#define LIMINE_SMP_X2APIC (1 << 0) + +struct limine_smp_info { + uint32_t processor_id; + uint32_t lapic_id; + uint64_t reserved; + LIMINE_PTR(limine_goto_address) goto_address; + uint64_t extra_argument; +}; + +struct limine_smp_response { + uint64_t revision; + uint32_t flags; + uint32_t bsp_lapic_id; + uint64_t cpu_count; + LIMINE_PTR(struct limine_smp_info **) cpus; +}; + +#elif defined (__aarch64__) + +struct limine_smp_info { + uint32_t processor_id; + uint32_t gic_iface_no; + uint64_t mpidr; + uint64_t reserved; + LIMINE_PTR(limine_goto_address) goto_address; + uint64_t extra_argument; +}; + +struct limine_smp_response { + uint64_t revision; + uint32_t flags; + uint64_t bsp_mpidr; + uint64_t cpu_count; + LIMINE_PTR(struct limine_smp_info **) cpus; +}; + +#else +#error Unknown architecture +#endif + +struct limine_smp_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_smp_response *) response; + uint64_t flags; +}; + +/* Memory map */ + +#define LIMINE_MEMMAP_REQUEST { LIMINE_COMMON_MAGIC, 0x67cf3d9d378a806f, 0xe304acdfc50c3c62 } + +#define LIMINE_MEMMAP_USABLE 0 +#define LIMINE_MEMMAP_RESERVED 1 +#define LIMINE_MEMMAP_ACPI_RECLAIMABLE 2 +#define LIMINE_MEMMAP_ACPI_NVS 3 +#define LIMINE_MEMMAP_BAD_MEMORY 4 +#define LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE 5 +#define LIMINE_MEMMAP_KERNEL_AND_MODULES 6 +#define LIMINE_MEMMAP_FRAMEBUFFER 7 + +struct limine_memmap_entry { + uint64_t base; + uint64_t length; + uint64_t type; +}; + +struct limine_memmap_response { + uint64_t revision; + uint64_t entry_count; + LIMINE_PTR(struct limine_memmap_entry **) entries; +}; + +struct limine_memmap_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_memmap_response *) response; +}; + +/* Entry point */ + +#define LIMINE_ENTRY_POINT_REQUEST { LIMINE_COMMON_MAGIC, 0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a } + +typedef void (*limine_entry_point)(void); + +struct limine_entry_point_response { + uint64_t revision; +}; + +struct limine_entry_point_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_entry_point_response *) response; + LIMINE_PTR(limine_entry_point) entry; +}; + +/* Kernel File */ + +#define LIMINE_KERNEL_FILE_REQUEST { LIMINE_COMMON_MAGIC, 0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69 } + +struct limine_kernel_file_response { + uint64_t revision; + LIMINE_PTR(struct limine_file *) kernel_file; +}; + +struct limine_kernel_file_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_kernel_file_response *) response; +}; + +/* Module */ + +#define LIMINE_MODULE_REQUEST { LIMINE_COMMON_MAGIC, 0x3e7e279702be32af, 0xca1c4f3bd1280cee } + +struct limine_module_response { + uint64_t revision; + uint64_t module_count; + LIMINE_PTR(struct limine_file **) modules; +}; + +struct limine_module_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_module_response *) response; +}; + +/* RSDP */ + +#define LIMINE_RSDP_REQUEST { LIMINE_COMMON_MAGIC, 0xc5e77b6b397e7b43, 0x27637845accdcf3c } + +struct limine_rsdp_response { + uint64_t revision; + LIMINE_PTR(void *) address; +}; + +struct limine_rsdp_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_rsdp_response *) response; +}; + +/* SMBIOS */ + +#define LIMINE_SMBIOS_REQUEST { LIMINE_COMMON_MAGIC, 0x9e9046f11e095391, 0xaa4a520fefbde5ee } + +struct limine_smbios_response { + uint64_t revision; + LIMINE_PTR(void *) entry_32; + LIMINE_PTR(void *) entry_64; +}; + +struct limine_smbios_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_smbios_response *) response; +}; + +/* EFI system table */ + +#define LIMINE_EFI_SYSTEM_TABLE_REQUEST { LIMINE_COMMON_MAGIC, 0x5ceba5163eaaf6d6, 0x0a6981610cf65fcc } + +struct limine_efi_system_table_response { + uint64_t revision; + LIMINE_PTR(void *) address; +}; + +struct limine_efi_system_table_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_efi_system_table_response *) response; +}; + +/* Boot time */ + +#define LIMINE_BOOT_TIME_REQUEST { LIMINE_COMMON_MAGIC, 0x502746e184c088aa, 0xfbc5ec83e6327893 } + +struct limine_boot_time_response { + uint64_t revision; + int64_t boot_time; +}; + +struct limine_boot_time_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_boot_time_response *) response; +}; + +/* Kernel address */ + +#define LIMINE_KERNEL_ADDRESS_REQUEST { LIMINE_COMMON_MAGIC, 0x71ba76863cc55f63, 0xb2644a48c516a487 } + +struct limine_kernel_address_response { + uint64_t revision; + uint64_t physical_base; + uint64_t virtual_base; +}; + +struct limine_kernel_address_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_kernel_address_response *) response; +}; + +/* Device Tree Blob */ + +#define LIMINE_DTB_REQUEST { LIMINE_COMMON_MAGIC, 0xb40ddb48fb54bac7, 0x545081493f81ffb7 } + +struct limine_dtb_response { + uint64_t revision; + LIMINE_PTR(void *) dtb_ptr; +}; + +struct limine_dtb_request { + uint64_t id[4]; + uint64_t revision; + LIMINE_PTR(struct limine_dtb_response *) response; +}; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/bootloader/limine/installables/limine.sys b/src/bootloader/limine/installables/limine.sys new file mode 100755 index 0000000..aa3cb4a Binary files /dev/null and b/src/bootloader/limine/installables/limine.sys differ diff --git a/src/bootloader/limine/installer.zig b/src/bootloader/limine/installer.zig new file mode 100644 index 0000000..8df1de2 --- /dev/null +++ b/src/bootloader/limine/installer.zig @@ -0,0 +1,1676 @@ +const std = @import("std"); +const assert = std.debug.assert; +const builtin = @import("builtin"); +const log = std.log; + +comptime { + assert(@import("builtin").cpu.arch.endian() == .Little); +} + +const InstallerError = error{ + not_64_bit, + unable_to_get_arguments, + image_file_not_found, + unable_to_get_file_size, + unable_to_allocate_memory_for_file, + unable_to_read_file_into_memory, + secondary_GPT_header_invalid, + invalid_partition_table, +}; + +const GPT = struct { + const Header = extern struct { + signature: u64 align(4), + revision: u32, + header_size: u32, + CRC32: u32, + _reserved0: u32, + + LBA: u64 align(4), + alternate_LBA: u64 align(4), + first_usable_LBA: u64 align(4), + last_usable_LBA: u64 align(4), + + disk_GUID_0: [2]u64 align(4), + + partition_entry_LBA: u64 align(4), + partition_entry_count: u32, + partition_entry_size: u32, + partition_entry_array_CRC32: u32, + + comptime { + const expected_size = @sizeOf([8]u8) + (4 * @sizeOf(u32)) + (4 * @sizeOf(u64)) + (2 * @sizeOf(u64)) + @sizeOf(u64) + (3 * @sizeOf(u32)); + std.debug.assert(expected_size == 92); + std.debug.assert(@sizeOf(Header) == expected_size); + } + }; + + const Entry = extern struct { + partition_type_guid0: u64, + partition_type_guid1: u64, + unique_partition_guid0: u64, + unique_partition_guid1: u64, + starting_LBA: u64, + ending_LBA: u64, + attributes: u64, + partition_name: [36]u16, + + comptime { + const expected_size = @sizeOf([2]u64) + (2 * @sizeOf(u64)) + (3 * @sizeOf(u64)) + (36 * @sizeOf(u16)); + std.debug.assert(expected_size == 128); + std.debug.assert(@sizeOf(Entry) == expected_size); + } + }; +}; + +var format_buffer: [8192]u8 = undefined; +fn print(comptime format: []const u8, args: anytype) void { + const format_buffer_slice = std.fmt.bufPrint(&format_buffer, format, args) catch @panic("Unable to format stdout buffer\n"); + stderr(format_buffer_slice); +} + +fn stderr(bytes: []const u8) void { + _ = std.io.getStdErr().write(bytes) catch @panic("Unable to write to stdout\n"); +} + +fn print_error_and_exit(e: InstallerError) InstallerError { + print("An error occurred: {}\n", .{e}); + return e; +} + +const gpt_header_signature = @as(*align(1) const u64, @ptrCast("EFI PART")).*; + +fn div_roundup(a: u64, b: u64) u64 { + return (((a) + ((b) - 1)) / (b)); +} + +fn crc32(bytes: []const u8) u32 { + var result: u32 = std.math.maxInt(u32); + for (bytes) |byte| { + result = (result >> 8) ^ crc32_table[@as(u8, @truncate(result ^ byte))]; + } + + result ^= std.math.maxInt(u32); + return result; +} + +const crc32_table = [_]u32{ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; + +pub const hdd = [_]u8{ + 0xeb, 0x3c, 0x90, 0x4c, 0x49, 0x4d, 0x49, 0x4e, 0x45, 0x20, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x49, 0x4d, 0x49, 0x4e, + 0x45, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfa, 0xfc, + 0xea, 0x45, 0x7c, 0x00, 0x00, 0x31, 0xf6, 0x8e, 0xde, 0x8e, 0xc6, 0x8e, 0xd6, 0xbc, 0x00, 0x7c, + 0xfb, 0x80, 0xfa, 0x80, 0x0f, 0x82, 0x10, 0x01, 0x80, 0xfa, 0x8f, 0x0f, 0x87, 0x08, 0x01, 0xb4, + 0x41, 0xbb, 0xaa, 0x55, 0xcd, 0x13, 0x0f, 0x82, 0xfc, 0x00, 0x81, 0xfb, 0x55, 0xaa, 0x0f, 0x85, + 0xf3, 0x00, 0x68, 0x00, 0x70, 0x07, 0xbf, 0xa8, 0x7d, 0x66, 0x8b, 0x05, 0x66, 0x8b, 0x6d, 0x04, + 0x31, 0xdb, 0x66, 0x31, 0xc9, 0x8b, 0x4d, 0xfc, 0xe8, 0x55, 0x00, 0x0f, 0x82, 0xd5, 0x00, 0x66, + 0x8b, 0x45, 0x08, 0x66, 0x8b, 0x6d, 0x0c, 0x01, 0xcb, 0x8b, 0x4d, 0xfe, 0xe8, 0x41, 0x00, 0x0f, + 0x82, 0xc0, 0x00, 0x0f, 0x01, 0x16, 0x4d, 0x7d, 0xfa, 0x0f, 0x20, 0xc0, 0x0f, 0xba, 0xe8, 0x00, + 0x0f, 0x22, 0xc0, 0xea, 0x79, 0x7d, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x60, 0xbe, 0x00, 0x80, 0xc7, 0x04, 0x10, 0x00, 0xc7, 0x44, 0x02, 0x01, 0x00, 0x89, 0x5c, 0x04, + 0x8c, 0x44, 0x06, 0x52, 0x56, 0x66, 0x50, 0x66, 0x55, 0xb4, 0x48, 0xbe, 0x10, 0x80, 0xc7, 0x04, + 0x1e, 0x00, 0xcd, 0x13, 0x72, 0x45, 0x8b, 0x6c, 0x18, 0x89, 0xc8, 0x66, 0xc1, 0xe9, 0x10, 0x89, + 0xca, 0x31, 0xc9, 0xf7, 0xf5, 0x85, 0xd2, 0x0f, 0x95, 0xc1, 0x01, 0xc1, 0x66, 0x5a, 0x66, 0x58, + 0x5e, 0x66, 0xf7, 0xf5, 0x66, 0x89, 0x44, 0x08, 0x66, 0xc7, 0x44, 0x0c, 0x00, 0x00, 0x00, 0x00, + 0x5a, 0xb4, 0x42, 0xf8, 0xcd, 0x13, 0x72, 0x13, 0x01, 0x6c, 0x04, 0x66, 0x31, 0xdb, 0x66, 0xff, + 0x44, 0x08, 0x0f, 0x90, 0xc3, 0x66, 0x01, 0x5c, 0x0c, 0xe2, 0xe6, 0x61, 0xc3, 0x17, 0x00, 0x4b, + 0x7d, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x9a, 0xcf, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, + 0x92, 0xcf, 0x00, 0x46, 0x46, 0x46, 0x46, 0x46, 0x81, 0xc6, 0x30, 0x4f, 0x68, 0x00, 0xb8, 0x07, + 0x26, 0x89, 0x36, 0x00, 0x00, 0xfb, 0xf4, 0xeb, 0xfd, 0xb8, 0x10, 0x00, 0x00, 0x00, 0x8e, 0xd8, + 0x8e, 0xc0, 0x8e, 0xe0, 0x8e, 0xe8, 0x8e, 0xd0, 0x81, 0xe2, 0xff, 0x00, 0x00, 0x00, 0x6a, 0x00, + 0x52, 0x68, 0xc3, 0x48, 0x00, 0x00, 0x68, 0xe0, 0x0a, 0x07, 0x00, 0xe8, 0x60, 0x82, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa, + 0xfc, 0x30, 0xc0, 0xbf, 0xd8, 0x0a, 0x07, 0x00, 0xb9, 0xd8, 0x0a, 0x07, 0x00, 0x81, 0xe9, 0xd8, + 0x0a, 0x07, 0x00, 0xf3, 0xaa, 0xe9, 0x7a, 0x00, 0x00, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + 0x56, 0x57, 0x8b, 0x44, 0x24, 0x0c, 0x89, 0xc7, 0x8b, 0x74, 0x24, 0x10, 0x8b, 0x4c, 0x24, 0x14, + 0xf3, 0xa4, 0x5f, 0x5e, 0xc3, 0x57, 0x8b, 0x54, 0x24, 0x08, 0x89, 0xd7, 0x8b, 0x44, 0x24, 0x0c, + 0x8b, 0x4c, 0x24, 0x10, 0xf3, 0xaa, 0x89, 0xd0, 0x5f, 0xc3, 0x56, 0x57, 0x8b, 0x44, 0x24, 0x0c, + 0x89, 0xc7, 0x8b, 0x74, 0x24, 0x10, 0x8b, 0x4c, 0x24, 0x14, 0x39, 0xf7, 0x77, 0x04, 0xf3, 0xa4, + 0xeb, 0x0c, 0x8d, 0x7c, 0x0f, 0xff, 0x8d, 0x74, 0x0e, 0xff, 0xfd, 0xf3, 0xa4, 0xfc, 0x5f, 0x5e, + 0xc3, 0x56, 0x57, 0x8b, 0x7c, 0x24, 0x0c, 0x8b, 0x74, 0x24, 0x10, 0x8b, 0x4c, 0x24, 0x14, 0xf3, + 0xa6, 0x74, 0x0b, 0x8a, 0x47, 0xff, 0x2a, 0x46, 0xff, 0x0f, 0xbe, 0xc0, 0xeb, 0x02, 0x31, 0xc0, + 0x5f, 0x5e, 0xc3, 0xcc, 0x57, 0x56, 0x0f, 0xb6, 0x74, 0x24, 0x14, 0x8b, 0x7c, 0x24, 0x18, 0xff, + 0x74, 0x24, 0x10, 0xff, 0x74, 0x24, 0x10, 0x68, 0x00, 0x80, 0x00, 0x00, 0xe8, 0x17, 0x00, 0x00, + 0x00, 0x83, 0xc4, 0x0c, 0xbc, 0x00, 0x7c, 0x00, 0x00, 0x31, 0xed, 0x57, 0x56, 0x6a, 0x00, 0x68, + 0x00, 0x80, 0x00, 0x00, 0xc3, 0xcc, 0xcc, 0xcc, 0x55, 0x53, 0x57, 0x56, 0x8b, 0x54, 0x24, 0x1c, + 0xb8, 0xfd, 0xff, 0xff, 0xff, 0x83, 0xfa, 0x12, 0x0f, 0x82, 0xa2, 0x00, 0x00, 0x00, 0x8b, 0x4c, + 0x24, 0x18, 0x80, 0x39, 0x1f, 0x0f, 0x85, 0x95, 0x00, 0x00, 0x00, 0x80, 0x79, 0x01, 0x8b, 0x0f, + 0x85, 0x8b, 0x00, 0x00, 0x00, 0x80, 0x79, 0x02, 0x08, 0x0f, 0x85, 0x81, 0x00, 0x00, 0x00, 0x0f, + 0xb6, 0x59, 0x03, 0x83, 0xfb, 0x1f, 0x77, 0x78, 0x8d, 0x71, 0x0a, 0xf6, 0xc3, 0x04, 0x74, 0x0e, + 0x0f, 0xb6, 0x3e, 0x8d, 0x6a, 0xf4, 0x39, 0xfd, 0x72, 0x66, 0x8d, 0x74, 0x37, 0x02, 0xf6, 0xc3, + 0x08, 0x75, 0x04, 0x89, 0xf7, 0xeb, 0x13, 0x89, 0xf5, 0x29, 0xcd, 0x39, 0xd5, 0x73, 0x51, 0x8d, + 0x7e, 0x01, 0x45, 0x80, 0x3e, 0x00, 0x89, 0xfe, 0x75, 0xf1, 0xf6, 0xc3, 0x10, 0x75, 0x04, 0x89, + 0xfd, 0xeb, 0x13, 0x89, 0xfe, 0x29, 0xce, 0x39, 0xd6, 0x73, 0x35, 0x8d, 0x6f, 0x01, 0x46, 0x80, + 0x3f, 0x00, 0x89, 0xef, 0x75, 0xf1, 0x83, 0xe3, 0x02, 0x01, 0xeb, 0x01, 0xd1, 0x29, 0xd9, 0x83, + 0xf9, 0x08, 0x7c, 0x1c, 0x83, 0xc1, 0xf8, 0x51, 0x53, 0xff, 0x74, 0x24, 0x1c, 0xe8, 0x16, 0x00, + 0x00, 0x00, 0x83, 0xc4, 0x0c, 0x31, 0xc9, 0x85, 0xc0, 0x0f, 0x94, 0xc1, 0x8d, 0x44, 0x49, 0xfd, + 0x5e, 0x5f, 0x5b, 0x5d, 0xc3, 0xcc, 0xcc, 0xcc, 0x55, 0x53, 0x57, 0x56, 0x81, 0xec, 0x40, 0x06, + 0x00, 0x00, 0x8b, 0x84, 0x24, 0x54, 0x06, 0x00, 0x00, 0x8b, 0x8c, 0x24, 0x58, 0x06, 0x00, 0x00, + 0x8d, 0x54, 0x24, 0x38, 0x89, 0x4a, 0xe4, 0x03, 0x8c, 0x24, 0x5c, 0x06, 0x00, 0x00, 0x89, 0x4a, + 0xe8, 0x31, 0xc9, 0x89, 0x4a, 0xec, 0x89, 0x4a, 0xf0, 0x89, 0x4a, 0xf4, 0x89, 0x42, 0xfc, 0x89, + 0x42, 0xf8, 0x8b, 0x4c, 0x24, 0x28, 0x85, 0xc9, 0x7e, 0x04, 0x89, 0xca, 0xeb, 0x38, 0x8b, 0x74, + 0x24, 0x1c, 0x8b, 0x44, 0x24, 0x20, 0x39, 0xc6, 0x74, 0x14, 0x8d, 0x56, 0x01, 0x89, 0x54, 0x24, + 0x1c, 0x0f, 0xb6, 0x36, 0xd3, 0xe6, 0x09, 0x74, 0x24, 0x24, 0x89, 0xd6, 0xeb, 0x0a, 0xc7, 0x44, + 0x24, 0x2c, 0x01, 0x00, 0x00, 0x00, 0x89, 0xc6, 0x8d, 0x51, 0x08, 0x89, 0x54, 0x24, 0x28, 0x83, + 0xf9, 0xf9, 0x89, 0xd1, 0x7c, 0xd0, 0x8b, 0x74, 0x24, 0x24, 0x89, 0x74, 0x24, 0x14, 0xd1, 0xee, + 0x89, 0x74, 0x24, 0x24, 0x8d, 0x5a, 0xff, 0x89, 0x5c, 0x24, 0x28, 0x83, 0xfb, 0x01, 0x77, 0x45, + 0x8b, 0x4c, 0x24, 0x1c, 0x8b, 0x7c, 0x24, 0x20, 0x83, 0xc2, 0xf7, 0x8d, 0x5a, 0x08, 0x39, 0xf9, + 0x74, 0x18, 0x8d, 0x41, 0x01, 0x89, 0x44, 0x24, 0x1c, 0x0f, 0xb6, 0x29, 0x89, 0xd9, 0xd3, 0xe5, + 0x09, 0xee, 0x89, 0x74, 0x24, 0x24, 0x89, 0xc1, 0xeb, 0x0a, 0xc7, 0x44, 0x24, 0x2c, 0x01, 0x00, + 0x00, 0x00, 0x89, 0xf9, 0x83, 0xc2, 0x10, 0x89, 0x54, 0x24, 0x28, 0x83, 0xfb, 0xfa, 0x89, 0xda, + 0x7c, 0xc9, 0x83, 0xc3, 0x08, 0x89, 0xf5, 0xc1, 0xed, 0x02, 0x89, 0x6c, 0x24, 0x24, 0x8d, 0x53, + 0xfe, 0x89, 0x54, 0x24, 0x28, 0xb8, 0xfd, 0xff, 0xff, 0xff, 0x83, 0xe6, 0x03, 0x0f, 0x84, 0xeb, + 0x00, 0x00, 0x00, 0x83, 0xfe, 0x02, 0x0f, 0x84, 0x4c, 0x01, 0x00, 0x00, 0x83, 0xfe, 0x01, 0x0f, + 0x85, 0xcf, 0x04, 0x00, 0x00, 0xb8, 0xe0, 0xff, 0xff, 0xff, 0x66, 0xc7, 0x44, 0x04, 0x58, 0x00, + 0x00, 0x83, 0xc0, 0x02, 0x75, 0xf4, 0xc7, 0x44, 0x24, 0x46, 0x18, 0x00, 0x98, 0x00, 0x66, 0xc7, + 0x44, 0x24, 0x4a, 0x70, 0x00, 0xb8, 0xe8, 0xff, 0xff, 0xff, 0x8d, 0x88, 0x18, 0x01, 0x00, 0x00, + 0x66, 0x89, 0x8c, 0x44, 0x88, 0x00, 0x00, 0x00, 0x40, 0x75, 0xef, 0x31, 0xc0, 0x66, 0x89, 0x84, + 0x44, 0x88, 0x00, 0x00, 0x00, 0x40, 0x3d, 0x90, 0x00, 0x00, 0x00, 0x75, 0xf0, 0xb8, 0xf8, 0xff, + 0xff, 0xff, 0x8d, 0x88, 0x20, 0x01, 0x00, 0x00, 0x66, 0x89, 0x8c, 0x44, 0xb8, 0x01, 0x00, 0x00, + 0x40, 0x75, 0xef, 0xb8, 0x90, 0xff, 0xff, 0xff, 0x8d, 0x88, 0x00, 0x01, 0x00, 0x00, 0x66, 0x89, + 0x8c, 0x44, 0x98, 0x02, 0x00, 0x00, 0x40, 0x75, 0xef, 0xc7, 0x84, 0x24, 0x98, 0x02, 0x00, 0x00, + 0x1d, 0x01, 0x00, 0x00, 0xb8, 0xe0, 0xff, 0xff, 0xff, 0x66, 0xc7, 0x84, 0x04, 0xbc, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x83, 0xc0, 0x02, 0x75, 0xf1, 0x66, 0xc7, 0x84, 0x24, 0xa6, 0x02, 0x00, 0x00, + 0x20, 0x00, 0x31, 0xc0, 0x66, 0x89, 0x84, 0x44, 0xbc, 0x02, 0x00, 0x00, 0x40, 0x83, 0xf8, 0x20, + 0x75, 0xf2, 0xc7, 0x84, 0x24, 0xfc, 0x04, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x8d, 0x4c, 0x24, + 0x1c, 0x8d, 0x54, 0x24, 0x38, 0x8d, 0x84, 0x24, 0x9c, 0x02, 0x00, 0x00, 0x50, 0xe8, 0x66, 0x04, + 0x00, 0x00, 0x83, 0xc4, 0x04, 0x85, 0xc0, 0x74, 0x5f, 0xe9, 0xf6, 0x03, 0x00, 0x00, 0x8b, 0x4c, + 0x24, 0x1c, 0x8b, 0x54, 0x24, 0x20, 0x29, 0xca, 0x83, 0xfa, 0x04, 0x0f, 0x8c, 0xe3, 0x03, 0x00, + 0x00, 0x0f, 0xb7, 0x19, 0x0f, 0xb7, 0x51, 0x02, 0x81, 0xf2, 0xff, 0xff, 0x00, 0x00, 0x39, 0xd3, + 0x0f, 0x85, 0xce, 0x03, 0x00, 0x00, 0x83, 0xc1, 0x04, 0x89, 0x4c, 0x24, 0x1c, 0x85, 0xdb, 0x74, + 0x1d, 0x8b, 0x4c, 0x24, 0x1c, 0x8b, 0x54, 0x24, 0x34, 0x8d, 0x71, 0x01, 0x8d, 0x7a, 0x01, 0x4b, + 0x89, 0x74, 0x24, 0x1c, 0x8a, 0x09, 0x89, 0x7c, 0x24, 0x34, 0x88, 0x0a, 0x75, 0xe3, 0x31, 0xc0, + 0x89, 0x44, 0x24, 0x24, 0x89, 0x44, 0x24, 0x28, 0xf6, 0x44, 0x24, 0x14, 0x01, 0x0f, 0x84, 0xef, + 0xfd, 0xff, 0xff, 0xe9, 0x6d, 0x03, 0x00, 0x00, 0x83, 0xfa, 0x04, 0x77, 0x45, 0x8b, 0x4c, 0x24, + 0x1c, 0x8b, 0x74, 0x24, 0x20, 0x83, 0xc3, 0xf6, 0x8d, 0x53, 0x08, 0x39, 0xf1, 0x74, 0x18, 0x8d, + 0x79, 0x01, 0x89, 0x7c, 0x24, 0x1c, 0x0f, 0xb6, 0x01, 0x89, 0xd1, 0xd3, 0xe0, 0x09, 0xc5, 0x89, + 0x6c, 0x24, 0x24, 0x89, 0xf9, 0xeb, 0x0a, 0xc7, 0x44, 0x24, 0x2c, 0x01, 0x00, 0x00, 0x00, 0x89, + 0xf1, 0x83, 0xc3, 0x10, 0x89, 0x5c, 0x24, 0x28, 0x83, 0xfa, 0xfd, 0x89, 0xd3, 0x7c, 0xc9, 0x83, + 0xc2, 0x08, 0x89, 0xe9, 0xc1, 0xe9, 0x05, 0x89, 0x4c, 0x24, 0x24, 0x8d, 0x42, 0xfb, 0x89, 0x44, + 0x24, 0x28, 0x83, 0xf8, 0x04, 0x77, 0x55, 0x89, 0x6c, 0x24, 0x08, 0x89, 0xcd, 0x8b, 0x4c, 0x24, + 0x1c, 0x8b, 0x74, 0x24, 0x20, 0x83, 0xc2, 0xf3, 0x8d, 0x42, 0x08, 0x39, 0xf1, 0x74, 0x1c, 0x8d, + 0x79, 0x01, 0x89, 0x7c, 0x24, 0x1c, 0x0f, 0xb6, 0x19, 0x89, 0xc1, 0xd3, 0xe3, 0x89, 0xe9, 0x09, + 0xd9, 0x89, 0xcd, 0x89, 0x4c, 0x24, 0x24, 0x89, 0xf9, 0xeb, 0x0a, 0xc7, 0x44, 0x24, 0x2c, 0x01, + 0x00, 0x00, 0x00, 0x89, 0xf1, 0x83, 0xc2, 0x10, 0x89, 0x54, 0x24, 0x28, 0x83, 0xf8, 0xfd, 0x89, + 0xc2, 0x7c, 0xc5, 0x83, 0xc0, 0x08, 0x89, 0xe9, 0x8b, 0x6c, 0x24, 0x08, 0x83, 0xe5, 0x1f, 0x89, + 0x4c, 0x24, 0x04, 0x89, 0xca, 0xc1, 0xea, 0x05, 0x89, 0x54, 0x24, 0x24, 0x8d, 0x48, 0xfb, 0x89, + 0x4c, 0x24, 0x28, 0x83, 0xf9, 0x03, 0x77, 0x43, 0x8b, 0x7c, 0x24, 0x1c, 0x8b, 0x74, 0x24, 0x20, + 0x83, 0xc0, 0xf3, 0x8d, 0x48, 0x08, 0x39, 0xf7, 0x74, 0x16, 0x8d, 0x5f, 0x01, 0x89, 0x5c, 0x24, + 0x1c, 0x0f, 0xb6, 0x3f, 0xd3, 0xe7, 0x09, 0xfa, 0x89, 0x54, 0x24, 0x24, 0x89, 0xdf, 0xeb, 0x0a, + 0xc7, 0x44, 0x24, 0x2c, 0x01, 0x00, 0x00, 0x00, 0x89, 0xf7, 0x83, 0xc0, 0x10, 0x89, 0x44, 0x24, + 0x28, 0x83, 0xf9, 0xfc, 0x89, 0xc8, 0x7c, 0xcb, 0x83, 0xc1, 0x08, 0x89, 0xd0, 0xc1, 0xe8, 0x04, + 0x89, 0x44, 0x24, 0x24, 0x83, 0xc1, 0xfc, 0x89, 0x4c, 0x24, 0x28, 0x83, 0xfd, 0x1d, 0x0f, 0x87, + 0x56, 0x02, 0x00, 0x00, 0x8b, 0x74, 0x24, 0x04, 0x83, 0xe6, 0x1f, 0x83, 0xfe, 0x1d, 0x0f, 0x87, + 0x46, 0x02, 0x00, 0x00, 0xc7, 0x04, 0x24, 0xfd, 0xff, 0xff, 0xff, 0x81, 0xc5, 0x01, 0x01, 0x00, + 0x00, 0x89, 0x6c, 0x24, 0x08, 0x46, 0x89, 0x74, 0x24, 0x04, 0x83, 0xe2, 0x0f, 0xbe, 0xed, 0xff, + 0xff, 0xff, 0xc6, 0x84, 0x34, 0x13, 0x05, 0x00, 0x00, 0x00, 0x46, 0x75, 0xf5, 0x83, 0xc2, 0x03, + 0x31, 0xff, 0x8b, 0x74, 0x24, 0x20, 0x83, 0xf9, 0x02, 0x7f, 0x39, 0x8b, 0x6c, 0x24, 0x1c, 0x39, + 0xf5, 0x74, 0x17, 0x8d, 0x5d, 0x01, 0x89, 0x5c, 0x24, 0x1c, 0x0f, 0xb6, 0x6d, 0x00, 0xd3, 0xe5, + 0x09, 0xe8, 0x89, 0x44, 0x24, 0x24, 0x89, 0xdd, 0xeb, 0x0a, 0xc7, 0x44, 0x24, 0x2c, 0x01, 0x00, + 0x00, 0x00, 0x89, 0xf5, 0x8d, 0x59, 0x08, 0x89, 0x5c, 0x24, 0x28, 0x83, 0xf9, 0xfb, 0x89, 0xd9, + 0x7c, 0xcd, 0xeb, 0x02, 0x89, 0xcb, 0x89, 0xc1, 0xc1, 0xe9, 0x03, 0x89, 0x4c, 0x24, 0x24, 0x83, + 0xc3, 0xfd, 0x89, 0x5c, 0x24, 0x28, 0x24, 0x07, 0x0f, 0xb6, 0xaf, 0x88, 0x0a, 0x07, 0x00, 0x88, + 0x84, 0x2c, 0x00, 0x05, 0x00, 0x00, 0x39, 0xd7, 0x8d, 0x7f, 0x01, 0x89, 0xc8, 0x89, 0xd9, 0x75, + 0x95, 0x8d, 0x4c, 0x24, 0x38, 0x8d, 0x94, 0x24, 0x00, 0x05, 0x00, 0x00, 0x6a, 0x13, 0xe8, 0x82, + 0x03, 0x00, 0x00, 0x83, 0xc4, 0x04, 0x85, 0xc0, 0x0f, 0x85, 0x87, 0x01, 0x00, 0x00, 0x83, 0xbc, + 0x24, 0x98, 0x02, 0x00, 0x00, 0xff, 0x0f, 0x84, 0x85, 0x01, 0x00, 0x00, 0x8b, 0x5c, 0x24, 0x08, + 0x8b, 0x44, 0x24, 0x04, 0x8d, 0x2c, 0x18, 0x31, 0xff, 0x89, 0x6c, 0x24, 0x18, 0x8d, 0x4c, 0x24, + 0x1c, 0x8d, 0x54, 0x24, 0x38, 0xe8, 0xf8, 0x02, 0x00, 0x00, 0x3b, 0x84, 0x24, 0x98, 0x02, 0x00, + 0x00, 0x0f, 0x8f, 0x5a, 0x01, 0x00, 0x00, 0x89, 0xc1, 0x83, 0xf8, 0x12, 0x74, 0x35, 0x83, 0xf9, + 0x11, 0x74, 0x47, 0x83, 0xf9, 0x10, 0x0f, 0x85, 0xc7, 0x00, 0x00, 0x00, 0x85, 0xff, 0x0f, 0x84, + 0x3d, 0x01, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x10, 0x03, 0x00, 0x00, 0x00, 0x8d, 0x84, 0x24, 0x00, + 0x05, 0x00, 0x00, 0x0f, 0xb6, 0x44, 0x07, 0xff, 0x89, 0x44, 0x24, 0x0c, 0xb8, 0x02, 0x00, 0x00, + 0x00, 0xeb, 0x2c, 0xc7, 0x44, 0x24, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xc7, 0x44, 0x24, 0x10, 0x0b, + 0x00, 0x00, 0x00, 0xb8, 0x07, 0x00, 0x00, 0x00, 0xeb, 0x15, 0xc7, 0x44, 0x24, 0x0c, 0x00, 0x00, + 0x00, 0x00, 0xc7, 0x44, 0x24, 0x10, 0x03, 0x00, 0x00, 0x00, 0xb8, 0x03, 0x00, 0x00, 0x00, 0x8b, + 0x54, 0x24, 0x28, 0x39, 0xc2, 0x7d, 0x37, 0x8b, 0x5c, 0x24, 0x1c, 0x8b, 0x74, 0x24, 0x20, 0x39, + 0xf3, 0x74, 0x16, 0x8d, 0x6b, 0x01, 0x89, 0x6c, 0x24, 0x1c, 0x0f, 0xb6, 0x1b, 0x89, 0xd1, 0xd3, + 0xe3, 0x09, 0x5c, 0x24, 0x24, 0x89, 0xeb, 0xeb, 0x0a, 0xc7, 0x44, 0x24, 0x2c, 0x01, 0x00, 0x00, + 0x00, 0x89, 0xf3, 0x83, 0xc2, 0x08, 0x89, 0x54, 0x24, 0x28, 0x39, 0xc2, 0x7c, 0xd1, 0x8b, 0x6c, + 0x24, 0x24, 0x31, 0xf6, 0x4e, 0x89, 0xc1, 0xd3, 0xe6, 0xf7, 0xd6, 0x21, 0xee, 0x03, 0x74, 0x24, + 0x10, 0xd3, 0xed, 0x89, 0x6c, 0x24, 0x24, 0x29, 0xc2, 0x89, 0x54, 0x24, 0x28, 0x8b, 0x6c, 0x24, + 0x18, 0x89, 0xe9, 0x29, 0xf9, 0x39, 0xce, 0x8b, 0x5c, 0x24, 0x08, 0x8b, 0x4c, 0x24, 0x0c, 0x76, + 0x0b, 0xeb, 0x7e, 0x39, 0xfd, 0xbe, 0x01, 0x00, 0x00, 0x00, 0x74, 0x75, 0x88, 0x8c, 0x3c, 0x00, + 0x05, 0x00, 0x00, 0x47, 0x4e, 0x75, 0xf5, 0x39, 0xfd, 0x0f, 0x87, 0xee, 0xfe, 0xff, 0xff, 0x80, + 0xbc, 0x24, 0x00, 0x06, 0x00, 0x00, 0x00, 0x74, 0x58, 0x8d, 0x4c, 0x24, 0x38, 0x8d, 0x94, 0x24, + 0x00, 0x05, 0x00, 0x00, 0x53, 0xe8, 0x2b, 0x02, 0x00, 0x00, 0x83, 0xc4, 0x04, 0x85, 0xc0, 0x75, + 0x34, 0x8d, 0x94, 0x1c, 0x00, 0x05, 0x00, 0x00, 0x8d, 0x8c, 0x24, 0x9c, 0x02, 0x00, 0x00, 0xff, + 0x74, 0x24, 0x04, 0xe8, 0x0d, 0x02, 0x00, 0x00, 0x83, 0xc4, 0x04, 0x85, 0xc0, 0x0f, 0x84, 0x0a, + 0xfc, 0xff, 0xff, 0xeb, 0x1f, 0x31, 0xc0, 0x83, 0x7c, 0x24, 0x2c, 0x00, 0x0f, 0x94, 0xc0, 0x8d, + 0x44, 0x40, 0xfd, 0xeb, 0x0f, 0x89, 0x04, 0x24, 0xeb, 0x07, 0xc7, 0x04, 0x24, 0xfd, 0xff, 0xff, + 0xff, 0x8b, 0x04, 0x24, 0x81, 0xc4, 0x40, 0x06, 0x00, 0x00, 0x5e, 0x5f, 0x5b, 0x5d, 0xc3, 0x55, + 0x53, 0x57, 0x56, 0x89, 0xce, 0x8b, 0x59, 0x0c, 0x39, 0xd3, 0x7d, 0x2f, 0x8b, 0x0e, 0x8b, 0x46, + 0x04, 0x39, 0xc1, 0x74, 0x13, 0x8d, 0x79, 0x01, 0x89, 0x3e, 0x0f, 0xb6, 0x29, 0x89, 0xd9, 0xd3, + 0xe5, 0x09, 0x6e, 0x08, 0x89, 0xf9, 0xeb, 0x09, 0xc7, 0x46, 0x10, 0x01, 0x00, 0x00, 0x00, 0x89, + 0xc1, 0x83, 0xc3, 0x08, 0x89, 0x5e, 0x0c, 0x39, 0xd3, 0x7c, 0xd6, 0x8b, 0x7e, 0x08, 0x31, 0xc0, + 0x48, 0x89, 0xd1, 0xd3, 0xe0, 0xf7, 0xd0, 0x21, 0xf8, 0xd3, 0xef, 0x89, 0x7e, 0x08, 0x29, 0xd3, + 0x89, 0x5e, 0x0c, 0x5e, 0x5f, 0x5b, 0x5d, 0xc3, 0x55, 0x53, 0x57, 0x56, 0x50, 0x89, 0xd6, 0x89, + 0xcf, 0xe8, 0x1c, 0x01, 0x00, 0x00, 0xb9, 0xfd, 0xff, 0xff, 0xff, 0x83, 0x7f, 0x10, 0x00, 0x0f, + 0x85, 0xff, 0x00, 0x00, 0x00, 0x8b, 0x6c, 0x24, 0x18, 0x89, 0x34, 0x24, 0x3d, 0xff, 0x00, 0x00, + 0x00, 0x7f, 0x10, 0x8b, 0x57, 0x18, 0x8d, 0x4a, 0x01, 0x89, 0x4f, 0x18, 0x88, 0x02, 0xe9, 0xc9, + 0x00, 0x00, 0x00, 0x3d, 0x00, 0x01, 0x00, 0x00, 0x0f, 0x84, 0xe0, 0x00, 0x00, 0x00, 0x3b, 0x86, + 0x60, 0x02, 0x00, 0x00, 0x0f, 0x8f, 0xc5, 0x00, 0x00, 0x00, 0x3d, 0x1d, 0x01, 0x00, 0x00, 0x0f, + 0x87, 0xba, 0x00, 0x00, 0x00, 0x83, 0xbd, 0x60, 0x02, 0x00, 0x00, 0xff, 0x0f, 0x84, 0xad, 0x00, + 0x00, 0x00, 0x89, 0xc3, 0x05, 0xff, 0xfe, 0xff, 0xff, 0x31, 0xf6, 0xb9, 0xff, 0x00, 0x00, 0x10, + 0x0f, 0xa3, 0xc1, 0x72, 0x10, 0x0f, 0xb6, 0x90, 0x10, 0x0a, 0x07, 0x00, 0x89, 0xf9, 0xe8, 0x1c, + 0xff, 0xff, 0xff, 0x89, 0xc6, 0x89, 0xf9, 0x89, 0xea, 0xe8, 0x94, 0x00, 0x00, 0x00, 0x83, 0xf8, + 0x1d, 0x7f, 0x7c, 0x89, 0xe9, 0x89, 0xc5, 0x3b, 0x81, 0x60, 0x02, 0x00, 0x00, 0x7f, 0x70, 0x31, + 0xc0, 0x83, 0xfd, 0x04, 0x72, 0x0e, 0x0f, 0xb6, 0x95, 0x2e, 0x0a, 0x07, 0x00, 0x89, 0xf9, 0xe8, + 0xeb, 0xfe, 0xff, 0xff, 0x0f, 0xb7, 0x8c, 0x2d, 0x4c, 0x0a, 0x07, 0x00, 0x01, 0xc1, 0x8b, 0x47, + 0x18, 0x89, 0xc2, 0x2b, 0x57, 0x14, 0x39, 0xd1, 0x7f, 0x45, 0x0f, 0xb7, 0x94, 0x1b, 0x9a, 0x08, + 0x07, 0x00, 0x01, 0xd6, 0x85, 0xf6, 0x7e, 0x18, 0xf7, 0xd9, 0x31, 0xc0, 0x8b, 0x57, 0x18, 0x8d, + 0x2c, 0x02, 0x8a, 0x1c, 0x29, 0x88, 0x1c, 0x02, 0x40, 0x39, 0xc6, 0x75, 0xef, 0x8b, 0x47, 0x18, + 0x01, 0xf0, 0x89, 0x47, 0x18, 0x8b, 0x34, 0x24, 0x8b, 0x6c, 0x24, 0x18, 0x89, 0xf9, 0x89, 0xf2, + 0xe8, 0x1d, 0x00, 0x00, 0x00, 0x83, 0x7f, 0x10, 0x00, 0x0f, 0x84, 0x0d, 0xff, 0xff, 0xff, 0xb9, + 0xfd, 0xff, 0xff, 0xff, 0x89, 0xc8, 0x83, 0xc4, 0x04, 0x5e, 0x5f, 0x5b, 0x5d, 0xc3, 0x31, 0xc9, + 0xeb, 0xf2, 0x55, 0x53, 0x57, 0x56, 0x50, 0x89, 0xd5, 0x89, 0xcb, 0x31, 0xd2, 0x42, 0xe8, 0x6c, + 0xfe, 0xff, 0xff, 0x89, 0xc7, 0x0f, 0xb7, 0x45, 0x02, 0x31, 0xf6, 0x39, 0xc7, 0x89, 0x2c, 0x24, + 0x7c, 0x21, 0x83, 0xc5, 0x04, 0x31, 0xf6, 0x01, 0xc6, 0x29, 0xc7, 0x89, 0xd9, 0x31, 0xd2, 0x42, + 0xe8, 0x4a, 0xfe, 0xff, 0xff, 0x8d, 0x3c, 0x78, 0x0f, 0xb7, 0x45, 0x00, 0x83, 0xc5, 0x02, 0x39, + 0xc7, 0x7d, 0xe4, 0x01, 0xfe, 0x8b, 0x04, 0x24, 0x0f, 0xb7, 0x44, 0x70, 0x20, 0x83, 0xc4, 0x04, + 0x5e, 0x5f, 0x5b, 0x5d, 0xc3, 0x55, 0x53, 0x57, 0x56, 0x83, 0xec, 0x20, 0x8b, 0x7c, 0x24, 0x34, + 0xb8, 0xe0, 0xff, 0xff, 0xff, 0x66, 0xc7, 0x44, 0x01, 0x20, 0x00, 0x00, 0x83, 0xc0, 0x02, 0x75, + 0xf4, 0xc7, 0x81, 0x60, 0x02, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x85, 0xff, 0x74, 0x1b, 0x31, + 0xc0, 0x80, 0x3c, 0x02, 0x00, 0x74, 0x0e, 0x89, 0x81, 0x60, 0x02, 0x00, 0x00, 0x0f, 0xb6, 0x34, + 0x02, 0x66, 0xff, 0x04, 0x71, 0x40, 0x39, 0xc7, 0x75, 0xe7, 0x31, 0xff, 0x31, 0xdb, 0x43, 0xbd, + 0xe0, 0xff, 0xff, 0xff, 0xb8, 0xfd, 0xff, 0xff, 0xff, 0x0f, 0xb7, 0x74, 0x29, 0x20, 0x29, 0xf3, + 0x72, 0x66, 0x01, 0xdb, 0x66, 0x89, 0x7c, 0x2c, 0x20, 0x01, 0xf7, 0x83, 0xc5, 0x02, 0x75, 0xe9, + 0x83, 0xff, 0x02, 0x72, 0x04, 0x85, 0xdb, 0x75, 0x4f, 0x83, 0xff, 0x01, 0x75, 0x07, 0x66, 0x83, + 0x79, 0x02, 0x01, 0x75, 0x43, 0x8b, 0x5c, 0x24, 0x34, 0x85, 0xdb, 0x74, 0x23, 0x31, 0xc0, 0x0f, + 0xb6, 0x34, 0x02, 0x85, 0xf6, 0x74, 0x14, 0x0f, 0xb7, 0x1c, 0x74, 0x8d, 0x6b, 0x01, 0x66, 0x89, + 0x2c, 0x74, 0x66, 0x89, 0x44, 0x59, 0x20, 0x8b, 0x5c, 0x24, 0x34, 0x40, 0x39, 0xc3, 0x75, 0xdf, + 0x31, 0xc0, 0x83, 0xff, 0x01, 0x75, 0x11, 0x66, 0xc7, 0x41, 0x02, 0x02, 0x00, 0x8b, 0x91, 0x60, + 0x02, 0x00, 0x00, 0x42, 0x66, 0x89, 0x51, 0x22, 0x83, 0xc4, 0x20, 0x5e, 0x5f, 0x5b, 0x5d, 0xc3, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x00, 0x7f, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, + 0x08, 0x08, 0x09, 0x09, 0x0a, 0x0a, 0x0b, 0x0b, 0x0c, 0x0c, 0x0d, 0x0d, 0x01, 0x00, 0x02, 0x00, + 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0d, 0x00, 0x11, 0x00, 0x19, 0x00, + 0x21, 0x00, 0x31, 0x00, 0x41, 0x00, 0x61, 0x00, 0x81, 0x00, 0xc1, 0x00, 0x01, 0x01, 0x81, 0x01, + 0x01, 0x02, 0x01, 0x03, 0x01, 0x04, 0x01, 0x06, 0x01, 0x08, 0x01, 0x0c, 0x01, 0x10, 0x01, 0x18, + 0x01, 0x20, 0x01, 0x30, 0x01, 0x40, 0x01, 0x60, 0x10, 0x11, 0x12, 0x00, 0x08, 0x07, 0x09, 0x06, + 0x0a, 0x05, 0x0b, 0x04, 0x0c, 0x03, 0x0d, 0x02, 0x0e, 0x01, 0x0f, 0x00, 0x03, 0x00, 0x04, 0x00, + 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0d, 0x00, + 0x0f, 0x00, 0x11, 0x00, 0x13, 0x00, 0x17, 0x00, 0x1b, 0x00, 0x1f, 0x00, 0x23, 0x00, 0x2b, 0x00, + 0x33, 0x00, 0x3b, 0x00, 0x43, 0x00, 0x53, 0x00, 0x63, 0x00, 0x73, 0x00, 0x83, 0x00, 0xa3, 0x00, + 0xc3, 0x00, 0xe3, 0x00, 0x02, 0x01, 0x00, 0x00, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x8d, 0x7d, 0x0d, 0x5c, 0x53, 0x57, + 0xb2, 0xf8, 0xbd, 0x10, 0x30, 0x62, 0xf4, 0x46, 0xc5, 0x16, 0x94, 0x6a, 0xb4, 0xd8, 0x95, 0xd6, + 0x2a, 0x51, 0xab, 0xa6, 0x6a, 0x17, 0x84, 0x00, 0xb6, 0xc6, 0x06, 0x90, 0x60, 0xb7, 0x22, 0xa5, + 0x4a, 0x0c, 0x14, 0x81, 0x26, 0xb9, 0x7e, 0xb4, 0xc5, 0x82, 0x97, 0xb4, 0x1c, 0x6e, 0x6f, 0xb7, + 0xdb, 0x8f, 0xdd, 0x6e, 0x77, 0x7d, 0xdb, 0x6e, 0xbb, 0xbb, 0xdd, 0xee, 0xee, 0xd3, 0xdd, 0x6e, + 0xfd, 0xfe, 0x48, 0xc0, 0x07, 0x88, 0x56, 0x51, 0x5c, 0x8d, 0x4a, 0x2b, 0x56, 0xda, 0x5e, 0x1a, + 0x5e, 0x1b, 0xb5, 0x8f, 0x2f, 0x2d, 0xf9, 0xcf, 0x9c, 0x9b, 0x08, 0xe8, 0xbe, 0xff, 0xab, 0xbf, + 0x5f, 0xc8, 0x3d, 0x73, 0xce, 0x99, 0x33, 0x33, 0x67, 0x66, 0xce, 0xcc, 0x39, 0xe7, 0xc6, 0x9b, + 0x89, 0xee, 0xc3, 0x2d, 0xdf, 0x86, 0x31, 0x7b, 0xdf, 0x2e, 0x09, 0x67, 0xaa, 0x3a, 0xf1, 0xf1, + 0xfa, 0x47, 0x1c, 0x3b, 0x61, 0x7b, 0x25, 0xc3, 0x7c, 0x73, 0x2f, 0xfc, 0x89, 0x61, 0xf6, 0xe8, + 0x18, 0x86, 0x79, 0xd5, 0xfb, 0xaa, 0xfb, 0xd5, 0xf6, 0x57, 0xe5, 0x57, 0x5b, 0x3a, 0x73, 0x26, + 0x31, 0xcc, 0xf1, 0xd0, 0xbf, 0x1c, 0xf2, 0xa5, 0xd0, 0xa5, 0xb6, 0x36, 0x1a, 0x7b, 0x03, 0xe1, + 0x8d, 0xc6, 0x7e, 0x68, 0xc9, 0x48, 0xc6, 0x5e, 0xd2, 0xdc, 0x7f, 0x93, 0x63, 0x63, 0x8a, 0xd5, + 0xb6, 0x42, 0x40, 0x71, 0x6c, 0x8f, 0x76, 0x48, 0x7f, 0x4e, 0xe7, 0x8e, 0x1f, 0xe0, 0xa6, 0xb9, + 0xad, 0x7b, 0xaa, 0xa1, 0xae, 0x98, 0x31, 0x1f, 0xd3, 0xbb, 0x07, 0x6b, 0x6f, 0x14, 0x33, 0x36, + 0xe6, 0x85, 0x63, 0x01, 0x66, 0xf5, 0x33, 0xe1, 0xcc, 0x02, 0xe6, 0xc3, 0x0b, 0xcc, 0xb0, 0x7f, + 0xbf, 0x9d, 0x65, 0x0d, 0x44, 0xfe, 0xa9, 0x72, 0xfb, 0xac, 0xc0, 0xac, 0xbf, 0x54, 0x8a, 0x13, + 0x90, 0x32, 0x32, 0xe1, 0x2f, 0x80, 0xa7, 0x31, 0x02, 0x9f, 0x77, 0xc0, 0x93, 0xb8, 0x32, 0x5e, + 0xd5, 0x18, 0xf1, 0xa7, 0x4a, 0xa5, 0x03, 0xc7, 0x46, 0x7c, 0x5a, 0x85, 0x5f, 0xa3, 0xff, 0x45, + 0xbf, 0xee, 0xf9, 0x0c, 0xbe, 0xb2, 0x2d, 0xb9, 0x39, 0xdf, 0x30, 0xf0, 0xa0, 0x66, 0xfe, 0x2d, + 0x69, 0xdf, 0x4c, 0x84, 0xba, 0x61, 0x54, 0x35, 0x44, 0x5e, 0xa9, 0x62, 0x1a, 0x22, 0xbf, 0xc6, + 0x3f, 0xbe, 0x2a, 0xc6, 0x6a, 0xb8, 0xeb, 0x4f, 0x95, 0xce, 0x59, 0xd6, 0x4c, 0x6b, 0xd6, 0x3f, + 0xd9, 0x44, 0xf7, 0xa7, 0x77, 0x59, 0x7f, 0x66, 0x7d, 0xc2, 0xd9, 0xa9, 0x87, 0xa7, 0xdf, 0x7f, + 0x5d, 0x55, 0x73, 0x9f, 0xaf, 0x6a, 0x0f, 0x13, 0xf6, 0xe9, 0x5d, 0x1f, 0xaa, 0x9c, 0x51, 0xd8, + 0x53, 0x55, 0x09, 0x3d, 0x9f, 0xea, 0x9f, 0x6f, 0xe5, 0xd8, 0xbb, 0x3e, 0xad, 0xc2, 0xaf, 0xc9, + 0xff, 0xaa, 0xe2, 0x74, 0xf5, 0x95, 0xc7, 0x58, 0x6e, 0x5a, 0xfd, 0x37, 0x35, 0x55, 0x31, 0x8c, + 0x75, 0xbf, 0x8e, 0x79, 0xf5, 0xe2, 0xab, 0xf5, 0xaf, 0x5e, 0x79, 0xd5, 0xf7, 0x6a, 0x6b, 0x5e, + 0xfe, 0x9a, 0x27, 0x45, 0xca, 0x17, 0xa1, 0x3c, 0x56, 0x2e, 0x81, 0x41, 0x19, 0xc6, 0x39, 0x29, + 0xf0, 0x10, 0xd0, 0xc1, 0x04, 0x1e, 0xfa, 0x9a, 0xfe, 0x45, 0xa0, 0x5c, 0xdc, 0xc3, 0x30, 0xc2, + 0x11, 0x4d, 0xbd, 0xde, 0x5d, 0xcf, 0xdc, 0xf6, 0x2f, 0x10, 0x7e, 0x3b, 0xe4, 0x38, 0x48, 0xe0, + 0xfe, 0x6d, 0x30, 0xb5, 0x03, 0xff, 0x96, 0x7b, 0x6e, 0x9f, 0x9f, 0xa1, 0xfc, 0x6f, 0xbb, 0x8d, + 0xff, 0xef, 0x7d, 0x3f, 0x28, 0xc8, 0x6a, 0x53, 0xe3, 0x55, 0xbf, 0xff, 0x18, 0xaa, 0xc5, 0xd4, + 0x78, 0xf5, 0xfb, 0xf1, 0x02, 0x7d, 0xd0, 0xbc, 0x3f, 0x43, 0xa0, 0xa2, 0x9e, 0x49, 0xbf, 0x46, + 0xcf, 0xa3, 0x5f, 0xf7, 0x2c, 0x16, 0x82, 0xa2, 0x7e, 0x66, 0xdb, 0xff, 0x2e, 0x6a, 0x97, 0x32, + 0x54, 0xcb, 0x7c, 0x2b, 0xb9, 0x4f, 0x27, 0xcc, 0xb7, 0x8a, 0xf7, 0xcd, 0x10, 0xb8, 0x3f, 0x73, + 0xef, 0x8d, 0x98, 0x62, 0xdd, 0x6e, 0xcd, 0xb3, 0xe6, 0x5b, 0xd7, 0xa0, 0x6c, 0xad, 0x4f, 0x5a, + 0x57, 0x61, 0x9d, 0x4e, 0xb8, 0xf1, 0x29, 0xd3, 0x3f, 0xd8, 0x36, 0x5e, 0xb0, 0xae, 0x97, 0xd6, + 0xc5, 0xcf, 0xb0, 0x9a, 0xad, 0xd9, 0x38, 0x1b, 0x56, 0x8b, 0x35, 0xd7, 0x9a, 0x63, 0xfd, 0xed, + 0xe4, 0x48, 0xee, 0x5d, 0xee, 0x43, 0xa5, 0x07, 0x15, 0xfb, 0x4c, 0xfa, 0x35, 0x79, 0x9e, 0x00, + 0x03, 0x6b, 0x58, 0x1c, 0x78, 0x60, 0x1b, 0x48, 0x1d, 0x74, 0x7b, 0x90, 0x26, 0x94, 0x7a, 0xfd, + 0x6b, 0xc3, 0xff, 0x31, 0xff, 0x9f, 0x7f, 0x8a, 0x44, 0x8e, 0x0f, 0xfb, 0x07, 0xd2, 0x50, 0xbd, + 0xef, 0x07, 0xc6, 0xeb, 0x41, 0x1c, 0xdf, 0x2b, 0xe2, 0xb8, 0xa9, 0x88, 0x43, 0x55, 0xad, 0x88, + 0x43, 0x2a, 0x89, 0x8f, 0xfe, 0x66, 0xab, 0xf0, 0xbf, 0x8b, 0xe4, 0x6d, 0x41, 0x11, 0x49, 0xa8, + 0xea, 0x46, 0x60, 0xa3, 0x2a, 0xb0, 0x51, 0x1d, 0xd8, 0xc8, 0x04, 0x26, 0xfb, 0x05, 0xe1, 0x48, + 0x64, 0xff, 0x2c, 0x64, 0xe9, 0x7b, 0x01, 0xbf, 0x26, 0xdf, 0x14, 0x06, 0x35, 0xe9, 0xb4, 0x70, + 0xbb, 0x26, 0x4d, 0x0f, 0x04, 0x18, 0x66, 0x08, 0x63, 0xff, 0x3f, 0x3e, 0xc0, 0x96, 0xb3, 0x73, + 0x2d, 0x42, 0xd7, 0x4c, 0x31, 0x4f, 0x5d, 0xf9, 0xfc, 0x68, 0x86, 0x73, 0x1d, 0x66, 0x61, 0x7e, + 0x4d, 0x31, 0x3b, 0xd9, 0x51, 0xa6, 0x38, 0xae, 0xfa, 0xcf, 0x58, 0x32, 0x46, 0x8b, 0x26, 0xad, + 0xf8, 0x7c, 0x34, 0xb7, 0xe3, 0x79, 0x95, 0xbe, 0x5b, 0x7f, 0x9a, 0x54, 0xf8, 0x09, 0x2f, 0xb3, + 0x27, 0x48, 0x4e, 0xd7, 0xd8, 0xba, 0x62, 0x26, 0x97, 0xe4, 0xb4, 0x65, 0x11, 0xde, 0x6b, 0x91, + 0xef, 0x5d, 0x89, 0x7a, 0xa9, 0x25, 0x0d, 0xc2, 0xf3, 0x3a, 0x96, 0x1f, 0x27, 0xa6, 0xcc, 0xd0, + 0xfb, 0xc5, 0xc7, 0x66, 0xea, 0x4f, 0x8d, 0xf4, 0x70, 0xd5, 0x17, 0xd1, 0x70, 0xf9, 0x0e, 0x92, + 0xd3, 0xde, 0x98, 0x82, 0x0a, 0xce, 0x08, 0xcf, 0xc7, 0x33, 0xfc, 0x78, 0x31, 0x05, 0xf0, 0xa6, + 0xa8, 0xcc, 0xf2, 0xe4, 0x0c, 0xec, 0xac, 0x22, 0x29, 0xf1, 0xe2, 0x63, 0xf3, 0x88, 0xa9, 0x05, + 0x2a, 0xc4, 0xec, 0x44, 0x92, 0x73, 0x46, 0xda, 0x19, 0x00, 0x2a, 0x5c, 0x6e, 0x6e, 0x99, 0x9f, + 0xf4, 0x78, 0x02, 0x23, 0x3d, 0x03, 0x53, 0x2c, 0xb9, 0x99, 0x59, 0xf2, 0x27, 0xd9, 0xca, 0x68, + 0xa7, 0xb8, 0x1d, 0x7e, 0x62, 0x6c, 0xea, 0xf9, 0x9a, 0xbd, 0x46, 0x4c, 0xc7, 0xb9, 0x1d, 0x7d, + 0xec, 0x39, 0xd1, 0x74, 0x26, 0xc1, 0x23, 0x1a, 0x5b, 0x62, 0x7b, 0xf5, 0xdd, 0x23, 0x9b, 0x38, + 0x57, 0x3b, 0x0c, 0x27, 0x3e, 0xaf, 0x12, 0x1f, 0x53, 0x89, 0x79, 0xed, 0xdc, 0x8e, 0xf3, 0xa2, + 0xb1, 0xa3, 0xe7, 0x32, 0x7b, 0x01, 0x98, 0x0e, 0x37, 0x36, 0x8d, 0xcf, 0x39, 0x9e, 0x9b, 0x65, + 0x0e, 0x38, 0xe2, 0xb3, 0xe5, 0xf3, 0x31, 0x88, 0x32, 0xda, 0xe5, 0x76, 0x4e, 0x14, 0x7a, 0x59, + 0xae, 0xfa, 0x43, 0x24, 0xb3, 0x31, 0x20, 0x9c, 0x0d, 0x90, 0xde, 0x91, 0x7e, 0xfe, 0x70, 0xe7, + 0xaf, 0x00, 0x00, 0xe4, 0xb3, 0x88, 0x0f, 0x78, 0x71, 0xcc, 0x10, 0x73, 0xda, 0x49, 0xf6, 0x4c, + 0xb1, 0xc2, 0x2f, 0x1a, 0x63, 0xc8, 0x75, 0xd2, 0xf0, 0x00, 0x2f, 0x8b, 0xa6, 0xb8, 0x49, 0xa6, + 0x2e, 0xe2, 0x25, 0xad, 0x3d, 0x5f, 0x73, 0x3b, 0x2e, 0xb1, 0xad, 0x62, 0x8e, 0x37, 0xa1, 0x4e, + 0x34, 0xb6, 0xc5, 0x7a, 0xa1, 0x5d, 0xc2, 0xb9, 0xfd, 0xc8, 0x7c, 0x6c, 0xbd, 0x61, 0x80, 0xf4, + 0x72, 0x4b, 0x1b, 0x0c, 0xe7, 0xb9, 0xa5, 0x03, 0x22, 0x2f, 0x73, 0xa9, 0x50, 0x02, 0xc2, 0x34, + 0xac, 0x5f, 0x34, 0xa9, 0xc3, 0x33, 0xe3, 0x81, 0x26, 0xf9, 0x94, 0x09, 0xc6, 0xc9, 0xe9, 0x02, + 0x5b, 0x67, 0x07, 0xa0, 0xef, 0x58, 0xa4, 0x78, 0x11, 0x1f, 0x43, 0x5a, 0x26, 0x19, 0xe3, 0x94, + 0x69, 0xe1, 0xb6, 0x3d, 0x3b, 0x10, 0x08, 0xec, 0x64, 0x7d, 0x61, 0x7a, 0xb7, 0x70, 0x64, 0xe6, + 0x9a, 0xfc, 0x27, 0xf3, 0xea, 0x6d, 0x7b, 0x3e, 0x43, 0x2f, 0x2b, 0x9f, 0x1c, 0xc7, 0x30, 0xa1, + 0x09, 0x5e, 0x2d, 0x55, 0xbc, 0x5d, 0xbc, 0x1a, 0x26, 0x4b, 0x7e, 0xdd, 0x44, 0x9d, 0x87, 0xfe, + 0xe8, 0xc7, 0xac, 0xbe, 0x3b, 0x4d, 0x54, 0x49, 0x1f, 0x5e, 0x81, 0x89, 0xf1, 0x78, 0xfa, 0xa6, + 0x14, 0x33, 0xc5, 0xab, 0x33, 0xcd, 0xb9, 0x01, 0x5e, 0x23, 0x77, 0xfc, 0x10, 0x08, 0x08, 0x47, + 0xd0, 0x9b, 0x4b, 0x5f, 0x30, 0xd0, 0xe9, 0xd5, 0x68, 0xda, 0x09, 0x04, 0xa3, 0xd5, 0xbb, 0xbb, + 0xeb, 0x59, 0xca, 0x03, 0xb9, 0xca, 0xbb, 0x7d, 0x09, 0x55, 0x15, 0xef, 0x32, 0x0c, 0xcb, 0xf0, + 0x93, 0x81, 0x43, 0x31, 0x47, 0x4d, 0x96, 0x6a, 0x44, 0xa3, 0x97, 0x2c, 0x55, 0x83, 0xf8, 0x61, + 0x1a, 0xc8, 0xa3, 0x2a, 0x12, 0x16, 0x22, 0x6f, 0x35, 0x25, 0x2f, 0x44, 0x52, 0xaa, 0xc8, 0xab, + 0xa5, 0x8a, 0x6b, 0xd6, 0xc6, 0x11, 0xa0, 0xfb, 0x17, 0xb3, 0x8b, 0xd9, 0x6c, 0xdb, 0x21, 0x10, + 0x6c, 0xae, 0x45, 0xb6, 0x2a, 0xa3, 0x73, 0xbb, 0x46, 0x7c, 0xf8, 0x22, 0xe7, 0x7a, 0x19, 0x80, + 0xb4, 0xf6, 0x84, 0x52, 0x9b, 0x11, 0xaa, 0x35, 0x5e, 0xc3, 0x6a, 0x1b, 0x7b, 0xab, 0xfb, 0xe7, + 0x4a, 0x83, 0x9f, 0x0c, 0x6f, 0x90, 0x1e, 0xea, 0xff, 0xad, 0x52, 0xad, 0x1e, 0x5e, 0x3d, 0x83, + 0xf6, 0xdf, 0xab, 0x46, 0x86, 0xfc, 0x12, 0xff, 0x8f, 0xec, 0xcc, 0xec, 0x62, 0x95, 0xc5, 0x2c, + 0x5f, 0xbe, 0x49, 0x9b, 0xed, 0xc1, 0x0a, 0xb3, 0x6d, 0x2a, 0xa8, 0xaf, 0x45, 0x7e, 0x7d, 0x79, + 0x50, 0x0c, 0x5c, 0xf5, 0x55, 0x00, 0xef, 0x51, 0xc1, 0x9f, 0x6c, 0x73, 0x76, 0x71, 0x38, 0xf4, + 0xb3, 0x04, 0x78, 0xb5, 0xfc, 0xd7, 0x60, 0x27, 0x15, 0xed, 0xb4, 0xb1, 0x1d, 0x3b, 0xe5, 0x0d, + 0x76, 0x3a, 0xc0, 0xe0, 0x58, 0x7b, 0xc2, 0x83, 0xdd, 0xe6, 0x87, 0xba, 0xf1, 0xc1, 0x6e, 0xe1, + 0xb4, 0xdb, 0xb2, 0x36, 0xec, 0x76, 0xdf, 0x60, 0x37, 0x17, 0xb6, 0x57, 0x2a, 0xb3, 0x8b, 0xb3, + 0x42, 0x9d, 0x92, 0xfe, 0x4d, 0xa7, 0xab, 0x8f, 0x85, 0xe6, 0x69, 0x25, 0x8c, 0x13, 0x71, 0x3b, + 0x79, 0xa3, 0x83, 0x5d, 0x22, 0x68, 0x17, 0xe3, 0x25, 0xec, 0x72, 0xe8, 0x56, 0x97, 0xa9, 0xd9, + 0xc5, 0x61, 0xd9, 0xb6, 0x85, 0x40, 0x7b, 0x2e, 0x36, 0x6e, 0xbf, 0xa1, 0xc8, 0x69, 0xb7, 0xf1, + 0xda, 0x92, 0xec, 0xef, 0xc0, 0x9f, 0xbd, 0x55, 0x4f, 0xbc, 0xc2, 0x91, 0xd4, 0xd0, 0x24, 0xe6, + 0x5a, 0xcc, 0x62, 0x85, 0x5a, 0xdf, 0x9d, 0x2b, 0xdf, 0x3f, 0x80, 0x2d, 0x55, 0xd5, 0x60, 0x36, + 0x7a, 0xb7, 0xc4, 0x7f, 0x6f, 0x2e, 0x56, 0x99, 0x6d, 0x7b, 0x40, 0xae, 0x96, 0x5c, 0xf9, 0x1d, + 0x05, 0x8b, 0x38, 0x9f, 0xf8, 0xa1, 0xc9, 0x9a, 0xfc, 0xd0, 0xfc, 0x57, 0x75, 0x45, 0x43, 0x03, + 0xc9, 0xd5, 0x0e, 0x7d, 0x51, 0x31, 0xcd, 0x72, 0xdf, 0xa3, 0x8a, 0x62, 0xba, 0x3f, 0x66, 0xc5, + 0xc5, 0x2e, 0xaa, 0x95, 0x03, 0x60, 0xeb, 0xa8, 0x95, 0x96, 0x5c, 0xa5, 0xa1, 0x19, 0x75, 0x33, + 0xe3, 0xc6, 0x50, 0xdd, 0x0c, 0x56, 0xc8, 0x5d, 0x63, 0x43, 0x6c, 0x44, 0x87, 0xf4, 0x13, 0x29, + 0x60, 0xf8, 0x8f, 0x3b, 0x4b, 0xe1, 0xbb, 0xea, 0xa0, 0x3c, 0x80, 0x7e, 0x91, 0x05, 0x17, 0xb7, + 0x06, 0xca, 0xe4, 0xc6, 0x61, 0x50, 0x83, 0x80, 0x68, 0xd4, 0x1a, 0x5c, 0x89, 0xf0, 0xc0, 0xbd, + 0xd4, 0x01, 0x7c, 0xef, 0xc5, 0x10, 0x42, 0x7f, 0x5a, 0x72, 0x2d, 0x06, 0x50, 0x26, 0x18, 0x60, + 0xa1, 0x42, 0x93, 0xe8, 0x9a, 0x81, 0x6d, 0xc0, 0xcf, 0x5c, 0x12, 0xf3, 0xb4, 0xe8, 0x69, 0x44, + 0x69, 0x26, 0x85, 0x9c, 0x24, 0x9f, 0x7b, 0xbe, 0x1a, 0xc1, 0x36, 0x8c, 0x3d, 0xa5, 0x77, 0x9b, + 0xf7, 0x60, 0x77, 0x73, 0xa6, 0x45, 0xe9, 0x6f, 0x16, 0x79, 0x8d, 0x45, 0x7e, 0xa7, 0x9f, 0x52, + 0x2b, 0x19, 0x0f, 0xe8, 0x8f, 0x92, 0x0c, 0x0d, 0xc9, 0x50, 0x93, 0x0c, 0x15, 0x51, 0x4b, 0xd2, + 0x72, 0x68, 0x52, 0xac, 0x35, 0x67, 0xca, 0x4b, 0x1e, 0xbd, 0x35, 0xd1, 0x1c, 0xd0, 0x20, 0x46, + 0x82, 0xfd, 0x13, 0x56, 0x4c, 0x53, 0x91, 0x64, 0x95, 0x98, 0xa6, 0x26, 0xc9, 0xea, 0xda, 0x34, + 0x4d, 0x4d, 0xb2, 0x26, 0x85, 0x3c, 0xa1, 0x15, 0xd3, 0xa2, 0x49, 0x72, 0xb4, 0xe8, 0x5a, 0x0d, + 0x9d, 0x89, 0xb1, 0x4b, 0xfa, 0x1d, 0x75, 0x8d, 0x01, 0x6e, 0xd9, 0x25, 0x72, 0x09, 0x5d, 0xe3, + 0x8d, 0x29, 0xc4, 0xc3, 0xed, 0x38, 0x06, 0x14, 0x36, 0x8b, 0x9f, 0x3c, 0x85, 0x8d, 0xae, 0x02, + 0x8d, 0xec, 0x29, 0x6a, 0x98, 0x89, 0xe4, 0xd1, 0x79, 0xa2, 0x6b, 0x1d, 0x40, 0x1f, 0x30, 0x76, + 0x89, 0x92, 0x0d, 0x1e, 0x62, 0xaf, 0x0a, 0x6e, 0x56, 0x38, 0xc5, 0x40, 0x23, 0x6e, 0x87, 0x17, + 0xba, 0x5d, 0x00, 0xa3, 0x64, 0x9b, 0x49, 0xda, 0x42, 0x62, 0x59, 0x0c, 0xce, 0x88, 0xa4, 0x01, + 0xe5, 0x6d, 0x60, 0x0d, 0xa6, 0xc9, 0x48, 0xa4, 0xba, 0x1a, 0x88, 0x7c, 0x2e, 0x1c, 0xd7, 0x04, + 0x99, 0xa4, 0x99, 0xc1, 0x8d, 0x92, 0xb4, 0xe5, 0x68, 0xf7, 0xa6, 0x76, 0xb2, 0x22, 0x83, 0xa4, + 0xa5, 0xee, 0x64, 0x3b, 0xf3, 0xa0, 0xfa, 0xf0, 0x00, 0xca, 0x16, 0xd9, 0xe7, 0x6e, 0x06, 0x55, + 0x83, 0xab, 0x16, 0xb0, 0x5f, 0x9e, 0x56, 0xb8, 0xa1, 0xe2, 0x5e, 0x59, 0x1f, 0x06, 0x33, 0x97, + 0xc2, 0x12, 0xf0, 0x11, 0x46, 0xb5, 0xe0, 0x4e, 0x25, 0x46, 0xaf, 0x94, 0x72, 0x93, 0x18, 0xcf, + 0xe8, 0xc1, 0xd3, 0x7a, 0xda, 0x55, 0x11, 0xe8, 0x09, 0xf4, 0x47, 0x33, 0x8b, 0xb5, 0x99, 0x66, + 0x20, 0xc0, 0x6c, 0x91, 0xb3, 0xfa, 0x42, 0x56, 0xdb, 0x2e, 0xf4, 0x72, 0xce, 0x91, 0x42, 0x6f, + 0x04, 0xe7, 0xea, 0xc7, 0x99, 0x34, 0x1e, 0x27, 0x15, 0x5d, 0x52, 0x2e, 0xbb, 0x07, 0xe7, 0x7c, + 0x6f, 0x2c, 0x86, 0xa3, 0x07, 0x33, 0x60, 0xfc, 0xeb, 0x7f, 0x11, 0x23, 0x09, 0x9d, 0x09, 0x94, + 0xa5, 0x2b, 0x89, 0x3e, 0xa8, 0x89, 0x2b, 0x15, 0x1e, 0x50, 0xa2, 0x2e, 0x6c, 0xa4, 0x0f, 0x00, + 0x3d, 0xe4, 0xad, 0xe5, 0xb4, 0x32, 0x9a, 0xb8, 0xcc, 0x28, 0xb3, 0x83, 0xe5, 0x54, 0x35, 0x3a, + 0x88, 0xab, 0x84, 0x3e, 0xc8, 0xe4, 0xe0, 0x66, 0x84, 0xbb, 0x9c, 0xf8, 0xf7, 0x93, 0x95, 0xf0, + 0xf7, 0x10, 0x0e, 0xe6, 0x5c, 0xa3, 0x0f, 0x88, 0x79, 0x67, 0xf4, 0xdd, 0xc4, 0xcf, 0x7d, 0xd0, + 0x3b, 0x92, 0xf4, 0x79, 0x2e, 0x8f, 0xac, 0x3a, 0x8a, 0x6e, 0x0a, 0x94, 0x16, 0xa6, 0x36, 0xa8, + 0x9b, 0x41, 0x75, 0x90, 0x7f, 0xde, 0x1b, 0x64, 0xc1, 0xd5, 0x81, 0x06, 0x03, 0x5c, 0x44, 0x00, + 0x17, 0xbc, 0x5a, 0xa4, 0xba, 0xa9, 0xef, 0x1e, 0xe4, 0x8b, 0x03, 0x14, 0x8f, 0xf1, 0x7f, 0x1e, + 0x86, 0xf6, 0xd0, 0xff, 0x86, 0x76, 0xa1, 0x82, 0xb6, 0xf2, 0x20, 0x62, 0x65, 0xf6, 0x04, 0xa8, + 0x6a, 0xf8, 0x61, 0xa5, 0xe0, 0xaa, 0x4f, 0xe0, 0x3a, 0x45, 0x9b, 0x91, 0x70, 0x91, 0x4a, 0x80, + 0xa4, 0xa8, 0x44, 0x2a, 0x01, 0x92, 0xa2, 0xae, 0xa5, 0x12, 0xa8, 0x49, 0x01, 0x87, 0xdd, 0x42, + 0x52, 0xb4, 0xa2, 0xc2, 0x7c, 0x4a, 0x74, 0xf8, 0x41, 0x64, 0x7b, 0xfc, 0x27, 0x28, 0x85, 0x70, + 0x4a, 0x9b, 0x70, 0x96, 0x21, 0xcf, 0x27, 0x12, 0x07, 0xa8, 0x4f, 0xd7, 0x00, 0xb6, 0x59, 0xd8, + 0x98, 0xb2, 0x98, 0xa1, 0xc8, 0x57, 0xd2, 0x3e, 0x31, 0xd9, 0xa0, 0xe0, 0x66, 0xf9, 0x27, 0x71, + 0x41, 0x2d, 0x71, 0xc2, 0x6a, 0xd0, 0x04, 0xeb, 0x1e, 0x5d, 0x0d, 0x8e, 0xd0, 0xd5, 0xe0, 0x80, + 0x68, 0x72, 0x0f, 0x59, 0x0d, 0x6a, 0x52, 0x92, 0xb2, 0xe5, 0xcf, 0x26, 0xd1, 0x35, 0x1f, 0x8c, + 0x76, 0x4c, 0xa3, 0xd1, 0x4f, 0xd7, 0x94, 0x94, 0x12, 0xa8, 0x1f, 0x19, 0x2c, 0x61, 0x33, 0x5b, + 0x43, 0xca, 0x4a, 0xc6, 0x37, 0x02, 0x20, 0x3f, 0x28, 0x4a, 0x05, 0x3a, 0x24, 0x56, 0x74, 0x89, + 0xc6, 0xe3, 0x28, 0xaa, 0xea, 0x73, 0xb8, 0x44, 0xf7, 0x72, 0x0a, 0xaf, 0xe9, 0x42, 0x40, 0xc5, + 0xb9, 0xf6, 0x41, 0x43, 0x6a, 0xdd, 0x9d, 0x5a, 0xd4, 0x0f, 0x6f, 0x48, 0x9f, 0xc8, 0x25, 0xfd, + 0x45, 0x72, 0x55, 0xe2, 0xdb, 0xb2, 0x8b, 0xb5, 0xd9, 0x66, 0x4b, 0xa6, 0x3c, 0xd0, 0xad, 0x48, + 0x6e, 0xab, 0x8a, 0xe1, 0xaa, 0xdf, 0x40, 0x76, 0x4c, 0x1a, 0x91, 0x05, 0x0b, 0x20, 0x91, 0x62, + 0xb2, 0x8a, 0xa4, 0xa9, 0xc4, 0x64, 0x35, 0x49, 0x53, 0xd7, 0x26, 0x6b, 0x6a, 0xd2, 0x34, 0xe9, + 0x64, 0xab, 0x56, 0x4c, 0x8e, 0x26, 0x69, 0xd1, 0xa8, 0x19, 0x6b, 0xe6, 0x91, 0xb4, 0x44, 0xd4, + 0x8c, 0x35, 0x8b, 0xd1, 0x58, 0x56, 0xc4, 0x28, 0xb3, 0x01, 0x96, 0x72, 0xef, 0xa4, 0x90, 0x0c, + 0xee, 0x13, 0xa9, 0x80, 0xd1, 0x50, 0x14, 0x91, 0x83, 0xad, 0x28, 0xfa, 0x28, 0xd1, 0xa9, 0xa0, + 0x16, 0xf3, 0x31, 0x5b, 0xb3, 0x26, 0xc9, 0x22, 0xb7, 0x4e, 0xa4, 0x92, 0xd0, 0x07, 0x40, 0x16, + 0x63, 0x49, 0x1a, 0x0a, 0x41, 0x03, 0x2b, 0xbe, 0x2f, 0x53, 0x1f, 0x78, 0xdc, 0xb7, 0x1c, 0xe4, + 0x90, 0x66, 0x6b, 0x48, 0x03, 0x39, 0x24, 0x83, 0x83, 0xdd, 0x8e, 0xda, 0x5a, 0x93, 0x06, 0xbd, + 0xb6, 0x0f, 0xe9, 0x35, 0x42, 0xe9, 0x15, 0x16, 0x6a, 0xcb, 0x8a, 0xe1, 0x10, 0xef, 0x90, 0xb4, + 0x55, 0x64, 0xc5, 0x6a, 0x31, 0x05, 0xf8, 0x78, 0x4a, 0x4c, 0x01, 0x7b, 0x5e, 0x47, 0x7a, 0xab, + 0x8e, 0xa0, 0xeb, 0x1d, 0x74, 0xe3, 0xc2, 0x92, 0x33, 0x90, 0x03, 0x32, 0xce, 0x1c, 0xea, 0xce, + 0xdf, 0xc3, 0x84, 0x50, 0xd4, 0xec, 0xac, 0xdc, 0x92, 0x04, 0xd9, 0x87, 0xe0, 0x49, 0x2d, 0xd6, + 0xe6, 0x66, 0xca, 0x6f, 0x1b, 0x83, 0x4e, 0x8a, 0x56, 0x3b, 0x67, 0xd1, 0x06, 0x2b, 0x19, 0x67, + 0x8c, 0xe0, 0x59, 0x45, 0x1b, 0x6c, 0x1a, 0xde, 0x60, 0x74, 0xda, 0xa2, 0x87, 0x10, 0xab, 0xfd, + 0x13, 0xbd, 0xdb, 0x17, 0x2e, 0xaa, 0x76, 0x06, 0x3d, 0x7f, 0xae, 0xe5, 0x3d, 0x04, 0x03, 0xbd, + 0xf3, 0xe8, 0x68, 0xe2, 0x68, 0x3a, 0x5c, 0xf4, 0xdf, 0x2b, 0x9f, 0xb3, 0xc1, 0x70, 0xb9, 0x01, + 0x7b, 0x89, 0xfc, 0xdc, 0x28, 0x2a, 0x41, 0x97, 0x5b, 0xa9, 0x74, 0x8e, 0xa1, 0x5d, 0xd2, 0x0c, + 0x0d, 0xf6, 0x73, 0x0a, 0xb2, 0xbf, 0x0f, 0x2e, 0x23, 0xe2, 0x68, 0x8a, 0xee, 0x28, 0x08, 0x3b, + 0x47, 0x0b, 0xca, 0x51, 0x9b, 0xa7, 0x16, 0x97, 0x50, 0x94, 0x23, 0x0c, 0xe5, 0x6a, 0x3e, 0x6a, + 0xe1, 0x2a, 0x0d, 0x1f, 0x61, 0x30, 0x6b, 0x9d, 0x6a, 0xa1, 0x51, 0xb5, 0x8c, 0x87, 0x7c, 0x2d, + 0xc4, 0xf7, 0x60, 0xd7, 0x09, 0xe0, 0x1d, 0xc5, 0x87, 0x68, 0xa7, 0x48, 0x03, 0xb6, 0x6c, 0x80, + 0x96, 0xdf, 0x43, 0x4b, 0xda, 0xae, 0xd6, 0xa8, 0x26, 0x9e, 0xca, 0xcb, 0x9c, 0x5b, 0x56, 0x71, + 0xbb, 0xdc, 0xac, 0x5b, 0x52, 0x55, 0x32, 0x4d, 0xb4, 0x86, 0xdb, 0x65, 0x52, 0x17, 0x79, 0x3e, + 0x05, 0xbd, 0xf1, 0xc8, 0xa3, 0xa4, 0x68, 0x46, 0x8a, 0x7e, 0x7d, 0xc6, 0x29, 0x77, 0xbb, 0x4a, + 0xad, 0x54, 0xd7, 0x9a, 0xd4, 0xdc, 0x21, 0x0f, 0x69, 0xae, 0xac, 0xfb, 0x5d, 0x65, 0x7f, 0xc4, + 0xa6, 0x08, 0xc1, 0xfd, 0x67, 0xdf, 0x64, 0x2c, 0x1e, 0x0e, 0x16, 0x8f, 0xfa, 0xb8, 0x4a, 0x4f, + 0x8b, 0xe0, 0x6e, 0xd1, 0x9f, 0x7e, 0xb4, 0xb2, 0x2f, 0x8a, 0x4b, 0xa9, 0x1b, 0x0c, 0x8e, 0x50, + 0x34, 0x46, 0x2d, 0x10, 0xe6, 0xe9, 0x9d, 0x02, 0xd6, 0xac, 0x0f, 0x70, 0x87, 0x26, 0x12, 0xaf, + 0xea, 0x77, 0x8b, 0xb1, 0x67, 0xfd, 0x9f, 0x7d, 0x31, 0x50, 0x38, 0xac, 0x14, 0x8e, 0xfa, 0x46, + 0x41, 0xa1, 0x65, 0xf1, 0xc8, 0x4d, 0xb3, 0x84, 0xfa, 0x16, 0xe1, 0x46, 0xc0, 0x79, 0x1f, 0xf1, + 0x43, 0x6c, 0x0e, 0xd1, 0x61, 0xcf, 0x65, 0x6e, 0x07, 0xef, 0x67, 0xcf, 0x62, 0x50, 0xdc, 0x43, + 0x2e, 0x78, 0xfa, 0xa7, 0xb0, 0xde, 0xb1, 0xe7, 0x20, 0xa2, 0x5c, 0x4a, 0x1a, 0x7c, 0x7f, 0x03, + 0xff, 0x0e, 0xf3, 0x10, 0x46, 0xb4, 0xd0, 0xbc, 0x1f, 0x04, 0x3e, 0x24, 0x3a, 0x13, 0x2b, 0x34, + 0x60, 0x06, 0xdc, 0x2e, 0x4e, 0xe8, 0x9b, 0xed, 0xbc, 0x0b, 0x28, 0x71, 0x1d, 0xe5, 0x63, 0xc0, + 0x0b, 0x6f, 0x1b, 0x49, 0xa7, 0xa6, 0x73, 0x22, 0x28, 0x50, 0x43, 0xe4, 0x6c, 0x69, 0x0d, 0x9b, + 0xee, 0xbb, 0x1b, 0xe0, 0xc5, 0x0a, 0xdc, 0x22, 0xff, 0x21, 0x4a, 0x89, 0xe2, 0xeb, 0xd9, 0xeb, + 0x95, 0x3f, 0x9d, 0xcd, 0x55, 0x7f, 0x0c, 0xd2, 0x29, 0x0e, 0xb3, 0xed, 0xbc, 0x82, 0x51, 0x64, + 0x46, 0xd4, 0xad, 0x95, 0xae, 0x11, 0xe1, 0xe1, 0xb6, 0x3d, 0x14, 0x9e, 0x38, 0x08, 0xdf, 0xa1, + 0xc0, 0x0f, 0x50, 0x78, 0xcc, 0x20, 0xbc, 0x1a, 0xe1, 0x2a, 0xdb, 0xc7, 0x14, 0xce, 0x84, 0xe0, + 0xce, 0x72, 0x68, 0xdb, 0xd2, 0x86, 0xb0, 0xae, 0x91, 0x21, 0xd8, 0x7d, 0x30, 0xde, 0x3c, 0x1a, + 0xb5, 0x7a, 0x6f, 0xc1, 0x66, 0x49, 0x29, 0x01, 0x83, 0xdf, 0x39, 0xa3, 0x72, 0xe1, 0x6c, 0xe7, + 0xbd, 0x0d, 0xe1, 0xb3, 0x53, 0x7c, 0xf7, 0xa4, 0x54, 0x3e, 0x1f, 0x98, 0x2d, 0x3d, 0x19, 0xe0, + 0x7b, 0xf4, 0x6e, 0xc3, 0x75, 0xee, 0x4d, 0x77, 0x83, 0x2a, 0x9c, 0x61, 0xeb, 0x85, 0xc6, 0x30, + 0x1f, 0x9b, 0x9e, 0x5e, 0x9b, 0x0e, 0x12, 0x6f, 0x02, 0xe5, 0x98, 0x0a, 0x12, 0xe0, 0xaa, 0x31, + 0x44, 0xa8, 0x09, 0x4f, 0xf1, 0x7d, 0x7d, 0x47, 0x2f, 0xb6, 0x9e, 0xa2, 0xe6, 0x52, 0xeb, 0xb1, + 0x86, 0x33, 0xd6, 0x37, 0x04, 0x4d, 0x0b, 0x73, 0x2b, 0x58, 0x21, 0xd5, 0x92, 0x49, 0x43, 0x4c, + 0x37, 0x33, 0xcd, 0x32, 0x06, 0x5d, 0x40, 0xcd, 0xe0, 0x1c, 0x27, 0xa2, 0x33, 0xab, 0x50, 0x4b, + 0x79, 0xfe, 0x46, 0x9a, 0x8e, 0xc1, 0xd0, 0xdc, 0xae, 0xf4, 0x01, 0x64, 0xf7, 0x8b, 0x30, 0xf4, + 0x6a, 0xd3, 0x9d, 0x53, 0x44, 0x93, 0xbf, 0xaa, 0x6f, 0x80, 0x63, 0x98, 0x4d, 0x51, 0x35, 0x55, + 0xcb, 0xb5, 0xe1, 0x4c, 0x32, 0x31, 0xf9, 0x1b, 0xe8, 0x13, 0x93, 0xee, 0x6b, 0xe1, 0x0e, 0xa5, + 0xc3, 0x42, 0xf2, 0xf4, 0x8b, 0xb3, 0x84, 0xde, 0xac, 0xad, 0x2b, 0x84, 0xde, 0x6c, 0xae, 0xfa, + 0x17, 0xd4, 0x21, 0xe6, 0x70, 0xd5, 0x1d, 0xf4, 0x61, 0x15, 0xe7, 0x5a, 0x85, 0xbe, 0xcf, 0x2f, + 0x34, 0xa8, 0x45, 0x2d, 0x39, 0x1f, 0x28, 0x57, 0xf9, 0x62, 0xa4, 0x8c, 0xd7, 0x84, 0x3e, 0xf5, + 0xa6, 0x25, 0x81, 0x78, 0xc9, 0x0d, 0x12, 0x15, 0xef, 0x02, 0x95, 0x27, 0xe7, 0x21, 0x65, 0xb8, + 0xa9, 0xa2, 0xd3, 0xd7, 0xf9, 0x6c, 0x00, 0x17, 0xa8, 0x7b, 0xb9, 0xea, 0x9d, 0x14, 0x49, 0x2a, + 0xe7, 0x1a, 0x7f, 0x3b, 0x92, 0x29, 0x42, 0xef, 0x5a, 0xae, 0xda, 0xca, 0x62, 0xfd, 0x3a, 0xce, + 0x75, 0x9d, 0x09, 0xe1, 0x69, 0x41, 0x95, 0x3d, 0x6f, 0x96, 0xe3, 0xc3, 0x15, 0x5c, 0xd3, 0xd1, + 0xe3, 0x8d, 0x91, 0xd2, 0xd4, 0xae, 0xa3, 0x5c, 0x75, 0x05, 0x26, 0x94, 0x1b, 0x55, 0xae, 0x6e, + 0x31, 0xc7, 0xef, 0x5c, 0x00, 0x21, 0xcc, 0xfe, 0x00, 0x30, 0x57, 0xd5, 0x8f, 0x7f, 0x9d, 0x71, + 0x2b, 0x6a, 0x59, 0xe9, 0x67, 0x6c, 0xcd, 0x36, 0x64, 0x4f, 0xf8, 0x96, 0xb5, 0x8f, 0x4a, 0xae, + 0xea, 0x47, 0xe6, 0xc9, 0x05, 0xfe, 0x4b, 0x92, 0xe7, 0x27, 0x17, 0x40, 0x56, 0xa2, 0xb1, 0xab, + 0x81, 0x36, 0x60, 0x48, 0x43, 0xe7, 0x51, 0x5c, 0x8d, 0x86, 0x50, 0xd5, 0x79, 0x94, 0x86, 0xf1, + 0xe2, 0x7c, 0xc1, 0xad, 0x02, 0xe4, 0x16, 0x79, 0x82, 0x5a, 0xd1, 0xc7, 0x3a, 0xa1, 0x0e, 0x05, + 0xfb, 0xea, 0x29, 0xac, 0x3f, 0x25, 0xad, 0x0a, 0x3c, 0x5a, 0xf9, 0x42, 0x64, 0xe0, 0x5e, 0xe2, + 0xe5, 0x3b, 0x5d, 0x17, 0x45, 0xa3, 0xdf, 0xf9, 0xc0, 0x3e, 0x24, 0x61, 0x09, 0xa5, 0x63, 0x62, + 0xed, 0x64, 0xc9, 0xcc, 0xd6, 0xbc, 0x4d, 0xc9, 0xe8, 0x64, 0xed, 0x51, 0x69, 0x4b, 0x28, 0x15, + 0x2d, 0xfc, 0x57, 0x24, 0xc7, 0x4f, 0x5a, 0x90, 0x0a, 0xbe, 0xab, 0x53, 0x45, 0x93, 0x63, 0x3f, + 0xad, 0xe3, 0x5e, 0xee, 0x06, 0xcc, 0x0d, 0x95, 0xd8, 0xe5, 0xa7, 0x49, 0x9d, 0x5f, 0xa1, 0x34, + 0xc6, 0x80, 0x34, 0x90, 0xe7, 0xbf, 0xe3, 0x26, 0xca, 0xd8, 0xea, 0xd3, 0xd0, 0x96, 0xab, 0x3e, + 0x09, 0x85, 0x64, 0x3a, 0x0c, 0x57, 0x8d, 0x51, 0x6a, 0xcd, 0x6b, 0xd8, 0x25, 0xa9, 0x76, 0x6c, + 0x72, 0xf5, 0x69, 0xfe, 0xeb, 0xce, 0xbf, 0x31, 0x43, 0xa7, 0xa3, 0x2e, 0x4c, 0x11, 0xe1, 0x17, + 0x3f, 0x60, 0x18, 0xa2, 0x8c, 0xb4, 0x49, 0x53, 0x3b, 0xa6, 0xa6, 0x86, 0x76, 0x02, 0xc7, 0x00, + 0x2d, 0x7d, 0x2f, 0xd6, 0x8e, 0xa9, 0x3e, 0x0a, 0xd5, 0x40, 0x16, 0x57, 0xfd, 0x6b, 0x40, 0x90, + 0x16, 0xc4, 0x8f, 0x79, 0x43, 0xb0, 0x69, 0xed, 0x98, 0xb4, 0xea, 0xa3, 0xfc, 0xd7, 0xbe, 0xe7, + 0x48, 0x03, 0xf1, 0x7e, 0x3c, 0x63, 0x5f, 0x3f, 0x4e, 0x0a, 0xea, 0x16, 0x65, 0x78, 0x59, 0xcd, + 0xaf, 0xa9, 0x6e, 0xd5, 0xbe, 0x83, 0x69, 0xfa, 0x52, 0x5e, 0xf6, 0x2d, 0xfe, 0xc7, 0x8c, 0xbd, + 0xb4, 0x0d, 0x8c, 0x4a, 0x9b, 0x8c, 0x0b, 0x11, 0xfa, 0x0b, 0x6c, 0x92, 0xcc, 0x77, 0xfa, 0x22, + 0xf6, 0x60, 0x05, 0xd0, 0xa0, 0x70, 0xcd, 0x74, 0x2e, 0x06, 0x2a, 0xf7, 0x52, 0xd8, 0x2d, 0x15, + 0x25, 0xf5, 0x9d, 0xf7, 0x01, 0xf4, 0xb6, 0x96, 0x28, 0x3c, 0x0e, 0xc0, 0x95, 0x4b, 0x96, 0x61, + 0xd1, 0x59, 0x2c, 0xf2, 0x7e, 0x57, 0xb7, 0xf3, 0x49, 0x7d, 0x08, 0xc2, 0x3f, 0x56, 0xb9, 0xe4, + 0x51, 0x5a, 0xb5, 0x94, 0x3b, 0xf4, 0x32, 0x9d, 0x86, 0xde, 0x49, 0xce, 0xf1, 0x42, 0x6f, 0x14, + 0x3f, 0xae, 0x78, 0xb4, 0xfc, 0x47, 0x65, 0x52, 0x77, 0x46, 0xf9, 0xee, 0xde, 0x39, 0xc9, 0x37, + 0xde, 0x2c, 0xff, 0x5a, 0x51, 0x5b, 0x58, 0x73, 0xc7, 0xd5, 0xd2, 0xe6, 0xe0, 0xa9, 0xcd, 0xf2, + 0x8b, 0x4a, 0xb3, 0x74, 0xc3, 0x00, 0xff, 0x27, 0xe1, 0x48, 0x22, 0xb5, 0xca, 0xca, 0x25, 0xef, + 0xea, 0x10, 0xaf, 0xee, 0xbd, 0x85, 0xdf, 0xd1, 0x65, 0xa8, 0x1a, 0xc6, 0xb6, 0xd8, 0xb0, 0x8f, + 0x59, 0xce, 0xda, 0x4c, 0x2d, 0xb4, 0x93, 0x86, 0x7f, 0x94, 0xa4, 0x26, 0x7d, 0x40, 0xca, 0x3b, + 0x52, 0x3c, 0xa3, 0x98, 0xc9, 0x96, 0x9f, 0x78, 0x84, 0xd6, 0xde, 0x22, 0x28, 0x8a, 0xff, 0x49, + 0xa3, 0x51, 0x1e, 0x3d, 0x06, 0x72, 0x2c, 0x08, 0x28, 0xe4, 0x7f, 0x75, 0x61, 0x30, 0xa1, 0x29, + 0x8e, 0xc7, 0xb6, 0x0f, 0x2a, 0x6d, 0xf7, 0x44, 0x41, 0xad, 0x2f, 0x62, 0x34, 0x33, 0x06, 0xd9, + 0x97, 0x69, 0xbb, 0xbf, 0x29, 0xed, 0x28, 0x55, 0x9d, 0xe1, 0x30, 0x52, 0xc8, 0x1d, 0xc4, 0x81, + 0xc7, 0xd5, 0xbb, 0xa1, 0x19, 0x81, 0xf8, 0xc2, 0xd8, 0x4e, 0x8c, 0x6d, 0x10, 0x15, 0x93, 0x96, + 0x91, 0x7e, 0x67, 0x01, 0x39, 0x07, 0x22, 0x25, 0x7e, 0x30, 0x2c, 0x7d, 0x83, 0xbe, 0x31, 0xa1, + 0x31, 0xb6, 0x61, 0xff, 0x38, 0x86, 0x86, 0x83, 0x51, 0x90, 0x62, 0x3d, 0x91, 0xa4, 0x6c, 0x84, + 0xe4, 0x74, 0x11, 0x0f, 0xa9, 0x63, 0x3d, 0x92, 0x8a, 0x90, 0xbe, 0x04, 0x4f, 0xa5, 0x27, 0xb1, + 0x66, 0xf9, 0x3d, 0xde, 0xc7, 0x84, 0xc6, 0x91, 0x10, 0xbf, 0xed, 0xc1, 0x0e, 0x86, 0x5e, 0x1a, + 0x24, 0xc7, 0xfa, 0xc9, 0x39, 0x98, 0x03, 0xfb, 0x61, 0xa1, 0x42, 0xcd, 0x6c, 0x8e, 0x4b, 0xf1, + 0x4d, 0x16, 0x59, 0x45, 0xb7, 0x8c, 0xe4, 0xa4, 0x94, 0xc1, 0x12, 0x4e, 0x99, 0xa8, 0x44, 0xdf, + 0xdc, 0x86, 0xd4, 0x7b, 0xbc, 0x0f, 0x42, 0x20, 0x52, 0x0b, 0xdf, 0x20, 0xde, 0x7b, 0x25, 0x93, + 0x57, 0x5a, 0x3e, 0x8a, 0x15, 0xef, 0x0e, 0x9a, 0xec, 0x38, 0xc9, 0xce, 0x92, 0x05, 0x8a, 0xc5, + 0xd6, 0xb2, 0xc9, 0xd5, 0x6e, 0xbe, 0x43, 0x1c, 0x11, 0x9c, 0x66, 0xe1, 0x48, 0xdc, 0xb0, 0x84, + 0x5f, 0x47, 0xce, 0xc2, 0x82, 0x86, 0x2c, 0x0e, 0xe1, 0xcf, 0x78, 0xa6, 0xc1, 0x28, 0x83, 0xc9, + 0x8e, 0x6c, 0x71, 0xae, 0x06, 0x26, 0xa5, 0x8a, 0x4e, 0xe0, 0x03, 0x19, 0xcb, 0x82, 0xa0, 0x28, + 0x59, 0x61, 0xcc, 0x03, 0x3c, 0x69, 0x08, 0xb9, 0x9e, 0x70, 0xac, 0xf2, 0x58, 0x62, 0x4d, 0xfe, + 0xc0, 0xe3, 0x7b, 0x47, 0x22, 0x33, 0x57, 0x69, 0x10, 0x3e, 0x89, 0xef, 0x22, 0x0d, 0xf6, 0x66, + 0x50, 0x66, 0x60, 0x82, 0xd2, 0x94, 0x00, 0xc6, 0x4b, 0xc6, 0x2a, 0xe6, 0x5b, 0x3b, 0x25, 0xbd, + 0xfa, 0x22, 0xff, 0xa5, 0x6f, 0x42, 0x88, 0xc3, 0x31, 0xb4, 0x32, 0xc8, 0x9f, 0xc8, 0xde, 0x22, + 0x55, 0x37, 0x8c, 0xd4, 0x38, 0x2a, 0xf1, 0x3b, 0xa8, 0xa5, 0xa4, 0xf6, 0x02, 0xa9, 0x05, 0x92, + 0xb1, 0x93, 0x9c, 0x25, 0x37, 0x84, 0x2b, 0x5c, 0xad, 0x78, 0x06, 0x16, 0xa0, 0x9a, 0x8c, 0x01, + 0xa0, 0x9b, 0xfb, 0xa0, 0x3f, 0xce, 0xf3, 0xad, 0x2a, 0x63, 0x3f, 0x87, 0xf4, 0xdd, 0x38, 0x4c, + 0xe9, 0xab, 0xe8, 0x02, 0x64, 0xd7, 0xec, 0xa7, 0xc9, 0xb1, 0xdd, 0x89, 0xfb, 0x68, 0x62, 0x54, + 0xe1, 0x17, 0x17, 0x54, 0x0d, 0x50, 0x5a, 0x1f, 0x95, 0x56, 0x80, 0xb8, 0x6b, 0x0e, 0x51, 0x5a, + 0xf7, 0x3d, 0xfe, 0x19, 0x5a, 0xdf, 0x17, 0xe2, 0xdd, 0xbe, 0x05, 0xff, 0x48, 0xdc, 0xf3, 0x43, + 0xb0, 0xad, 0x69, 0xb0, 0xb9, 0x51, 0xda, 0xca, 0x12, 0x03, 0x00, 0x6b, 0x7e, 0x49, 0x7b, 0xbc, + 0xf6, 0x62, 0x3b, 0xc3, 0x24, 0xf1, 0x97, 0xc4, 0x11, 0xbe, 0xd9, 0xd4, 0x81, 0x05, 0x5d, 0x63, + 0xf5, 0x45, 0x67, 0xd7, 0xad, 0x39, 0xfa, 0x42, 0x7a, 0x14, 0xc7, 0x78, 0x87, 0xf6, 0x88, 0x49, + 0xf2, 0x7d, 0x41, 0xcd, 0xf1, 0xb6, 0x49, 0x0a, 0xed, 0x05, 0x42, 0xd6, 0xaa, 0x76, 0x75, 0xf3, + 0x61, 0xe4, 0x5b, 0xdb, 0x63, 0x97, 0x18, 0x46, 0x9e, 0x4f, 0xf3, 0x2e, 0xd5, 0xe1, 0x98, 0x2f, + 0xc1, 0x6e, 0xd3, 0x30, 0x54, 0x5f, 0x2f, 0xce, 0x0f, 0x5a, 0xd1, 0xde, 0x57, 0x4e, 0x86, 0x31, + 0x5c, 0xea, 0x49, 0x71, 0xac, 0xa1, 0xce, 0xf1, 0x08, 0xe1, 0xfd, 0x10, 0x5f, 0x9f, 0x25, 0x97, + 0x20, 0xd2, 0x22, 0x37, 0x2a, 0x17, 0xc7, 0x32, 0xd2, 0x93, 0x2c, 0xdf, 0x23, 0x46, 0xc7, 0x1a, + 0xea, 0xec, 0x5f, 0xb3, 0x7d, 0xa4, 0x2e, 0xe1, 0x5a, 0x56, 0xa6, 0xd9, 0xb6, 0x19, 0xd6, 0x66, + 0xf9, 0x12, 0x4d, 0x5a, 0x20, 0x62, 0xf3, 0x53, 0xcc, 0xbe, 0x31, 0x66, 0xdb, 0x4a, 0x10, 0xa3, + 0xdc, 0x48, 0xe1, 0x48, 0xc1, 0xeb, 0xb6, 0x2f, 0x11, 0xb0, 0x4b, 0x01, 0x0c, 0xd2, 0x88, 0x14, + 0x6a, 0x24, 0xa3, 0x96, 0x18, 0x7b, 0x1b, 0x22, 0xa8, 0xb7, 0x78, 0x17, 0x89, 0xa9, 0x86, 0x68, + 0x71, 0x1e, 0x98, 0xb4, 0x33, 0x1a, 0x42, 0x15, 0xad, 0xbc, 0x7b, 0x57, 0x10, 0xae, 0x40, 0x85, + 0xbe, 0xf0, 0xbd, 0xab, 0x80, 0x9d, 0x7d, 0x0b, 0x60, 0x70, 0x2e, 0xb5, 0x19, 0xf2, 0xd1, 0xd4, + 0xe6, 0x4c, 0xf9, 0x09, 0x8a, 0x5c, 0x15, 0xe0, 0x7b, 0x2d, 0xb2, 0x55, 0x19, 0xc8, 0x16, 0x86, + 0xe4, 0x25, 0x29, 0x15, 0xe0, 0x82, 0xbf, 0x52, 0xf6, 0x36, 0x42, 0x7e, 0x63, 0x4a, 0x25, 0x98, + 0x8c, 0x33, 0xd6, 0x96, 0x7b, 0x19, 0x5a, 0x4d, 0x54, 0x5a, 0xc9, 0x13, 0x3f, 0xa7, 0xfb, 0x6d, + 0x6f, 0x1c, 0x60, 0x19, 0x9b, 0x1b, 0x9e, 0x65, 0x36, 0x58, 0x51, 0x25, 0x07, 0x02, 0x48, 0x33, + 0x4d, 0x72, 0x21, 0x6f, 0xcd, 0xe7, 0x5e, 0x77, 0x53, 0x25, 0xc3, 0xb8, 0x20, 0xa3, 0x47, 0xe8, + 0x8b, 0xe0, 0x5e, 0xf7, 0x08, 0xbd, 0x3a, 0x88, 0x23, 0x82, 0x41, 0x23, 0xe4, 0xfe, 0x52, 0xe6, + 0xef, 0xa4, 0xe4, 0x76, 0xa1, 0x7f, 0x22, 0x97, 0xe2, 0x19, 0x84, 0x1d, 0x96, 0x92, 0x75, 0x43, + 0x60, 0xd9, 0x98, 0xaa, 0x18, 0xd5, 0xb5, 0xb1, 0xfa, 0xd3, 0x30, 0xd7, 0x51, 0x35, 0x71, 0xda, + 0xda, 0xd5, 0x63, 0xd9, 0xa5, 0xbe, 0x6b, 0x0d, 0x2a, 0x2d, 0x13, 0x54, 0x64, 0x0b, 0x6e, 0xdb, + 0x1a, 0x61, 0xd9, 0x85, 0x08, 0x8e, 0xd7, 0xe8, 0x4f, 0xd7, 0xc6, 0xdd, 0x05, 0x6d, 0x35, 0xd0, + 0x76, 0xa9, 0xe1, 0x14, 0x7f, 0xd5, 0x37, 0x56, 0x7f, 0xda, 0xd0, 0xec, 0x18, 0x95, 0x70, 0x8a, + 0xad, 0x6b, 0x08, 0x63, 0x96, 0x2e, 0xe3, 0xfb, 0xd6, 0x84, 0x42, 0x63, 0x40, 0x9e, 0xa3, 0xae, + 0x0d, 0xab, 0x9d, 0xba, 0xb0, 0x9d, 0x1f, 0xa7, 0xef, 0x4e, 0x03, 0xd3, 0x8f, 0xa9, 0x55, 0xcd, + 0xa9, 0x8d, 0xd7, 0xa7, 0x2d, 0x6c, 0x77, 0x5e, 0xd5, 0x1f, 0x5d, 0xd8, 0xce, 0x6d, 0xf7, 0x48, + 0xa9, 0x23, 0x03, 0x98, 0x70, 0xac, 0xb9, 0x15, 0x36, 0xd2, 0x7e, 0xdc, 0xa1, 0x30, 0xee, 0x50, + 0xac, 0x54, 0x7e, 0x58, 0x18, 0x98, 0x28, 0x95, 0xeb, 0xb8, 0x14, 0xbf, 0xf4, 0xfc, 0x61, 0x21, + 0x30, 0x51, 0x7a, 0x1e, 0x9e, 0x6f, 0x80, 0xdf, 0x9b, 0xad, 0x60, 0x7c, 0x88, 0x3b, 0xa4, 0x9a, + 0x03, 0x5d, 0xb8, 0x43, 0x71, 0x7a, 0x69, 0xf3, 0x61, 0x29, 0x43, 0x07, 0x8d, 0xb8, 0x94, 0xa6, + 0xa1, 0xad, 0xd3, 0x0c, 0x7d, 0xce, 0x33, 0x30, 0x9c, 0x77, 0xe8, 0x70, 0x43, 0xe2, 0x54, 0x13, + 0x44, 0xa6, 0x4e, 0x9d, 0x98, 0x83, 0xe1, 0xea, 0x32, 0x7d, 0x40, 0xef, 0xae, 0x8d, 0x7b, 0xe4, + 0xe1, 0xb8, 0x87, 0xf9, 0xb1, 0xc0, 0xe9, 0xdd, 0x86, 0x3e, 0xe9, 0x45, 0x96, 0xff, 0x6f, 0xdf, + 0x18, 0x48, 0x1b, 0xa2, 0xb8, 0xed, 0x6e, 0x58, 0x15, 0xa4, 0x54, 0x26, 0x10, 0xea, 0xaf, 0x77, + 0x67, 0x80, 0x68, 0x2b, 0x5f, 0x60, 0x59, 0x46, 0x4a, 0x62, 0xf9, 0xee, 0x21, 0xd1, 0x37, 0xba, + 0xe2, 0x40, 0x71, 0x94, 0x64, 0xf4, 0x43, 0x70, 0x6b, 0xfb, 0x9e, 0x7a, 0x6c, 0xb0, 0x43, 0xc3, + 0x09, 0xe7, 0xd2, 0x4a, 0x03, 0xc3, 0xbd, 0x59, 0x2f, 0x04, 0xc2, 0xb9, 0xd7, 0x1b, 0xab, 0x6f, + 0xf0, 0x73, 0x0e, 0x05, 0xd0, 0xd6, 0x1b, 0xa8, 0x2f, 0x8a, 0x3d, 0x6b, 0x9f, 0x96, 0x5c, 0x93, + 0xba, 0xa4, 0x0b, 0x13, 0x51, 0x72, 0x82, 0x3f, 0x28, 0x19, 0xbb, 0x8a, 0x55, 0x74, 0xd7, 0x2a, + 0x72, 0x61, 0x70, 0x77, 0xcb, 0x17, 0x0e, 0xe3, 0x0e, 0x8f, 0xb6, 0xe5, 0x84, 0x05, 0x60, 0xb2, + 0x10, 0xa5, 0xc2, 0x34, 0xc9, 0xf7, 0xcc, 0x07, 0x05, 0x72, 0xcd, 0x82, 0x3f, 0xef, 0xe3, 0xd3, + 0x03, 0xa0, 0x75, 0x3d, 0x0d, 0xe8, 0x0a, 0xf8, 0x58, 0x6e, 0x97, 0x51, 0x6d, 0x96, 0x93, 0xae, + 0x53, 0xe5, 0xf2, 0xc5, 0xda, 0x3e, 0x07, 0x5d, 0xeb, 0x7c, 0x13, 0x23, 0xb4, 0x01, 0x96, 0x1f, + 0x2b, 0x6f, 0x53, 0xe3, 0xd9, 0x10, 0xfc, 0x79, 0xff, 0x03, 0xd0, 0x55, 0x5f, 0xc4, 0x7b, 0xf8, + 0xe5, 0x72, 0xf3, 0x23, 0x6c, 0xf7, 0xc3, 0x92, 0xed, 0x2b, 0xad, 0xdc, 0x3c, 0x9a, 0xe1, 0x97, + 0x90, 0x06, 0x31, 0x29, 0x06, 0xa0, 0x3d, 0xc2, 0xd6, 0xb8, 0xc0, 0x0b, 0x0f, 0x41, 0x7e, 0xa9, + 0xbf, 0xc8, 0xed, 0x4a, 0xd3, 0x64, 0x07, 0x36, 0x02, 0xf2, 0x3f, 0x5e, 0xa3, 0xbc, 0x82, 0x99, + 0x6b, 0x88, 0x47, 0x5e, 0xc6, 0x20, 0x5d, 0xbc, 0x36, 0x3d, 0x65, 0xd1, 0xd6, 0xb8, 0xad, 0xe7, + 0x7d, 0x23, 0x00, 0xf6, 0x10, 0x3d, 0x9c, 0xfa, 0x10, 0xad, 0x81, 0x1f, 0x6d, 0x63, 0x50, 0xdd, + 0xc7, 0xfd, 0xcf, 0x50, 0x1b, 0xe1, 0x35, 0xb6, 0x73, 0xca, 0x06, 0xf4, 0x8a, 0x9b, 0x81, 0x80, + 0xfc, 0x20, 0xa4, 0x00, 0xb2, 0xd7, 0x17, 0xda, 0x8a, 0x26, 0x27, 0x6b, 0xef, 0x69, 0x9b, 0x1a, + 0xce, 0x34, 0x44, 0xe0, 0x5f, 0xd6, 0xf6, 0x07, 0x40, 0x90, 0x29, 0xf7, 0x28, 0x29, 0x1f, 0x69, + 0x00, 0xc2, 0x92, 0x6c, 0xe5, 0x97, 0x30, 0x7a, 0xbf, 0x32, 0x04, 0x36, 0xd3, 0xf6, 0x3d, 0x8d, + 0xf2, 0x4f, 0x0e, 0x81, 0xc5, 0xd8, 0x54, 0xed, 0x08, 0xdb, 0x3f, 0x08, 0xe3, 0xaa, 0x05, 0x0c, + 0x79, 0x82, 0x03, 0x20, 0x8d, 0xec, 0x9e, 0xe3, 0xa5, 0x90, 0xd7, 0xad, 0x88, 0xd3, 0x7b, 0x46, + 0xad, 0xd0, 0xf1, 0x19, 0xfa, 0xa3, 0xd2, 0x04, 0xad, 0x83, 0x61, 0x12, 0x5a, 0x0e, 0x6b, 0x21, + 0x64, 0x8d, 0x3d, 0x9a, 0x69, 0x2e, 0x66, 0xb2, 0x70, 0x57, 0x59, 0x83, 0x58, 0x62, 0x2c, 0x72, + 0x3f, 0x45, 0xa7, 0x2a, 0x8e, 0xb6, 0x69, 0x8b, 0xc3, 0x18, 0x5b, 0xe5, 0x05, 0x20, 0xff, 0x9d, + 0xf9, 0xa1, 0x44, 0x62, 0x92, 0x4d, 0xb8, 0x84, 0x4b, 0xba, 0x4d, 0x85, 0x6e, 0x22, 0x96, 0x6a, + 0x86, 0x2a, 0xa8, 0x92, 0x0d, 0x11, 0xc8, 0x3f, 0x0b, 0x49, 0xb8, 0x5f, 0x21, 0xc1, 0xf7, 0x15, + 0xf2, 0x0c, 0x29, 0xa9, 0x3c, 0x1e, 0xc8, 0xb2, 0xee, 0xfb, 0x21, 0xbc, 0xeb, 0x43, 0x9d, 0xf3, + 0x06, 0xf1, 0x5a, 0xf7, 0xf5, 0x86, 0x7f, 0xab, 0x4c, 0x7f, 0xe5, 0x92, 0x3f, 0x53, 0xa1, 0xfd, + 0x54, 0xef, 0xb6, 0xee, 0xbb, 0x11, 0xfe, 0x2d, 0x7e, 0xf5, 0x85, 0x7f, 0xbb, 0xb3, 0x92, 0x96, + 0x76, 0x6a, 0x68, 0xd3, 0x10, 0x30, 0x5c, 0x01, 0x36, 0x5a, 0xf7, 0xf5, 0xc3, 0xd7, 0x28, 0xeb, + 0xbe, 0x9b, 0xe1, 0xdf, 0x36, 0x44, 0x20, 0x02, 0x96, 0x22, 0x93, 0xff, 0x02, 0x7a, 0xa2, 0x8c, + 0xc3, 0xf2, 0x11, 0xa0, 0x67, 0x3e, 0x35, 0xf6, 0xef, 0x02, 0xc5, 0x1f, 0x54, 0xee, 0x85, 0x62, + 0x45, 0x0c, 0xb8, 0x05, 0x31, 0x29, 0x9a, 0xdb, 0xd1, 0xb8, 0x84, 0xad, 0x04, 0x87, 0x28, 0xec, + 0xc5, 0xb0, 0x33, 0x27, 0x5a, 0xe4, 0xb5, 0xef, 0x9d, 0x99, 0x4a, 0x95, 0xe6, 0x2e, 0x1b, 0x83, + 0x67, 0x91, 0xad, 0xf2, 0x97, 0xd3, 0x31, 0xfe, 0x06, 0x2e, 0xdf, 0xc7, 0x1a, 0x29, 0xaf, 0xc3, + 0xda, 0x18, 0xae, 0x65, 0xac, 0xe4, 0xf9, 0x30, 0xe2, 0xf1, 0x74, 0xaa, 0xac, 0xe4, 0xb1, 0x48, + 0xa1, 0x9d, 0xb3, 0x92, 0x14, 0x15, 0x71, 0xa8, 0x49, 0xb6, 0x46, 0xaa, 0x38, 0x80, 0x31, 0x50, + 0xae, 0x9c, 0xf0, 0x10, 0x15, 0x5a, 0x63, 0x7a, 0x3c, 0xb3, 0x14, 0x03, 0x65, 0xb5, 0xc8, 0x90, + 0x74, 0xc8, 0x5b, 0x85, 0x76, 0x0d, 0x49, 0x8f, 0xf6, 0xf8, 0xa0, 0x6b, 0x7e, 0x64, 0x6e, 0x6e, + 0xf1, 0x78, 0xf9, 0x9d, 0x2b, 0x54, 0xe9, 0x0e, 0x41, 0xb4, 0xcb, 0x74, 0xa7, 0xab, 0x59, 0xfe, + 0x5e, 0x48, 0x4d, 0xf5, 0xdd, 0x78, 0x0c, 0x23, 0x9a, 0x62, 0xd0, 0xa3, 0xec, 0x78, 0x34, 0x3a, + 0x33, 0xf0, 0x10, 0x12, 0x60, 0x96, 0x0f, 0xcc, 0xa3, 0x78, 0x71, 0x3f, 0x79, 0xa1, 0x72, 0xde, + 0xb1, 0xf1, 0x32, 0x55, 0xb7, 0x4b, 0x7d, 0xa1, 0x60, 0xaa, 0xaa, 0xab, 0x17, 0x50, 0xd9, 0x98, + 0x38, 0x98, 0xa3, 0x86, 0x07, 0xe9, 0x7c, 0xbe, 0x8f, 0xf9, 0xbd, 0xe4, 0xba, 0x89, 0x09, 0x43, + 0x03, 0xac, 0xd1, 0x48, 0x85, 0xeb, 0x05, 0x10, 0x97, 0x64, 0x6c, 0x22, 0x1e, 0xe1, 0x32, 0x24, + 0x69, 0xd5, 0x1e, 0x19, 0x92, 0x86, 0x4a, 0x49, 0xd9, 0xac, 0x51, 0x60, 0x6e, 0x0a, 0x3b, 0xa2, + 0x77, 0x27, 0x1d, 0xc6, 0xcd, 0x5d, 0x8c, 0xce, 0xf8, 0x0e, 0x65, 0xc3, 0x24, 0x89, 0xab, 0x3e, + 0xa7, 0xa2, 0x61, 0x2d, 0xf2, 0x2b, 0x6d, 0x8f, 0x83, 0x5e, 0xd9, 0xf2, 0x5c, 0x85, 0xba, 0x46, + 0xba, 0xdb, 0xc6, 0x64, 0x40, 0xfd, 0xc1, 0x79, 0x38, 0x29, 0xe4, 0x93, 0x69, 0xb8, 0x9c, 0xd3, + 0x21, 0x09, 0xdd, 0x72, 0xb6, 0x36, 0x52, 0x6a, 0x26, 0x63, 0xf8, 0x38, 0x5e, 0xfe, 0xe5, 0x17, + 0x54, 0x06, 0xdd, 0xae, 0x78, 0xa8, 0x62, 0x39, 0x57, 0x31, 0xa0, 0x2e, 0x2e, 0x97, 0xd5, 0x0f, + 0x06, 0xf3, 0x69, 0x52, 0xd1, 0x4e, 0x16, 0xea, 0xbb, 0x49, 0xb9, 0x96, 0x94, 0xcf, 0x23, 0xe5, + 0x89, 0xa2, 0x4b, 0x43, 0x77, 0x92, 0xb4, 0x74, 0xff, 0x6d, 0x21, 0x79, 0x6c, 0x71, 0x63, 0x0a, + 0x52, 0x10, 0x10, 0x97, 0xd0, 0x19, 0x0c, 0xf0, 0xd1, 0x74, 0x06, 0xe5, 0xe9, 0xf1, 0x0a, 0x86, + 0x46, 0x3a, 0x7f, 0x8d, 0xc6, 0x26, 0x2d, 0xc3, 0x42, 0x10, 0x03, 0x7c, 0x59, 0x89, 0xf1, 0x04, + 0xe9, 0xa5, 0x93, 0x67, 0x3c, 0x4e, 0xf8, 0x33, 0x84, 0x6f, 0x29, 0x8e, 0xb7, 0x48, 0xfc, 0xdb, + 0x16, 0x79, 0xc3, 0x5c, 0x85, 0x0b, 0xe3, 0x01, 0x9c, 0x3b, 0x92, 0xd7, 0x25, 0x86, 0x13, 0xe3, + 0x4e, 0xd1, 0x58, 0x4d, 0x8c, 0x7f, 0x15, 0x8d, 0x95, 0xd0, 0xe1, 0x3f, 0x8a, 0x67, 0x58, 0x14, + 0x49, 0x99, 0xe5, 0xfb, 0x95, 0xd6, 0xa2, 0xf1, 0x0c, 0x71, 0x6d, 0xa6, 0x49, 0x43, 0x8b, 0xb2, + 0x57, 0x8a, 0x9b, 0x80, 0xa6, 0xe3, 0x44, 0x2a, 0xa7, 0x4c, 0xe3, 0xae, 0x22, 0xa5, 0x49, 0xef, + 0x36, 0xe7, 0xca, 0x51, 0x4a, 0x27, 0x8b, 0x05, 0x98, 0xff, 0xf5, 0x65, 0x85, 0x79, 0xe3, 0xbb, + 0xc8, 0x39, 0x6e, 0x20, 0x63, 0x33, 0xf1, 0x1e, 0xa4, 0xb8, 0xf2, 0x85, 0x45, 0x01, 0x86, 0x8f, + 0x78, 0x9c, 0xef, 0x06, 0x8f, 0x3c, 0x23, 0x38, 0x24, 0x25, 0xf2, 0xe3, 0x39, 0xc1, 0xcd, 0x79, + 0xba, 0x4b, 0xab, 0x0c, 0x02, 0x93, 0x69, 0x3a, 0x2e, 0x2a, 0x1b, 0xb5, 0xc6, 0x16, 0x91, 0x92, + 0x03, 0x31, 0x26, 0x1d, 0xd6, 0x86, 0x9e, 0x3a, 0x5b, 0xfe, 0xc5, 0x9c, 0xc1, 0x81, 0x75, 0x43, + 0x07, 0xbe, 0x0a, 0xfa, 0xb7, 0x17, 0x1b, 0x52, 0x1b, 0x10, 0xf3, 0x64, 0x88, 0x91, 0x2b, 0x5f, + 0x50, 0x83, 0xe7, 0x8d, 0x58, 0x06, 0xa3, 0x1f, 0x35, 0x9c, 0xe4, 0xd2, 0x4f, 0x92, 0x15, 0xd1, + 0xc5, 0xac, 0xfe, 0x68, 0x66, 0x26, 0x2c, 0x0c, 0x1f, 0xd1, 0xcd, 0xed, 0x68, 0xf0, 0x3b, 0xae, + 0x4f, 0xa0, 0x6f, 0xd5, 0xd6, 0x68, 0xc5, 0x9a, 0xbc, 0x50, 0x08, 0x4d, 0x03, 0xe9, 0xe5, 0xc7, + 0x29, 0x13, 0xf1, 0xdb, 0x69, 0x8a, 0x22, 0x22, 0x9c, 0xe4, 0xc9, 0x8d, 0x94, 0x13, 0x3a, 0x19, + 0x8a, 0x1d, 0x49, 0x8f, 0x22, 0xad, 0xf5, 0x74, 0x3e, 0x94, 0xbd, 0x69, 0x77, 0x70, 0x3f, 0xda, + 0x45, 0xcf, 0x0a, 0xe2, 0xcd, 0x92, 0xf1, 0x6d, 0xb3, 0x7c, 0x5a, 0xaf, 0xf8, 0xa2, 0x00, 0x4c, + 0x0e, 0x9b, 0x82, 0xe7, 0x05, 0x74, 0x66, 0xdc, 0x74, 0x66, 0x8e, 0xe0, 0xcc, 0x38, 0x47, 0x07, + 0x36, 0x46, 0xe7, 0x66, 0xcb, 0x6f, 0x28, 0x2d, 0xb1, 0x97, 0x19, 0x78, 0x9d, 0xd7, 0xae, 0x2c, + 0x63, 0x79, 0xef, 0x0a, 0x57, 0xd8, 0x9a, 0x35, 0x1a, 0xb1, 0x42, 0x26, 0xbd, 0xe4, 0x2a, 0x84, + 0x60, 0x5c, 0x6a, 0x03, 0x49, 0x56, 0x83, 0x65, 0xfa, 0x59, 0x81, 0x92, 0xcd, 0x80, 0x73, 0xa5, + 0xe6, 0x6f, 0xf4, 0xca, 0x5f, 0x4e, 0x1d, 0x24, 0x5b, 0x34, 0x7a, 0xd9, 0x06, 0x88, 0xf3, 0xd8, + 0x4b, 0xa4, 0x42, 0xd6, 0x9f, 0xd6, 0x1f, 0x4d, 0x06, 0x1f, 0x43, 0x1a, 0x49, 0x3d, 0x48, 0x8a, + 0x54, 0x78, 0xc5, 0xc5, 0x6f, 0x75, 0xe0, 0x76, 0x42, 0x1a, 0xf5, 0x2c, 0x88, 0x80, 0x7b, 0x79, + 0x04, 0xee, 0x1b, 0xe4, 0xbd, 0x46, 0x72, 0x5e, 0x21, 0xa6, 0x1a, 0x6b, 0x88, 0x69, 0x2b, 0x39, + 0x98, 0x06, 0x0f, 0x54, 0xd0, 0x43, 0xd9, 0x1f, 0xc2, 0xfc, 0x45, 0xb2, 0x9d, 0x32, 0x4f, 0x77, + 0x3b, 0x39, 0x3d, 0x04, 0xe0, 0x3d, 0x17, 0x48, 0xce, 0x9b, 0xd4, 0xd6, 0x70, 0xf6, 0x9b, 0x13, + 0x87, 0xaa, 0xa8, 0x68, 0xec, 0x02, 0xf7, 0x72, 0x9b, 0x20, 0xe8, 0x14, 0x2f, 0xb9, 0x74, 0x6b, + 0x8a, 0xf9, 0x02, 0xf6, 0x20, 0x62, 0x13, 0xde, 0x42, 0xcc, 0x0c, 0x7b, 0x43, 0xb8, 0x91, 0x64, + 0xff, 0x4f, 0xc0, 0xed, 0x11, 0xf9, 0xb6, 0x9e, 0xcf, 0x45, 0xe3, 0x9b, 0x82, 0x97, 0x61, 0x4f, + 0x8c, 0x6d, 0x11, 0x73, 0xbc, 0x86, 0xb3, 0xe4, 0x14, 0xb7, 0xf4, 0x04, 0x31, 0xb5, 0x41, 0x6d, + 0xde, 0x6b, 0xb1, 0xe7, 0x45, 0x53, 0x0d, 0xb7, 0xf4, 0xa4, 0xc1, 0xcb, 0xa5, 0x5c, 0xe3, 0x52, + 0xf9, 0x36, 0x6e, 0xa9, 0x57, 0xcc, 0x79, 0x65, 0xa9, 0xd0, 0x3f, 0x92, 0xf4, 0x00, 0xf7, 0x1c, + 0xb5, 0x65, 0x5f, 0x1c, 0x2c, 0xd1, 0x07, 0xa1, 0x18, 0x98, 0x6f, 0xb6, 0x3d, 0x88, 0xe1, 0x73, + 0xcf, 0x7f, 0xd3, 0xd1, 0x95, 0xe3, 0xc5, 0x15, 0x2a, 0x70, 0x5a, 0xee, 0x54, 0x8b, 0x59, 0x2e, + 0xa4, 0xd6, 0x8e, 0x9b, 0xc4, 0xaa, 0x86, 0xb4, 0x24, 0x56, 0xd9, 0x73, 0xa5, 0xfb, 0x91, 0x52, + 0x26, 0x4b, 0x26, 0xe0, 0x03, 0x99, 0x57, 0x83, 0xf6, 0x9f, 0x10, 0xb4, 0xde, 0x62, 0x06, 0xba, + 0x79, 0x3a, 0x28, 0x36, 0x29, 0x23, 0x5c, 0xe8, 0x0b, 0xb3, 0xa7, 0xea, 0x2f, 0xa6, 0x08, 0xbd, + 0x01, 0xe7, 0xc4, 0x7f, 0xdb, 0x7d, 0x71, 0x4d, 0x20, 0x2d, 0x0e, 0x30, 0x34, 0xcc, 0x08, 0x62, + 0xc8, 0x06, 0x04, 0x95, 0x0a, 0x02, 0x70, 0x6e, 0x9e, 0xf0, 0x14, 0xa1, 0x8f, 0xdd, 0x74, 0x58, + 0x84, 0x84, 0xad, 0xa2, 0x1d, 0xd2, 0xfc, 0xf4, 0xaa, 0x00, 0xee, 0xb9, 0x73, 0xae, 0xc9, 0x37, + 0x80, 0x91, 0xd1, 0xb6, 0x5f, 0xe2, 0x02, 0xb7, 0x90, 0x92, 0xaf, 0xaa, 0x3a, 0x82, 0xfe, 0x74, + 0x98, 0xb3, 0xb5, 0xc3, 0xd2, 0x47, 0xe3, 0x71, 0x18, 0x62, 0xad, 0x32, 0x44, 0x43, 0xd2, 0x68, + 0x65, 0x19, 0x82, 0x45, 0x66, 0x86, 0xc4, 0x9f, 0xc1, 0xa9, 0xb2, 0xc8, 0xd3, 0x66, 0x2b, 0xf3, + 0x94, 0x16, 0x6f, 0xb6, 0x30, 0x0c, 0x4c, 0xc9, 0x44, 0xf9, 0xa3, 0xcf, 0x28, 0x15, 0xdc, 0xee, + 0xb4, 0xf8, 0x25, 0x2b, 0x00, 0xc8, 0xf3, 0xdc, 0x6e, 0x23, 0x6e, 0xd3, 0x73, 0xbb, 0xf9, 0xef, + 0xd9, 0x86, 0xe2, 0x48, 0x9b, 0x89, 0x9e, 0x66, 0xde, 0x3b, 0x3b, 0xb8, 0xec, 0xf2, 0x4f, 0x42, + 0xdb, 0xc8, 0x25, 0x0c, 0xd8, 0xd5, 0xc6, 0x5c, 0x31, 0x6d, 0x06, 0xb7, 0xdb, 0xef, 0x91, 0x35, + 0x42, 0xbb, 0x1f, 0x1a, 0xab, 0x6c, 0x35, 0x5f, 0x60, 0xe3, 0xee, 0x59, 0xa1, 0xc6, 0x8b, 0x03, + 0x1b, 0xb5, 0xb2, 0xf5, 0xf3, 0x60, 0x4c, 0x92, 0x41, 0xf7, 0x11, 0x46, 0xdb, 0xe2, 0x20, 0x58, + 0x90, 0x6d, 0x34, 0xf9, 0x56, 0x09, 0x47, 0x66, 0x40, 0xcc, 0x6a, 0x6b, 0x87, 0xb5, 0xdd, 0x37, + 0xde, 0xf6, 0x39, 0x70, 0xe4, 0xd3, 0xd8, 0x74, 0x5f, 0xd0, 0x85, 0x3d, 0x5a, 0xe1, 0xef, 0xf5, + 0xeb, 0x41, 0xfe, 0xe8, 0x72, 0xb2, 0x19, 0xb7, 0x7a, 0x78, 0x8d, 0xab, 0xdb, 0xf9, 0xa0, 0x45, + 0xf6, 0x28, 0xe1, 0x0e, 0x2c, 0x4f, 0x3a, 0x89, 0xba, 0x79, 0x70, 0x05, 0x75, 0xff, 0xa3, 0xe4, + 0x2d, 0xe0, 0x0a, 0xae, 0xa1, 0xf5, 0x6f, 0x47, 0xb8, 0x2f, 0x55, 0xef, 0x96, 0x3e, 0xa1, 0x7b, + 0xfd, 0x91, 0x24, 0x4d, 0x4d, 0xd2, 0x54, 0xd6, 0xc6, 0xb4, 0x30, 0x98, 0x1b, 0x3f, 0xae, 0x2a, + 0x57, 0x61, 0x81, 0x39, 0x8c, 0x67, 0x92, 0x53, 0x7b, 0x21, 0x08, 0x79, 0x56, 0x3e, 0xa4, 0x48, + 0x45, 0x4c, 0x8b, 0x44, 0x8b, 0xd0, 0x08, 0x97, 0xfd, 0x53, 0x7b, 0xc5, 0xd5, 0x6c, 0xb4, 0xf4, + 0x09, 0x7a, 0xfa, 0xe2, 0x39, 0x28, 0xcc, 0x7f, 0x3d, 0x18, 0x5c, 0xfb, 0x00, 0x81, 0xf0, 0x15, + 0x48, 0xd0, 0x6d, 0x31, 0x17, 0x6b, 0x64, 0x41, 0xe9, 0x8a, 0xe7, 0x00, 0x9c, 0xeb, 0x09, 0x18, + 0x9f, 0xdb, 0xed, 0x1a, 0xa7, 0x9c, 0x03, 0x29, 0xe3, 0xdb, 0x5e, 0xa5, 0xdb, 0x05, 0x16, 0xf9, + 0xcd, 0x20, 0x82, 0x35, 0x61, 0x92, 0x0b, 0x0d, 0x2e, 0xc0, 0x6b, 0xcd, 0xf2, 0x5f, 0x69, 0x44, + 0xa9, 0x1e, 0x8e, 0x75, 0xba, 0xfc, 0xe0, 0x50, 0xac, 0xe1, 0xb8, 0x15, 0x65, 0xf4, 0xb7, 0x14, + 0x27, 0xca, 0xce, 0xfb, 0x14, 0x8d, 0x6a, 0x10, 0x8d, 0xcd, 0xfa, 0x00, 0xd9, 0xaa, 0x23, 0x69, + 0x71, 0x3b, 0xd9, 0x9a, 0xc8, 0x9a, 0xb4, 0x78, 0xb2, 0x66, 0x06, 0xb0, 0x38, 0xd3, 0x08, 0xf6, + 0x08, 0x78, 0xdf, 0x56, 0xc2, 0x23, 0x5c, 0x34, 0xf3, 0xba, 0xa4, 0x55, 0x61, 0xd9, 0xb2, 0x21, + 0xd8, 0x35, 0x4d, 0xdd, 0xc0, 0xcc, 0x16, 0xd3, 0xd4, 0x49, 0x01, 0x5e, 0xa6, 0x24, 0x7c, 0x3d, + 0x33, 0x48, 0x97, 0x66, 0x4f, 0x2d, 0x9e, 0xe0, 0x6d, 0x7f, 0x01, 0x8f, 0xb8, 0xc0, 0x89, 0x5d, + 0x99, 0x19, 0x5c, 0x5b, 0xba, 0x48, 0x4a, 0x98, 0xb5, 0x51, 0xc0, 0x5a, 0xc6, 0x08, 0xfa, 0x6a, + 0x25, 0xc2, 0x2b, 0xd8, 0xd2, 0xf5, 0xd2, 0x40, 0x90, 0x8d, 0x2f, 0xaf, 0x29, 0x6c, 0x78, 0x91, + 0x8d, 0x2b, 0xc8, 0x46, 0xb6, 0xb9, 0x58, 0x27, 0x5b, 0xdb, 0x42, 0x21, 0xed, 0x74, 0x10, 0x0b, + 0xc5, 0x6b, 0x5b, 0x84, 0xca, 0xf0, 0xe4, 0x37, 0xb4, 0x7d, 0x71, 0xa2, 0x45, 0x5e, 0x35, 0x5d, + 0xd9, 0xd7, 0xbe, 0x87, 0xa5, 0xb2, 0x53, 0x2b, 0xc7, 0xab, 0x66, 0x59, 0x8e, 0x53, 0xe8, 0x35, + 0xfa, 0x03, 0x1b, 0xe3, 0xe4, 0xe7, 0xa7, 0x07, 0xa9, 0xd7, 0x8a, 0x69, 0x71, 0xa3, 0xd2, 0x74, + 0x5c, 0xf5, 0x3e, 0x86, 0x6e, 0x01, 0x62, 0xb0, 0xc0, 0x11, 0x93, 0x4c, 0x83, 0x85, 0x16, 0xc9, + 0xd8, 0x46, 0x7d, 0x1a, 0x84, 0x18, 0x8d, 0x46, 0x2f, 0xbd, 0x92, 0xb6, 0x1b, 0xbc, 0xd2, 0x19, + 0x6e, 0x77, 0x9f, 0xfe, 0x62, 0xa3, 0xb1, 0x0d, 0x21, 0xd6, 0x46, 0x63, 0x3b, 0x3d, 0x46, 0x05, + 0x3f, 0xf5, 0x05, 0xac, 0x97, 0xf0, 0xd5, 0x11, 0xe0, 0xcf, 0xe4, 0x16, 0x4f, 0x93, 0x77, 0x5d, + 0x0c, 0xd2, 0xcb, 0xb9, 0x7e, 0x83, 0xe8, 0xd3, 0xb4, 0xac, 0x97, 0xdb, 0x6d, 0x6a, 0xcf, 0x0c, + 0xf0, 0x7e, 0xb3, 0x7c, 0xe8, 0x01, 0x65, 0x17, 0x6a, 0xb7, 0xb1, 0x9d, 0xad, 0xb7, 0x2e, 0x32, + 0x76, 0x39, 0x66, 0x88, 0x26, 0x6f, 0xe5, 0x65, 0x96, 0xd7, 0x2d, 0x5a, 0x13, 0xb7, 0x97, 0xee, + 0x38, 0xac, 0xd0, 0x39, 0xc6, 0x9b, 0x6d, 0xb8, 0x35, 0x20, 0x6f, 0xea, 0xa4, 0x3c, 0xee, 0x64, + 0xc1, 0x95, 0x43, 0x3d, 0xdd, 0xfe, 0x99, 0x04, 0x94, 0x6f, 0x73, 0xd2, 0xd0, 0xa7, 0xcd, 0x8a, + 0x1b, 0xf1, 0x41, 0x6a, 0xc7, 0x70, 0xbb, 0x8f, 0x9a, 0x33, 0x8b, 0xa7, 0xca, 0xf3, 0x42, 0x14, + 0xf0, 0xf3, 0x03, 0xe0, 0xcb, 0x78, 0xbf, 0xbc, 0x30, 0x5e, 0x89, 0xb1, 0x7b, 0xf0, 0xf4, 0x68, + 0x73, 0xe8, 0xf4, 0xc8, 0xa6, 0x6b, 0xa3, 0xb6, 0x62, 0xf5, 0x07, 0x02, 0x16, 0x5b, 0x2f, 0x66, + 0xc1, 0xac, 0xfc, 0x04, 0x14, 0x6c, 0x53, 0x61, 0x19, 0xf0, 0x7d, 0x49, 0xed, 0x07, 0xaf, 0x12, + 0x4c, 0x88, 0x08, 0x9d, 0xf3, 0x85, 0xe5, 0xd5, 0xe7, 0x75, 0x7e, 0x18, 0x43, 0xb3, 0x81, 0xbc, + 0xce, 0xc4, 0x98, 0x50, 0x5a, 0x20, 0xe6, 0x69, 0xb2, 0x65, 0xdb, 0x35, 0x45, 0x5f, 0x1a, 0x84, + 0x86, 0x30, 0x8b, 0x3c, 0x3a, 0x14, 0xb8, 0x54, 0x2e, 0x9a, 0xcd, 0xab, 0xb2, 0x73, 0x7d, 0xea, + 0x86, 0x11, 0xb3, 0x49, 0x6f, 0x52, 0xb6, 0x59, 0xbe, 0xe1, 0xa7, 0x5c, 0x05, 0x2f, 0x2d, 0x39, + 0xc7, 0xe6, 0xda, 0x70, 0xe7, 0x24, 0x5b, 0x36, 0xd1, 0x05, 0x59, 0xe3, 0x4b, 0x04, 0xc5, 0xd9, + 0x30, 0x26, 0x78, 0x8a, 0xc4, 0xc7, 0x41, 0x09, 0x4f, 0x54, 0x95, 0xd2, 0x18, 0x28, 0x09, 0x91, + 0xc1, 0x92, 0x73, 0x14, 0xd9, 0xac, 0x26, 0xe5, 0x1a, 0xca, 0x0e, 0xe4, 0x66, 0x3d, 0x34, 0xfb, + 0xe5, 0xd5, 0x95, 0x8f, 0x30, 0xce, 0xbb, 0x2a, 0xb7, 0xb2, 0x0c, 0x7f, 0x37, 0x4c, 0x3c, 0xfa, + 0x93, 0xf9, 0xf7, 0xd2, 0x2e, 0xbe, 0x11, 0x96, 0x80, 0x25, 0x06, 0xf7, 0x0c, 0x36, 0x6a, 0x20, + 0x61, 0x92, 0xa7, 0x28, 0x60, 0xd4, 0xa1, 0x68, 0xe5, 0x71, 0x4d, 0x28, 0xc1, 0xaf, 0x5c, 0xc8, + 0x38, 0xef, 0x86, 0x04, 0x35, 0x3c, 0x43, 0x1b, 0xe0, 0x63, 0x32, 0x31, 0x09, 0x64, 0xef, 0x0f, + 0x1e, 0x01, 0xe4, 0x05, 0x9e, 0x8a, 0x0e, 0x32, 0x6e, 0xc4, 0x33, 0x80, 0xca, 0x9f, 0x32, 0x4e, + 0x43, 0xe5, 0x8b, 0x2c, 0x23, 0x6e, 0xd2, 0xf2, 0xf9, 0xfa, 0xa3, 0x74, 0x2f, 0x27, 0x3c, 0x37, + 0x4e, 0xcc, 0xd7, 0x8d, 0x3d, 0x56, 0xf5, 0x05, 0x03, 0xfc, 0x66, 0x66, 0x16, 0xb3, 0x99, 0xe6, + 0xec, 0xac, 0x4c, 0x8b, 0x9c, 0x4c, 0x8f, 0x18, 0xe3, 0x1b, 0xd2, 0x59, 0x16, 0xda, 0xfb, 0x66, + 0x9b, 0x03, 0x9b, 0xe2, 0xe4, 0xff, 0x99, 0x16, 0x4c, 0x83, 0xf4, 0xee, 0xc0, 0x26, 0x1d, 0x40, + 0xcc, 0x66, 0x4b, 0x6e, 0x20, 0x37, 0x1a, 0x92, 0x1a, 0xf8, 0x42, 0x92, 0xc9, 0x26, 0xad, 0xb5, + 0x71, 0x04, 0xcb, 0x12, 0xff, 0x90, 0xdc, 0xb3, 0xaa, 0xeb, 0x5d, 0x35, 0x9e, 0xcb, 0x43, 0xbe, + 0x2b, 0xf1, 0x67, 0x73, 0x8b, 0x23, 0x72, 0x6d, 0x18, 0xc4, 0x58, 0xb2, 0xe5, 0xf6, 0xf3, 0xf4, + 0x74, 0xb4, 0x21, 0x2d, 0x82, 0xb1, 0x79, 0x2f, 0xa3, 0x8b, 0xbd, 0xfc, 0xdd, 0x2d, 0x27, 0xf7, + 0x27, 0x70, 0x07, 0xe0, 0x78, 0x8a, 0xa6, 0x85, 0xec, 0x83, 0x78, 0xc5, 0x7b, 0xda, 0x31, 0xe2, + 0xb9, 0xe8, 0x1c, 0x6d, 0x08, 0xe7, 0xaa, 0xf1, 0xa6, 0x94, 0xf8, 0xa4, 0xc6, 0xf7, 0x5d, 0xb1, + 0x56, 0x4e, 0x09, 0xb6, 0xaa, 0x07, 0xa9, 0x90, 0xf0, 0x43, 0x34, 0x04, 0x0c, 0xe4, 0xda, 0x18, + 0x35, 0xde, 0xc7, 0x91, 0xa4, 0x95, 0x3d, 0xc0, 0x9d, 0x59, 0x2e, 0x3f, 0x1f, 0x3a, 0x1e, 0x47, + 0x80, 0xd0, 0xcb, 0x3a, 0xef, 0x5a, 0x82, 0x11, 0x1b, 0x57, 0x5d, 0x8d, 0xc1, 0x55, 0x03, 0x36, + 0x07, 0x04, 0xbe, 0x06, 0xd1, 0xd5, 0xdf, 0x43, 0x0f, 0xb0, 0xcd, 0x72, 0xe7, 0xd4, 0xe0, 0xa5, + 0x31, 0x95, 0x28, 0x5d, 0x03, 0xa0, 0xe7, 0xf2, 0x28, 0xfd, 0xe9, 0xac, 0x80, 0x43, 0x9d, 0x95, + 0x69, 0x46, 0xc5, 0x8b, 0x53, 0x70, 0xbe, 0x87, 0x94, 0x91, 0x14, 0x0d, 0xa1, 0x24, 0xe2, 0xe1, + 0x16, 0x10, 0x1c, 0xf3, 0x38, 0x08, 0x7c, 0xb6, 0xf4, 0x22, 0xeb, 0xec, 0x11, 0x53, 0xf0, 0xb4, + 0x40, 0x74, 0xa8, 0x25, 0x3a, 0x36, 0xb5, 0x4d, 0x86, 0x49, 0xe7, 0x76, 0x3d, 0x1e, 0x70, 0x1d, + 0x75, 0x46, 0xe1, 0x21, 0x58, 0x44, 0x8d, 0x3a, 0xc9, 0xf7, 0xdf, 0x0d, 0x78, 0x87, 0xad, 0x0d, + 0x82, 0xa6, 0xbc, 0x2e, 0xdc, 0xd9, 0x7f, 0x1d, 0xdd, 0xc5, 0xae, 0xf1, 0xae, 0xd3, 0x5c, 0xf5, + 0x9f, 0xd1, 0x8e, 0x4c, 0x1d, 0xdc, 0xae, 0x14, 0x1d, 0x7a, 0x03, 0x36, 0xa1, 0x4e, 0x5a, 0x19, + 0xf5, 0xb9, 0xd0, 0xaf, 0x7a, 0x61, 0x8e, 0x34, 0x2f, 0x5c, 0xb8, 0xca, 0x4a, 0xcb, 0xf5, 0x53, + 0x2b, 0xb7, 0x84, 0xb3, 0xce, 0xc8, 0xca, 0x2d, 0x61, 0x61, 0x7c, 0x5c, 0xa5, 0x61, 0x05, 0x1f, + 0x55, 0xb9, 0x85, 0x35, 0x71, 0xd5, 0xef, 0x23, 0x97, 0xbb, 0x9e, 0x0d, 0x4b, 0xb8, 0xc6, 0x5e, + 0x15, 0xfa, 0xc3, 0x5f, 0xf4, 0xea, 0x8f, 0x82, 0x6a, 0xde, 0xcf, 0xed, 0x5a, 0x39, 0x6a, 0xaa, + 0xd0, 0x3f, 0xcb, 0x39, 0x42, 0xe8, 0x5f, 0xc4, 0x8f, 0xf4, 0xc5, 0x56, 0xbe, 0x30, 0x6a, 0xda, + 0x22, 0xe7, 0x38, 0xe9, 0x59, 0xb6, 0xe6, 0xcd, 0xd1, 0x16, 0x30, 0x60, 0x43, 0x03, 0xb9, 0xca, + 0x7b, 0x89, 0xa7, 0xa1, 0x9a, 0x16, 0x19, 0x85, 0x7c, 0x49, 0xc2, 0x02, 0x48, 0x74, 0xfd, 0xb7, + 0xc1, 0xf9, 0x72, 0x26, 0x72, 0xbb, 0xc2, 0x45, 0x53, 0x47, 0x82, 0x87, 0x45, 0x2d, 0x7f, 0xa0, + 0xaa, 0x8f, 0x05, 0x61, 0x72, 0xdb, 0x4e, 0x23, 0xd1, 0x4d, 0x55, 0x97, 0x99, 0xde, 0x40, 0x60, + 0x7a, 0x60, 0x04, 0xc3, 0xb0, 0xf5, 0x7b, 0x30, 0x09, 0xf3, 0x8d, 0x84, 0x84, 0x07, 0x62, 0x54, + 0xe8, 0x82, 0xf7, 0xe0, 0x5c, 0x98, 0x64, 0xf8, 0x56, 0x20, 0x89, 0x42, 0xc3, 0x0d, 0xae, 0x3a, + 0x0f, 0x63, 0x28, 0x4f, 0x84, 0x25, 0x53, 0x72, 0x59, 0x68, 0x02, 0x90, 0xf0, 0x13, 0xaa, 0xd7, + 0x0d, 0xd5, 0x0f, 0x51, 0x32, 0x2a, 0x97, 0x60, 0xf6, 0x1b, 0xa4, 0x86, 0xa3, 0xe1, 0xf9, 0x2d, + 0x9a, 0xb6, 0x43, 0x44, 0xd1, 0xb9, 0x1e, 0x11, 0x54, 0xc8, 0x8c, 0x73, 0x25, 0x78, 0x40, 0x71, + 0x55, 0x98, 0x58, 0x1e, 0x55, 0xf9, 0x62, 0x80, 0x71, 0x16, 0x74, 0x83, 0x57, 0x73, 0x8e, 0x09, + 0xf0, 0x6d, 0xe0, 0x77, 0xe4, 0xfa, 0x29, 0xc1, 0x43, 0xc5, 0x70, 0x1d, 0x9d, 0x65, 0xcf, 0x95, + 0x51, 0x10, 0x78, 0x5b, 0x32, 0xb3, 0xd1, 0x35, 0xe3, 0xec, 0x3e, 0x72, 0x8e, 0xce, 0x2e, 0x3a, + 0xb7, 0x76, 0x65, 0xb0, 0xce, 0x0c, 0xbc, 0x4e, 0xa4, 0x41, 0x97, 0xe5, 0x52, 0x7a, 0xe3, 0x85, + 0x92, 0xaa, 0x23, 0xa8, 0xe3, 0x54, 0xe9, 0x71, 0xe2, 0x57, 0xa9, 0x48, 0x39, 0xd8, 0x09, 0xac, + 0x64, 0x1f, 0x4d, 0x09, 0xba, 0x18, 0x48, 0xab, 0x1a, 0x93, 0xa2, 0xd3, 0xfe, 0xc6, 0x30, 0x8d, + 0x49, 0x31, 0x9b, 0xfe, 0x86, 0x5e, 0x57, 0x4d, 0xd2, 0xe3, 0x1a, 0xd3, 0xe9, 0x4d, 0x45, 0x54, + 0xdb, 0x74, 0x95, 0x6f, 0x97, 0x2d, 0x42, 0xd9, 0xd7, 0xd8, 0xe8, 0xa3, 0x89, 0xa6, 0x68, 0x8a, + 0xc6, 0xac, 0xd6, 0xac, 0x15, 0xc3, 0xc4, 0x2c, 0x95, 0xe7, 0x8b, 0x51, 0xe1, 0x39, 0x5a, 0xe1, + 0x14, 0x13, 0xe0, 0xe3, 0xd0, 0xde, 0xb3, 0xc0, 0xe0, 0x03, 0x89, 0xf2, 0x3b, 0x67, 0x29, 0x8d, + 0x21, 0xa7, 0x00, 0xb4, 0x95, 0x6b, 0xe5, 0x4e, 0xe5, 0x36, 0x0d, 0x05, 0x0a, 0x5d, 0xf3, 0x20, + 0xc3, 0x95, 0x4c, 0xc7, 0x65, 0x2d, 0x75, 0xc5, 0x7a, 0xb7, 0xeb, 0x28, 0x1f, 0x0e, 0xb9, 0x94, + 0x70, 0x64, 0xde, 0xa0, 0x91, 0xa6, 0xc2, 0xb4, 0x90, 0x13, 0x64, 0x2c, 0x04, 0x14, 0xd2, 0xce, + 0x1f, 0x70, 0x09, 0xc3, 0x60, 0xc8, 0x6c, 0xce, 0xcc, 0x92, 0x17, 0x2a, 0x23, 0x48, 0xae, 0xaf, + 0x00, 0x5e, 0x1c, 0x4e, 0x6f, 0x82, 0x99, 0xe5, 0xb6, 0xae, 0xd0, 0xaa, 0xb7, 0x58, 0x72, 0x85, + 0x0d, 0x0c, 0xa9, 0xa9, 0xbb, 0x55, 0x33, 0x4d, 0x72, 0x7d, 0x8c, 0x7d, 0x22, 0xe8, 0x55, 0x30, + 0xb3, 0xfc, 0xe1, 0xad, 0x1a, 0x35, 0x84, 0x96, 0x9d, 0x66, 0x65, 0x31, 0xf4, 0xfc, 0x80, 0x8b, + 0x21, 0x84, 0x67, 0x11, 0xdc, 0x6e, 0x69, 0x3f, 0x14, 0x60, 0x1d, 0x93, 0x52, 0x47, 0x60, 0x5a, + 0xf5, 0x6b, 0x80, 0x1c, 0xc2, 0xea, 0x8a, 0x8e, 0x9e, 0x00, 0xa9, 0x83, 0xc6, 0xf5, 0x50, 0x02, + 0x27, 0x1b, 0x29, 0xba, 0x5a, 0x68, 0xcb, 0xa3, 0xdc, 0xee, 0x83, 0xb8, 0x7b, 0xe5, 0x0a, 0x88, + 0xdb, 0xcf, 0xd0, 0x96, 0x10, 0x0b, 0xb4, 0x73, 0xa9, 0x37, 0x6a, 0xb7, 0xbb, 0xa1, 0x58, 0x93, + 0x77, 0x9d, 0xdb, 0x75, 0x91, 0xdb, 0x71, 0x03, 0xe2, 0x6c, 0xb6, 0x99, 0xed, 0x4f, 0x68, 0xe1, + 0x76, 0x1d, 0x3c, 0x08, 0x15, 0xfa, 0xd3, 0x3d, 0x3d, 0x4b, 0xbe, 0xc7, 0x0d, 0xfb, 0x09, 0xc4, + 0xdb, 0x98, 0xa6, 0xd2, 0xe0, 0xc5, 0x4b, 0x63, 0x17, 0xbd, 0x8f, 0x6c, 0xba, 0xee, 0x9b, 0xb5, + 0xe4, 0xfb, 0x00, 0x7d, 0x22, 0xde, 0x4d, 0x5a, 0xa8, 0xd6, 0x0e, 0x56, 0xfb, 0xc6, 0x41, 0x19, + 0xa7, 0xed, 0x1f, 0x2c, 0x1e, 0x82, 0xe7, 0xb4, 0x93, 0x1c, 0x59, 0xcc, 0xe9, 0xb0, 0x12, 0x8b, + 0x86, 0xf4, 0xd7, 0x58, 0xc6, 0x88, 0x79, 0x6d, 0x56, 0xb2, 0x46, 0x5b, 0xb3, 0x62, 0x9c, 0xf8, + 0xd6, 0x71, 0xa4, 0xc8, 0x12, 0x2d, 0x9a, 0x64, 0xb2, 0x22, 0x46, 0x3c, 0xd8, 0x86, 0xc5, 0xad, + 0x10, 0xc4, 0xc4, 0x41, 0x02, 0x00, 0x1d, 0x66, 0x70, 0x3b, 0x3c, 0xac, 0x17, 0x72, 0xef, 0xb4, + 0x99, 0x30, 0x1f, 0x40, 0x12, 0xdf, 0x51, 0x59, 0xd1, 0xc5, 0x90, 0xb4, 0x44, 0xa7, 0x56, 0xca, + 0xd9, 0x69, 0x25, 0xcf, 0x4d, 0xf4, 0x7c, 0xa7, 0x85, 0xaf, 0x68, 0x9f, 0x0a, 0x0c, 0xeb, 0x34, + 0x59, 0xa3, 0xd3, 0x5f, 0x94, 0x20, 0xf9, 0xbd, 0x0a, 0x6b, 0xd4, 0x67, 0xa1, 0x15, 0x87, 0xae, + 0xb9, 0x38, 0xa3, 0x2d, 0x18, 0x03, 0x7a, 0xab, 0x8e, 0xe0, 0x5c, 0x0e, 0xf3, 0xc0, 0xd5, 0x18, + 0x5d, 0xa2, 0x0e, 0xbc, 0x2d, 0x6b, 0xf0, 0x26, 0xd0, 0x45, 0xf4, 0xb0, 0xaf, 0x20, 0x30, 0x0f, + 0xa2, 0xaf, 0xc7, 0x21, 0x03, 0x56, 0xfc, 0x54, 0xb7, 0x68, 0xfc, 0xad, 0xfe, 0x28, 0x68, 0xbf, + 0xd0, 0xab, 0xe3, 0x41, 0xf7, 0x0e, 0x48, 0xd2, 0x66, 0xba, 0xb7, 0x90, 0x3c, 0xd1, 0x23, 0x03, + 0x1d, 0xc9, 0xd1, 0x78, 0x82, 0x92, 0x73, 0x5c, 0x52, 0x2d, 0xd2, 0x77, 0x73, 0xbb, 0x34, 0x89, + 0xe8, 0xb8, 0x38, 0xf0, 0x60, 0xcf, 0xa4, 0x55, 0x0d, 0x44, 0x00, 0x3e, 0xfe, 0x1b, 0x5f, 0x85, + 0xc5, 0x2c, 0xb9, 0x1c, 0xf4, 0xca, 0x9c, 0xe3, 0xde, 0x90, 0xd5, 0x62, 0x99, 0x81, 0xe1, 0xc5, + 0x1c, 0x7f, 0xb0, 0x52, 0x32, 0x1e, 0x37, 0xcb, 0x13, 0x43, 0x2c, 0x70, 0xae, 0x51, 0x78, 0x59, + 0xef, 0x85, 0x05, 0x6c, 0xb1, 0x4e, 0xe2, 0x8f, 0x43, 0xe2, 0xbf, 0x99, 0xda, 0xbd, 0x5e, 0xc1, + 0x20, 0x05, 0xd3, 0x6c, 0x3f, 0xb9, 0xe6, 0x13, 0x6e, 0xe1, 0x27, 0xe7, 0xe4, 0xb1, 0x50, 0x8f, + 0x57, 0x0b, 0x86, 0x8e, 0x81, 0x87, 0x40, 0xa1, 0x51, 0x2c, 0xf2, 0x6e, 0x46, 0xb1, 0x52, 0xca, + 0x32, 0x9e, 0x5d, 0x82, 0x55, 0x46, 0x07, 0x83, 0xae, 0xfa, 0xe2, 0x0c, 0x59, 0xad, 0x3c, 0x0b, + 0x15, 0x5d, 0x30, 0xf0, 0xdb, 0x7b, 0x47, 0xa3, 0x89, 0x34, 0x5e, 0xff, 0x03, 0xb7, 0x3b, 0xe7, + 0x2b, 0x62, 0x9e, 0xc7, 0x8f, 0x13, 0x4d, 0xed, 0x9e, 0xcb, 0xda, 0x91, 0xcd, 0x78, 0x2e, 0x6b, + 0x9e, 0x47, 0x1a, 0x7d, 0x11, 0x60, 0xcc, 0x39, 0x7e, 0x98, 0x51, 0x48, 0x7b, 0xb9, 0xdd, 0xe5, + 0x1a, 0x29, 0x55, 0x8f, 0xca, 0xd4, 0x4d, 0xd2, 0x17, 0x93, 0xc7, 0x17, 0x92, 0x5e, 0xc8, 0xbf, + 0x14, 0x46, 0xcd, 0xf2, 0x2f, 0x95, 0x03, 0x6a, 0x92, 0x9e, 0x44, 0x9e, 0xd7, 0x36, 0xa6, 0x44, + 0xaf, 0xf8, 0x04, 0x2f, 0xf7, 0xc6, 0x38, 0xe0, 0x4b, 0x4c, 0x5f, 0x48, 0x52, 0xe2, 0x82, 0x17, + 0x95, 0xd1, 0xfc, 0x61, 0x81, 0x80, 0x99, 0xab, 0x0e, 0x45, 0x3e, 0xfa, 0x8b, 0xbe, 0xef, 0x42, + 0xb3, 0xb7, 0x10, 0x33, 0xdc, 0xb3, 0xe4, 0x24, 0xb7, 0x2b, 0x19, 0x22, 0xa8, 0x27, 0x34, 0xdc, + 0x0e, 0xaf, 0xe4, 0x3a, 0x00, 0xec, 0xd9, 0x22, 0xe8, 0x95, 0x2d, 0xb3, 0x3c, 0x65, 0x9a, 0x92, + 0x9a, 0x74, 0x3b, 0x4b, 0xb8, 0xdd, 0x96, 0x89, 0xc2, 0x8b, 0x2a, 0x1d, 0xc9, 0xf3, 0xf3, 0x23, + 0x21, 0xa9, 0x89, 0xf6, 0xb4, 0x6b, 0x47, 0xd6, 0x61, 0xc4, 0xd8, 0x67, 0x96, 0x23, 0xc2, 0x83, + 0x61, 0x12, 0x57, 0xfd, 0x1c, 0x26, 0x11, 0x7c, 0x9b, 0x98, 0xe7, 0xe7, 0x76, 0x7c, 0x0e, 0x0e, + 0xaf, 0x43, 0x1e, 0x50, 0xee, 0xfa, 0x90, 0x3e, 0xd2, 0x08, 0xea, 0xac, 0x77, 0x9b, 0xb3, 0xe1, + 0x43, 0x77, 0x52, 0x9a, 0x68, 0xb7, 0x68, 0xcf, 0x57, 0x61, 0x96, 0x00, 0xdf, 0x21, 0xbf, 0x32, + 0x51, 0x89, 0x54, 0x92, 0xb8, 0xdd, 0xe9, 0x33, 0xc0, 0x6a, 0xa5, 0xd4, 0xf0, 0x00, 0x5e, 0xdb, + 0xee, 0xb9, 0x0e, 0x39, 0x34, 0x10, 0x97, 0x2d, 0x7f, 0xac, 0xa0, 0xe2, 0x76, 0xe7, 0x02, 0xb1, + 0xb9, 0x33, 0xb9, 0xdd, 0x8f, 0xcf, 0xf0, 0x5c, 0x06, 0x77, 0x6e, 0xc9, 0x32, 0x07, 0x16, 0x90, + 0x46, 0xf9, 0xab, 0x53, 0xd4, 0xa3, 0x08, 0x37, 0x20, 0x4e, 0xfc, 0x82, 0xe6, 0xef, 0x7e, 0x0f, + 0x08, 0x36, 0xaf, 0x45, 0xca, 0x65, 0xa5, 0xfc, 0x31, 0xa0, 0x82, 0x15, 0xed, 0xe4, 0x84, 0xe7, + 0xab, 0x88, 0x5a, 0xd5, 0x02, 0xbc, 0xd9, 0xb7, 0x1f, 0xdb, 0x98, 0x8e, 0x4b, 0x9a, 0x05, 0xc4, + 0xd4, 0x25, 0x54, 0x68, 0x98, 0xda, 0xe5, 0x0b, 0x46, 0x91, 0x9c, 0x0e, 0xae, 0xfa, 0x25, 0xa8, + 0xa9, 0xec, 0xe3, 0x38, 0x17, 0x9a, 0xe6, 0x87, 0x49, 0xce, 0x49, 0x8a, 0x4c, 0x74, 0x8a, 0x80, + 0x80, 0xe3, 0xa9, 0xa1, 0xe8, 0xbe, 0x96, 0x89, 0x9f, 0xc2, 0xed, 0x6a, 0x92, 0xa2, 0x97, 0x49, + 0xa9, 0xbf, 0xb8, 0xbe, 0x24, 0x1c, 0x7d, 0xcf, 0xcb, 0xcb, 0xe9, 0xe0, 0x32, 0xa9, 0x97, 0x52, + 0x01, 0xa1, 0xd1, 0x2b, 0x45, 0xff, 0x82, 0xe4, 0x9d, 0x91, 0x5e, 0xb9, 0x07, 0x7b, 0x4b, 0x6f, + 0x4e, 0xc0, 0xaf, 0xc3, 0x37, 0x70, 0xf7, 0x27, 0xaf, 0xa3, 0x36, 0xf5, 0xf9, 0xa8, 0x9a, 0xd4, + 0x87, 0x7b, 0xd3, 0xf9, 0xff, 0xd1, 0x9f, 0x16, 0xf3, 0xe4, 0x5a, 0x55, 0x76, 0x4d, 0xea, 0xd8, + 0x08, 0xc8, 0xd0, 0x23, 0xf9, 0xeb, 0xa2, 0xa9, 0xab, 0x36, 0x19, 0xac, 0xfa, 0x4c, 0x4d, 0xf5, + 0x84, 0x46, 0xe8, 0x54, 0x9b, 0x3c, 0x19, 0x9e, 0x9a, 0xe0, 0xc9, 0x75, 0x1a, 0xd2, 0x5f, 0xce, + 0xf5, 0x05, 0xde, 0x17, 0xc5, 0x41, 0x2b, 0x0f, 0x44, 0x20, 0x56, 0x1d, 0xaf, 0x16, 0xdc, 0x01, + 0xfb, 0x55, 0xbd, 0x3b, 0x43, 0x7a, 0x0b, 0x01, 0x0d, 0xa9, 0x5a, 0x36, 0xb4, 0xb0, 0xed, 0xcd, + 0x79, 0x07, 0x5a, 0x4f, 0x79, 0x07, 0x8f, 0x92, 0x9a, 0xc0, 0xcd, 0x67, 0x05, 0x4e, 0x29, 0x66, + 0x52, 0xfd, 0x13, 0x7a, 0x25, 0xc0, 0x5b, 0xab, 0xee, 0xf6, 0xa8, 0x39, 0x97, 0x84, 0x48, 0x21, + 0x31, 0x84, 0xce, 0x11, 0xc7, 0x75, 0x49, 0x7c, 0x2f, 0x04, 0x7b, 0xb5, 0xb8, 0xd1, 0xbf, 0x4a, + 0x1f, 0xf8, 0x3b, 0x0b, 0xf9, 0x82, 0xbe, 0x3b, 0x6d, 0xf1, 0x2c, 0xe7, 0x2c, 0x21, 0xa0, 0xe6, + 0xde, 0xf0, 0x08, 0x81, 0xa8, 0x4d, 0x2b, 0xa7, 0x99, 0x64, 0xfe, 0x71, 0xee, 0x90, 0xdb, 0x2c, + 0x7f, 0xf7, 0x25, 0x0d, 0x86, 0x6b, 0x52, 0x97, 0x78, 0xd2, 0xb1, 0x9f, 0x2a, 0x31, 0xad, 0xda, + 0xcd, 0xb7, 0xf8, 0xe2, 0xba, 0x8d, 0x32, 0xeb, 0x7c, 0x40, 0x01, 0x1d, 0x56, 0x53, 0xc7, 0x27, + 0x33, 0xca, 0x46, 0xfd, 0xdf, 0x8b, 0x47, 0x49, 0x46, 0x8f, 0x19, 0x17, 0xcf, 0xb6, 0xaf, 0x42, + 0xbe, 0xfc, 0x05, 0x60, 0xd0, 0x17, 0x51, 0xd9, 0xa7, 0x76, 0x2e, 0x14, 0x4d, 0xc7, 0x93, 0x41, + 0x79, 0x84, 0x3a, 0x9d, 0x50, 0xaf, 0x5b, 0x64, 0x6a, 0xe1, 0x5c, 0x0f, 0x0d, 0xa0, 0xf0, 0xfc, + 0xfa, 0xee, 0x15, 0xd9, 0xb9, 0x72, 0x56, 0x8c, 0xa2, 0x39, 0xe1, 0x50, 0x24, 0xfe, 0xaa, 0x23, + 0x0b, 0x43, 0xea, 0x0e, 0x91, 0xde, 0x8b, 0x31, 0xa1, 0x8d, 0x88, 0x51, 0x80, 0xde, 0x2c, 0xcf, + 0x9f, 0xa2, 0x9c, 0x70, 0x8f, 0x42, 0x03, 0xc9, 0xf3, 0x57, 0xbe, 0x30, 0x02, 0x65, 0xd6, 0x00, + 0xeb, 0x02, 0x93, 0xc1, 0x5f, 0x45, 0x93, 0x59, 0xa8, 0xef, 0xf6, 0x4d, 0x28, 0xd6, 0xd1, 0x0c, + 0x42, 0x2d, 0x47, 0x2a, 0xed, 0xf5, 0xdd, 0xd0, 0x18, 0x09, 0x7a, 0x4d, 0x4a, 0x7d, 0x44, 0x57, + 0xac, 0xa3, 0xeb, 0xf8, 0x37, 0x93, 0x07, 0xeb, 0x3a, 0x9d, 0x43, 0x8e, 0xc4, 0x35, 0xa0, 0x4f, + 0xe2, 0x16, 0x95, 0xde, 0x2d, 0x04, 0x34, 0x7b, 0xbf, 0xc3, 0x3b, 0x0d, 0xc6, 0x26, 0x21, 0xa0, + 0x3d, 0xf4, 0x5d, 0x00, 0x1f, 0xfd, 0x23, 0x4f, 0x08, 0x01, 0xdd, 0x7e, 0x08, 0x4a, 0x39, 0xce, + 0x08, 0xa1, 0x56, 0x98, 0xbd, 0xd8, 0xd0, 0x6a, 0xb7, 0x4a, 0xe0, 0x87, 0xb4, 0xa0, 0xf0, 0x01, + 0x74, 0xfc, 0xa6, 0x2e, 0x3c, 0xc3, 0x91, 0x77, 0x52, 0x4f, 0xa6, 0x02, 0xb3, 0x4f, 0x17, 0x1a, + 0x54, 0xd8, 0x54, 0x65, 0xb8, 0xb0, 0xf1, 0xb2, 0x45, 0xfe, 0xf0, 0xee, 0x5b, 0xa1, 0xa9, 0x0c, + 0x0e, 0xcc, 0x15, 0x70, 0x4e, 0xc1, 0x9b, 0x57, 0x7e, 0xa2, 0xfa, 0x58, 0x44, 0x37, 0xa2, 0x74, + 0x7f, 0x56, 0xe9, 0x9e, 0x66, 0xe8, 0xe1, 0xaf, 0x50, 0xd6, 0xf0, 0x22, 0xb4, 0x26, 0xe4, 0xc3, + 0x31, 0x08, 0xc8, 0xd0, 0x8a, 0x99, 0x49, 0xca, 0xca, 0x1f, 0xe0, 0xa3, 0x21, 0x1d, 0xc5, 0x70, + 0x7f, 0x36, 0x75, 0x6e, 0xd1, 0xc1, 0x33, 0x35, 0x68, 0x55, 0x0e, 0x89, 0x63, 0xaa, 0xa7, 0x3d, + 0xcc, 0x1c, 0xd8, 0x98, 0x24, 0x77, 0xdc, 0xa5, 0xa4, 0x0e, 0x19, 0x16, 0xf9, 0xfc, 0x5d, 0x43, + 0x52, 0x87, 0xe0, 0x59, 0x79, 0x8e, 0x17, 0x4f, 0xa0, 0x4d, 0x31, 0x22, 0x1f, 0x4d, 0xfc, 0x23, + 0x9b, 0x94, 0xfb, 0x3c, 0xa2, 0xc9, 0xcf, 0xed, 0x4e, 0xd6, 0x70, 0xbb, 0x96, 0x8d, 0xe1, 0x76, + 0x34, 0x81, 0x74, 0xf4, 0x47, 0x61, 0xd9, 0xc9, 0x69, 0x23, 0xa6, 0xf6, 0x70, 0xbc, 0x67, 0xd4, + 0x31, 0x3e, 0x47, 0x0b, 0x6c, 0x83, 0x5b, 0x00, 0xb1, 0x67, 0x65, 0xca, 0xbf, 0x98, 0xa6, 0x9c, + 0xa0, 0x1b, 0xcf, 0x90, 0xd6, 0x07, 0xf8, 0x76, 0xb1, 0x22, 0x66, 0x52, 0x45, 0x1b, 0x70, 0xc5, + 0xed, 0x38, 0xdf, 0x73, 0x99, 0x78, 0xd8, 0x0b, 0xa2, 0xb1, 0x23, 0xa1, 0x09, 0xdc, 0x0b, 0x38, + 0xcc, 0xd8, 0x53, 0xa0, 0x91, 0x10, 0xd8, 0x26, 0xd4, 0xef, 0xa3, 0x67, 0x59, 0xcd, 0x86, 0xcf, + 0x89, 0x17, 0xdf, 0x50, 0x38, 0xc7, 0x2d, 0xfd, 0x9c, 0x4b, 0x85, 0xc7, 0x73, 0xe0, 0xbb, 0x8d, + 0x5e, 0xd1, 0x74, 0x46, 0xd4, 0xd4, 0x08, 0x9e, 0x01, 0x10, 0x10, 0xb7, 0x6b, 0x29, 0x10, 0xe2, + 0xd1, 0x1f, 0x0d, 0x5f, 0xaa, 0xe3, 0x5e, 0xf7, 0x70, 0xbb, 0xb3, 0xc0, 0xa3, 0x34, 0xf7, 0x7c, + 0xc1, 0x9e, 0x0a, 0x37, 0x76, 0x8c, 0x07, 0x74, 0x39, 0x6a, 0x98, 0x6c, 0xb6, 0xdf, 0x92, 0x9d, + 0x69, 0xce, 0x02, 0xb1, 0x42, 0x40, 0x44, 0x3e, 0x05, 0x8d, 0xcb, 0xc1, 0x2b, 0xc3, 0xe0, 0x62, + 0xd8, 0xf3, 0x63, 0xaf, 0x01, 0x83, 0x86, 0xab, 0xf8, 0x4a, 0x43, 0x0c, 0xb7, 0x4d, 0x8f, 0x81, + 0xe1, 0xb0, 0xb3, 0x78, 0xbc, 0x63, 0x2e, 0x26, 0xab, 0x60, 0xc5, 0x73, 0xde, 0x27, 0xf4, 0x6a, + 0xf9, 0x54, 0xbd, 0x9b, 0x8c, 0xe0, 0x76, 0x3f, 0x0b, 0xe3, 0x3c, 0x1b, 0x27, 0x45, 0x5b, 0xcc, + 0xc5, 0x61, 0xe6, 0xac, 0xdc, 0x80, 0x5e, 0xd6, 0x7f, 0x8a, 0x5d, 0xe3, 0x7c, 0xcf, 0x52, 0x01, + 0xed, 0x48, 0x8e, 0x93, 0x54, 0xaf, 0x41, 0x2e, 0x50, 0xac, 0xca, 0x32, 0x63, 0xf5, 0xb8, 0x4f, + 0x95, 0xbb, 0x9e, 0xeb, 0xc3, 0x39, 0xdf, 0x63, 0x88, 0x43, 0xe2, 0xaf, 0x59, 0x1b, 0x23, 0x31, + 0xc2, 0x51, 0x70, 0x91, 0x73, 0xa4, 0xf5, 0x94, 0x8f, 0x6d, 0x65, 0xaf, 0x23, 0xca, 0x6c, 0xa8, + 0xb6, 0x40, 0xb7, 0x8b, 0xc7, 0x83, 0x97, 0xe0, 0xe9, 0xa6, 0x8c, 0x6a, 0x3a, 0xbd, 0xc8, 0xd2, + 0xdf, 0x5d, 0xc7, 0x72, 0x46, 0x0f, 0xbd, 0xa7, 0x4d, 0x2f, 0xb5, 0x0f, 0x39, 0x3f, 0x07, 0xa5, + 0x85, 0x64, 0xca, 0xb6, 0x0e, 0xfc, 0xb7, 0xfc, 0xd8, 0x84, 0xe0, 0x46, 0x09, 0x89, 0x11, 0xdc, + 0x2a, 0x4c, 0x79, 0x30, 0xa4, 0x82, 0xc4, 0x47, 0x85, 0x37, 0xf9, 0xe5, 0x17, 0x43, 0xb8, 0xd3, + 0x16, 0xd3, 0x0b, 0xf6, 0x7c, 0xba, 0xb0, 0xd5, 0xcc, 0x38, 0x1f, 0x14, 0xd3, 0xd6, 0xfd, 0x79, + 0x2c, 0xe4, 0x48, 0x4e, 0xad, 0xd9, 0x66, 0x86, 0x88, 0x53, 0xfe, 0x07, 0xcd, 0x62, 0xd4, 0xbe, + 0x9f, 0x70, 0xfb, 0xda, 0xb5, 0x8e, 0xd1, 0xb6, 0xe7, 0xf1, 0xf0, 0x79, 0x3b, 0x05, 0xaa, 0xac, + 0xc2, 0xd6, 0x47, 0xc2, 0xf9, 0xe9, 0xb6, 0x5f, 0xe3, 0x61, 0x39, 0x51, 0x60, 0x74, 0x74, 0x8b, + 0x2c, 0x44, 0x2b, 0x19, 0x61, 0x6f, 0x48, 0x57, 0x6b, 0x57, 0xc4, 0xed, 0xc1, 0xb1, 0x71, 0x45, + 0xe4, 0xfe, 0x50, 0xd7, 0xda, 0xde, 0xed, 0xd1, 0x71, 0x10, 0x96, 0xfc, 0x72, 0x75, 0x18, 0x8d, + 0x35, 0x38, 0x63, 0x23, 0x79, 0x69, 0x15, 0x5e, 0xcd, 0x7e, 0x95, 0xde, 0x19, 0xbb, 0x56, 0xcc, + 0x14, 0x87, 0xc9, 0xb3, 0x95, 0x28, 0xc0, 0xc6, 0x40, 0x90, 0x25, 0x6f, 0x8e, 0x0e, 0x25, 0x7c, + 0xd9, 0xf2, 0x5e, 0xba, 0x89, 0xa9, 0xca, 0x70, 0xb9, 0xb7, 0x8e, 0x85, 0x68, 0x23, 0x3c, 0x03, + 0xc3, 0x0d, 0x15, 0x44, 0xaf, 0xdf, 0x26, 0xc1, 0x1a, 0x65, 0xf4, 0xe7, 0x42, 0x14, 0xc4, 0x4d, + 0x52, 0x22, 0x04, 0x88, 0xb0, 0xae, 0x65, 0xcb, 0xbf, 0x0b, 0x0b, 0xde, 0xd3, 0xe1, 0xaa, 0xff, + 0x89, 0x2b, 0xf9, 0xf5, 0xaa, 0x7a, 0x2f, 0x0e, 0x74, 0x1e, 0x87, 0x0a, 0xf0, 0xb2, 0x5c, 0xaf, + 0x84, 0x03, 0xdc, 0xee, 0xf0, 0xe9, 0x8c, 0x9f, 0x61, 0x96, 0x30, 0xef, 0x82, 0x58, 0xee, 0xc2, + 0xfa, 0x5c, 0x8c, 0xe9, 0xbf, 0x50, 0x05, 0xef, 0xd6, 0xf2, 0x9f, 0xf9, 0x36, 0xd2, 0x0d, 0x66, + 0xfe, 0x69, 0x4a, 0x17, 0x56, 0xbe, 0x39, 0x9e, 0x56, 0x8a, 0x2f, 0xb5, 0x21, 0xca, 0x97, 0x56, + 0xe2, 0xdf, 0xab, 0xe4, 0x82, 0x5c, 0x12, 0x81, 0xc5, 0xa7, 0xc2, 0x68, 0x6c, 0xb1, 0x67, 0xfc, + 0xb0, 0x88, 0x3f, 0xc7, 0x4d, 0x23, 0xfe, 0x97, 0xe1, 0x4b, 0x54, 0x7a, 0xa4, 0xc7, 0xe1, 0xf5, + 0xb5, 0x74, 0x9d, 0x12, 0xf3, 0x77, 0xe2, 0x4d, 0x76, 0x1b, 0xde, 0x78, 0x92, 0x1f, 0xf4, 0x2a, + 0xb2, 0x0d, 0x0d, 0x37, 0x5d, 0x19, 0x2e, 0x28, 0xeb, 0xbb, 0xc7, 0x07, 0xb3, 0x8d, 0x4e, 0x69, + 0x60, 0xd0, 0x7f, 0x3d, 0xa5, 0xdc, 0xf0, 0xc3, 0x97, 0x87, 0xb8, 0xea, 0x77, 0x70, 0x6d, 0x58, + 0x6a, 0x86, 0xf0, 0xcd, 0xe5, 0x26, 0xa6, 0x33, 0x10, 0xa5, 0x38, 0x23, 0xbb, 0x97, 0xae, 0xab, + 0xe4, 0x47, 0x36, 0x1a, 0x3b, 0x68, 0xe4, 0x1a, 0xcf, 0xed, 0xde, 0x16, 0x86, 0x44, 0x78, 0x96, + 0x59, 0x5d, 0x4d, 0xdc, 0x9b, 0x1e, 0x2b, 0xac, 0x29, 0x6f, 0xd5, 0x59, 0x85, 0xde, 0xa9, 0xdc, + 0x1b, 0x6e, 0x5d, 0x93, 0xae, 0x05, 0x2c, 0x35, 0xf8, 0x3e, 0x12, 0x2f, 0xa3, 0x23, 0x75, 0xcc, + 0x04, 0x53, 0x87, 0x55, 0x5f, 0xce, 0x95, 0xff, 0x38, 0x39, 0x78, 0x0b, 0xa8, 0x0d, 0xdf, 0x88, + 0x3a, 0x01, 0x29, 0x51, 0xcf, 0x57, 0x6c, 0x73, 0x42, 0x23, 0xa9, 0xf0, 0xc6, 0xe6, 0xc8, 0xb5, + 0x8f, 0xc5, 0x1d, 0xa6, 0x53, 0x7f, 0x91, 0xfb, 0xc3, 0x8d, 0xd6, 0xaf, 0xf7, 0x60, 0x32, 0x3d, + 0x54, 0x09, 0x2e, 0xd1, 0x9d, 0x23, 0xce, 0xd8, 0x07, 0xfa, 0x00, 0xaa, 0xad, 0xef, 0xde, 0x8b, + 0xe9, 0xa0, 0xa1, 0x2f, 0xf6, 0x73, 0x6e, 0x69, 0x2b, 0xb7, 0xb4, 0x11, 0x02, 0x30, 0xc6, 0xa9, + 0xa1, 0xee, 0x67, 0xd9, 0x6a, 0xf0, 0x3b, 0x78, 0x21, 0xbb, 0x0b, 0x4f, 0xc4, 0x2a, 0x2b, 0x3a, + 0x18, 0xe7, 0x13, 0xa2, 0xa9, 0x4d, 0xe4, 0xdb, 0xb9, 0x0f, 0xae, 0x46, 0x7a, 0xbe, 0x8a, 0x64, + 0xfd, 0x22, 0x2f, 0x8f, 0x6d, 0x96, 0x4c, 0x6f, 0x82, 0xfe, 0x24, 0x65, 0x99, 0x33, 0x15, 0xc3, + 0x7f, 0xed, 0x68, 0xf0, 0x4d, 0x88, 0x37, 0xc5, 0x24, 0x35, 0x7d, 0xdb, 0x80, 0x3a, 0xa0, 0x3c, + 0x3f, 0x40, 0xb8, 0x1d, 0x9b, 0xe3, 0x59, 0xbc, 0x98, 0xca, 0xed, 0xb8, 0x06, 0x2e, 0x06, 0x1c, + 0xc8, 0x59, 0xb6, 0x69, 0xec, 0x80, 0x2f, 0xeb, 0x16, 0xe2, 0x08, 0x08, 0x37, 0x86, 0x21, 0xd6, + 0xdd, 0x42, 0xbc, 0x60, 0x10, 0x71, 0x06, 0x20, 0x3e, 0x4f, 0x7a, 0xc1, 0xb7, 0x35, 0x92, 0x53, + 0xec, 0x79, 0x14, 0xd1, 0x30, 0x9c, 0xbd, 0x63, 0x4f, 0x80, 0xfb, 0x2a, 0x66, 0x6c, 0x48, 0xba, + 0xc5, 0x9c, 0x19, 0x98, 0x2b, 0xf7, 0x35, 0x29, 0xc1, 0xce, 0x91, 0xa7, 0x86, 0xef, 0x8a, 0x28, + 0x81, 0x5f, 0xe5, 0xc2, 0xd9, 0x9c, 0x2b, 0x1f, 0x93, 0xaf, 0x73, 0xe4, 0x44, 0x12, 0x31, 0xca, + 0xd2, 0x36, 0x34, 0x0d, 0x49, 0xc2, 0x7b, 0xef, 0x36, 0xe5, 0x7d, 0x0f, 0xf9, 0xfe, 0x98, 0xe0, + 0xd9, 0x7e, 0x12, 0x3a, 0xda, 0xfa, 0xe0, 0x16, 0x1f, 0x08, 0xde, 0xc6, 0xd0, 0x08, 0xd0, 0xd6, + 0x01, 0x51, 0x83, 0x1c, 0xab, 0xb4, 0x92, 0xde, 0xc0, 0x4d, 0x0d, 0xfd, 0x51, 0x58, 0x70, 0xb8, + 0x5d, 0x2a, 0xb5, 0xd0, 0x3b, 0xdb, 0x39, 0x0d, 0x56, 0xe9, 0xc9, 0x35, 0x55, 0xd8, 0x2a, 0xb9, + 0xaa, 0x0f, 0xab, 0xf9, 0xcb, 0x34, 0x82, 0x19, 0xc5, 0x9e, 0xc7, 0xc0, 0xb6, 0xd9, 0x17, 0x25, + 0xe6, 0xc8, 0x90, 0x1f, 0x1d, 0x83, 0x48, 0x4c, 0x7f, 0x14, 0xcc, 0xe7, 0x2d, 0x0f, 0xa4, 0x31, + 0x90, 0x39, 0xd1, 0x57, 0x5c, 0xc0, 0x37, 0x93, 0x3c, 0x0c, 0x15, 0xd9, 0x63, 0x2c, 0x04, 0xc2, + 0x32, 0xe9, 0x93, 0xde, 0x42, 0xf2, 0x64, 0x3b, 0xd2, 0x6d, 0x6c, 0x87, 0x15, 0x22, 0xcf, 0xbf, + 0xe8, 0xad, 0x55, 0x78, 0x8e, 0xf2, 0xba, 0x1b, 0xf4, 0x8f, 0x13, 0xac, 0x8a, 0x71, 0xb4, 0x06, + 0xf8, 0xf6, 0x5c, 0x89, 0xde, 0xe1, 0xc7, 0x50, 0xb2, 0x58, 0x6d, 0x96, 0x2b, 0xe9, 0x56, 0x5d, + 0x0c, 0xb7, 0x2b, 0x2d, 0x32, 0xc9, 0x2c, 0x13, 0x6d, 0xd0, 0x5c, 0x94, 0x32, 0x0d, 0x37, 0x33, + 0xef, 0x56, 0xb6, 0x6a, 0xf3, 0x3a, 0xa4, 0x6c, 0x35, 0xc2, 0x49, 0x1f, 0xa0, 0x09, 0xf0, 0x5e, + 0x69, 0x3b, 0x22, 0xca, 0xd6, 0x5f, 0xcc, 0x06, 0xe7, 0xb6, 0x50, 0xc1, 0xf3, 0xef, 0x22, 0xa6, + 0x5c, 0x2a, 0x0f, 0x1a, 0x33, 0x91, 0x7a, 0x05, 0x71, 0xae, 0x7c, 0x16, 0x1d, 0xeb, 0x79, 0x0c, + 0xa3, 0x2e, 0x3a, 0xef, 0x02, 0x97, 0x08, 0x6b, 0x75, 0x07, 0x5b, 0xa7, 0x77, 0x8b, 0x15, 0x5e, + 0x8c, 0x0a, 0xde, 0xc6, 0xa3, 0x61, 0x63, 0x1b, 0x8b, 0xaf, 0x35, 0xb8, 0x3e, 0xa4, 0x7b, 0x2d, + 0x5d, 0xca, 0x24, 0x40, 0x03, 0xd2, 0x0f, 0xde, 0x63, 0xbe, 0xfc, 0x12, 0xdd, 0xfd, 0x03, 0xe7, + 0x41, 0x19, 0x1a, 0xe2, 0x40, 0x26, 0x2a, 0x2d, 0x49, 0x7f, 0x80, 0x3f, 0x93, 0x2d, 0x6f, 0x67, + 0x42, 0x4e, 0xe4, 0x5f, 0xbe, 0xf9, 0x4b, 0x98, 0x24, 0x68, 0x30, 0x4d, 0x74, 0xe1, 0xe9, 0x21, + 0x5d, 0x99, 0xf9, 0x76, 0xf9, 0x51, 0x8e, 0xb6, 0xa0, 0x5b, 0x21, 0x30, 0x38, 0x84, 0x9c, 0xbe, + 0x09, 0xb6, 0x7f, 0xa0, 0x13, 0x98, 0x71, 0x86, 0x3a, 0x01, 0xfa, 0x52, 0x18, 0x55, 0x2e, 0x58, + 0x38, 0x71, 0x27, 0xc8, 0xb5, 0x9d, 0xbe, 0xd1, 0x11, 0xc6, 0x55, 0x8f, 0xa4, 0x0f, 0x6a, 0x7e, + 0x66, 0x30, 0x1d, 0x1d, 0x8e, 0xd9, 0xaf, 0x6c, 0x4a, 0x42, 0xbc, 0x1b, 0xcf, 0x56, 0x1d, 0x79, + 0x37, 0x74, 0xa8, 0xe2, 0xbb, 0x6a, 0x63, 0xdb, 0xe8, 0xee, 0xe9, 0xa4, 0x8b, 0x83, 0xde, 0x23, + 0x5a, 0xd8, 0xa2, 0x7a, 0x98, 0x7b, 0x19, 0x5f, 0x4c, 0x20, 0x27, 0xc1, 0x3d, 0x4b, 0xc9, 0x33, + 0x40, 0x01, 0x1b, 0x92, 0xd7, 0x32, 0x7b, 0xd0, 0x09, 0x99, 0x41, 0x35, 0x64, 0x79, 0x4c, 0x70, + 0x89, 0xa1, 0x6f, 0xd3, 0x40, 0x5c, 0x76, 0x2e, 0x08, 0xa8, 0xc7, 0xed, 0x07, 0x1e, 0x26, 0x54, + 0xa2, 0x8b, 0x90, 0x26, 0x1b, 0x9c, 0x84, 0x45, 0x9e, 0x7c, 0x9a, 0x46, 0x85, 0x10, 0x1c, 0x64, + 0xcb, 0xeb, 0x14, 0x5a, 0x24, 0x53, 0x3b, 0x2c, 0xe4, 0x16, 0x39, 0xfc, 0x07, 0x65, 0xf3, 0xb5, + 0x5e, 0x41, 0x6e, 0x91, 0x53, 0x95, 0xfa, 0xea, 0x8b, 0xce, 0x7b, 0x49, 0x1f, 0x34, 0x41, 0x0f, + 0xdd, 0x2e, 0x4f, 0xbd, 0xa1, 0xac, 0x45, 0x9c, 0xed, 0xa1, 0x2b, 0x20, 0x90, 0xbb, 0x5b, 0x95, + 0x1b, 0x10, 0x17, 0x31, 0x22, 0x8a, 0x1e, 0x76, 0x87, 0x6e, 0xa6, 0xbe, 0xbb, 0x7b, 0xe9, 0x34, + 0x98, 0xae, 0xb3, 0x2c, 0x35, 0x9f, 0x63, 0x62, 0xd5, 0x2a, 0xaa, 0x90, 0xb2, 0xf8, 0x77, 0xba, + 0xe0, 0x2c, 0x8d, 0x43, 0xb1, 0xc8, 0xd3, 0xc7, 0x84, 0x96, 0x14, 0xe1, 0xc5, 0x38, 0x86, 0xab, + 0xfe, 0x80, 0xc6, 0xd0, 0x32, 0xf7, 0x41, 0xc3, 0x64, 0xc2, 0x77, 0x49, 0x69, 0x01, 0x3c, 0x9d, + 0xce, 0x6b, 0x87, 0x21, 0x40, 0x15, 0x78, 0xbf, 0x70, 0x63, 0xd4, 0x26, 0x8d, 0x98, 0xfa, 0xbb, + 0x19, 0x44, 0xf5, 0x1f, 0x9d, 0x25, 0x68, 0x8b, 0x8f, 0x7d, 0x4f, 0x9a, 0x44, 0xbe, 0x2b, 0x01, + 0x26, 0xaa, 0xc3, 0xf1, 0x88, 0x98, 0xbe, 0x4a, 0xcc, 0x6b, 0x17, 0x3f, 0xc6, 0x21, 0xb8, 0x1d, + 0xfe, 0x9e, 0xf7, 0x71, 0x50, 0xcf, 0xe5, 0x30, 0xd6, 0x83, 0xa1, 0x86, 0x1f, 0x02, 0x1e, 0x49, + 0xf5, 0x12, 0xf8, 0x0f, 0x15, 0x6e, 0x11, 0xce, 0x95, 0xfb, 0x8f, 0x28, 0xef, 0xa7, 0xe5, 0x75, + 0x74, 0x4e, 0xc1, 0x77, 0x8e, 0x30, 0x55, 0xf4, 0x18, 0xfc, 0x8e, 0x07, 0xc5, 0xf4, 0xd5, 0x78, + 0x30, 0xb6, 0x3b, 0x84, 0xe7, 0x4f, 0xc3, 0xf0, 0x74, 0x29, 0x18, 0x24, 0xe3, 0x19, 0x48, 0x99, + 0x7c, 0xcf, 0xe3, 0x88, 0xe1, 0x90, 0x5e, 0x89, 0xe9, 0x4f, 0x89, 0xfb, 0x95, 0x1e, 0xbd, 0xca, + 0xc8, 0x90, 0x19, 0xc2, 0x0a, 0xdb, 0x84, 0x9b, 0x07, 0x5d, 0x60, 0x9b, 0xc0, 0x71, 0xb8, 0xb1, + 0x65, 0xec, 0x80, 0xfe, 0x68, 0xe6, 0x5e, 0x7c, 0x49, 0x30, 0x13, 0xd2, 0x65, 0x63, 0x13, 0xe8, + 0x06, 0xb8, 0xb9, 0x2e, 0x79, 0x43, 0x90, 0x18, 0x63, 0x93, 0x28, 0x2a, 0x58, 0x9a, 0x14, 0x2c, + 0x62, 0x85, 0xec, 0xf9, 0x3a, 0x8c, 0x6d, 0x1c, 0xdb, 0x8c, 0xaf, 0x99, 0xd1, 0xb7, 0x0b, 0xb3, + 0x72, 0xe9, 0xe0, 0x73, 0xe5, 0x85, 0x4a, 0x27, 0xc8, 0x2e, 0x73, 0x44, 0x7c, 0xef, 0x07, 0xf8, + 0x7b, 0x1b, 0x48, 0x81, 0x30, 0x16, 0x82, 0x67, 0xa3, 0x77, 0xd1, 0xaa, 0x38, 0xc7, 0x4a, 0xd1, + 0x78, 0x06, 0x98, 0xc1, 0xf8, 0xee, 0xe7, 0x21, 0xbc, 0x94, 0x1f, 0xd6, 0x3f, 0xb6, 0x19, 0x5c, + 0x2c, 0x7b, 0x95, 0xb2, 0x03, 0xfe, 0x70, 0x81, 0xdc, 0x59, 0x8f, 0x37, 0x83, 0x65, 0x74, 0x89, + 0x0d, 0xaa, 0x94, 0x65, 0xfc, 0x01, 0xc0, 0x18, 0x9e, 0x77, 0x5c, 0xa4, 0xc2, 0x4f, 0x59, 0x94, + 0x1f, 0xc7, 0x6d, 0xdb, 0x82, 0x47, 0x9d, 0xe1, 0x50, 0xc4, 0x3b, 0x18, 0x33, 0x07, 0x5f, 0xa8, + 0xc0, 0xdb, 0x5f, 0x5a, 0x7a, 0xb5, 0x44, 0x2b, 0xed, 0xc4, 0x85, 0x3d, 0xb0, 0x13, 0x97, 0x5d, + 0x5c, 0xa0, 0x82, 0x81, 0xb1, 0x3c, 0x95, 0x06, 0x13, 0x31, 0x6b, 0x86, 0xc5, 0xc4, 0x74, 0x71, + 0x06, 0x1f, 0x38, 0x5e, 0x7c, 0xf5, 0x7b, 0x96, 0x0a, 0x39, 0xd3, 0x2c, 0xff, 0xd7, 0xa8, 0xa1, + 0xeb, 0xeb, 0xee, 0x51, 0x43, 0x22, 0x64, 0x70, 0xcf, 0x18, 0x48, 0xe1, 0xe1, 0xda, 0x5f, 0xe9, + 0x71, 0x0e, 0x96, 0xcc, 0x34, 0xac, 0xb2, 0x60, 0xdc, 0x7d, 0x5f, 0x7d, 0x28, 0xb0, 0x5a, 0xa8, + 0x04, 0x56, 0xf7, 0x41, 0xa8, 0x26, 0xbe, 0x15, 0x8d, 0xde, 0xe2, 0x93, 0x18, 0xf4, 0x04, 0xcf, + 0xaa, 0xc8, 0x58, 0xf1, 0xad, 0x38, 0x0a, 0xd0, 0x51, 0x80, 0x86, 0x64, 0x82, 0xe7, 0x70, 0x2b, + 0x6f, 0x3a, 0xbe, 0xe9, 0xae, 0x3a, 0x82, 0x38, 0xd7, 0x04, 0xdf, 0x59, 0x09, 0x8e, 0xd7, 0x2d, + 0x1d, 0xfc, 0x70, 0x00, 0x8f, 0x3a, 0xb0, 0x64, 0xa1, 0xe3, 0xd1, 0x37, 0x25, 0x77, 0xd5, 0x05, + 0xc7, 0x4b, 0x0f, 0x8e, 0x37, 0x47, 0x38, 0xf8, 0x3d, 0x6e, 0x77, 0x38, 0x13, 0xa4, 0x83, 0x88, + 0x3e, 0x57, 0x96, 0xce, 0x87, 0xce, 0x0c, 0xef, 0x02, 0xb7, 0x3a, 0x67, 0x54, 0xd0, 0x6a, 0x73, + 0xcd, 0xf2, 0x6c, 0x6f, 0xf0, 0xf0, 0x2d, 0x38, 0x64, 0xfe, 0xa0, 0x05, 0x25, 0x91, 0x9c, 0x23, + 0xc4, 0xd4, 0x24, 0x56, 0xe0, 0x9b, 0x2d, 0x90, 0x04, 0xf4, 0x72, 0xd5, 0x0f, 0xd2, 0x48, 0x0d, + 0x34, 0x24, 0x5a, 0x70, 0x83, 0x37, 0x38, 0xae, 0xc4, 0xff, 0xa0, 0x60, 0xe1, 0xc6, 0x23, 0x60, + 0xff, 0xa4, 0x65, 0x3c, 0xc4, 0x2d, 0xc6, 0x76, 0x71, 0x3f, 0xd5, 0xbd, 0x0a, 0xbf, 0xf8, 0x1b, + 0x9c, 0x6f, 0x30, 0x8c, 0xec, 0x5c, 0x4c, 0x09, 0xc8, 0x49, 0x79, 0xeb, 0x5d, 0x4a, 0x9c, 0x50, + 0x8f, 0x91, 0x42, 0xc5, 0x81, 0x84, 0x01, 0xd1, 0xa8, 0x9d, 0x04, 0x2a, 0x0a, 0xf9, 0xcf, 0x25, + 0x6e, 0x47, 0x45, 0x07, 0x81, 0x00, 0xdf, 0x0f, 0x0b, 0xa8, 0xd1, 0xcb, 0xf6, 0x8b, 0x15, 0x6d, + 0xca, 0xfb, 0xd4, 0xc6, 0xae, 0x07, 0x30, 0xf0, 0x6f, 0x8f, 0x45, 0x47, 0x00, 0xe3, 0x24, 0xd4, + 0x89, 0xd0, 0x12, 0xca, 0x27, 0x0d, 0x67, 0x49, 0x0b, 0x24, 0x09, 0xa0, 0x31, 0x86, 0x3e, 0x6e, + 0xe9, 0x59, 0x2e, 0xb5, 0x85, 0xe4, 0xb8, 0xb9, 0xa5, 0x7d, 0xa4, 0xe2, 0x0c, 0x10, 0xd9, 0x9d, + 0x34, 0x4d, 0xcd, 0x8f, 0x15, 0x8d, 0x71, 0x62, 0xdc, 0xdb, 0xd0, 0x03, 0xf4, 0xb3, 0x53, 0x47, + 0xcd, 0x3b, 0x06, 0x4f, 0xeb, 0xf7, 0x88, 0x15, 0xfe, 0x5c, 0x79, 0x7a, 0x54, 0x48, 0x18, 0x01, + 0xfe, 0xb8, 0x59, 0xfe, 0x4c, 0x1b, 0x3c, 0xe0, 0x8a, 0x5c, 0x12, 0x75, 0x1d, 0xcf, 0xa3, 0x97, + 0xa1, 0xfb, 0xe8, 0x11, 0x1a, 0x35, 0x74, 0xef, 0xf0, 0x9f, 0x84, 0xf7, 0x42, 0xcc, 0x1b, 0x09, + 0x71, 0x7b, 0x5a, 0x98, 0xb3, 0x18, 0x93, 0xee, 0xa9, 0xf4, 0x5d, 0x41, 0xd2, 0x6f, 0x98, 0xb8, + 0x29, 0x2a, 0x59, 0xa8, 0xd3, 0x18, 0x9a, 0xf8, 0xef, 0x89, 0x07, 0xf7, 0xe7, 0xf1, 0xf2, 0x81, + 0xa4, 0x59, 0x26, 0xa6, 0xfe, 0xbc, 0x17, 0x22, 0x52, 0xbe, 0x83, 0xdb, 0x31, 0x00, 0x5e, 0xbb, + 0xe7, 0x0a, 0x72, 0x26, 0x73, 0xbb, 0x97, 0xff, 0xfc, 0x26, 0xb7, 0xe3, 0x18, 0x7b, 0xca, 0x22, + 0xf2, 0xde, 0x6c, 0x31, 0xef, 0x9f, 0x99, 0xa8, 0xa9, 0x7b, 0xe4, 0xaf, 0xdd, 0xa1, 0x98, 0x9f, + 0x52, 0xe0, 0x7c, 0xad, 0xb3, 0x81, 0xa1, 0x2f, 0xa0, 0x57, 0x37, 0x33, 0xca, 0x48, 0x8b, 0x26, + 0x82, 0x64, 0x84, 0x4b, 0x8c, 0x3d, 0x34, 0xde, 0xb7, 0x38, 0x1e, 0xe2, 0xac, 0xfe, 0x13, 0x34, + 0x59, 0x26, 0xa9, 0x96, 0x89, 0x9a, 0x97, 0x13, 0x8e, 0x41, 0x9e, 0x23, 0x5c, 0x00, 0x4a, 0x97, + 0xbf, 0xac, 0x32, 0x1c, 0xc3, 0x27, 0xe1, 0xe7, 0x50, 0x1d, 0xbe, 0xfa, 0x65, 0xc8, 0x8a, 0x62, + 0x02, 0xbb, 0x57, 0x51, 0xd5, 0x6e, 0x53, 0x2b, 0x6b, 0xc2, 0xcb, 0xd4, 0xd0, 0x0f, 0x2b, 0xf6, + 0x79, 0x03, 0x88, 0x3c, 0x17, 0x6e, 0xec, 0x1a, 0x0f, 0x2e, 0x08, 0x34, 0x80, 0x3f, 0xc0, 0x5e, + 0x85, 0xc5, 0x4b, 0xcc, 0x73, 0xc3, 0x3c, 0x42, 0xd8, 0x13, 0x03, 0x61, 0x4f, 0x92, 0x1b, 0x5f, + 0xef, 0xa4, 0x49, 0x94, 0x98, 0xd3, 0x32, 0x3e, 0xe7, 0x0c, 0x28, 0x09, 0x26, 0x52, 0xa0, 0x2e, + 0xb1, 0xbd, 0xa0, 0x23, 0xdc, 0xb6, 0x2b, 0xd4, 0xa5, 0x27, 0x29, 0x67, 0x75, 0xdf, 0x7d, 0x46, + 0x8f, 0xb7, 0xf7, 0x2b, 0x47, 0x76, 0x1f, 0xc3, 0x42, 0x66, 0xeb, 0x68, 0x07, 0xd0, 0x35, 0xdb, + 0xdf, 0xf1, 0xe0, 0xdb, 0x17, 0xcc, 0x5f, 0x2a, 0x97, 0xbc, 0x7d, 0x81, 0x5e, 0x03, 0xf8, 0x17, + 0x5d, 0xe2, 0xd4, 0x55, 0xf5, 0xf4, 0x05, 0x8d, 0x2b, 0x78, 0x04, 0xf5, 0xde, 0x07, 0x50, 0x25, + 0x3e, 0xf4, 0xee, 0x05, 0x0c, 0x1e, 0x20, 0x34, 0xa9, 0x13, 0x4e, 0x33, 0x87, 0x19, 0x58, 0x2e, + 0x0c, 0x27, 0x69, 0xf8, 0x18, 0x7b, 0xca, 0xfe, 0x34, 0x5e, 0x8d, 0xc9, 0x2c, 0x66, 0x69, 0x98, + 0x9c, 0x99, 0x6d, 0xb6, 0xd8, 0x32, 0xee, 0x0d, 0x67, 0x6c, 0xcb, 0xe1, 0x8f, 0x3c, 0x9b, 0x4e, + 0xf4, 0x4c, 0x05, 0x41, 0xb5, 0x9b, 0x5c, 0xe2, 0x93, 0x28, 0xc6, 0xaa, 0x06, 0x6c, 0x2c, 0xb4, + 0x20, 0xd2, 0x3e, 0x8a, 0x94, 0xdc, 0x18, 0x8e, 0x96, 0xd0, 0x2e, 0xef, 0x63, 0x63, 0xc7, 0x7f, + 0xd8, 0x7e, 0x7f, 0x89, 0xb2, 0xe0, 0x6f, 0xc5, 0xad, 0xd2, 0xdc, 0x6c, 0x8b, 0x7c, 0x98, 0xae, + 0x20, 0x1a, 0x76, 0x09, 0xb6, 0x12, 0x26, 0x60, 0x33, 0x66, 0x2f, 0x0e, 0xb9, 0x0f, 0x07, 0xcf, + 0x96, 0x4d, 0x8c, 0xa2, 0x61, 0xca, 0xb9, 0x97, 0xed, 0x0a, 0xde, 0x86, 0x3c, 0x44, 0x5f, 0xed, + 0xd2, 0xd5, 0x9a, 0xe2, 0xb9, 0x5d, 0x39, 0x33, 0xb8, 0x5d, 0x47, 0xb9, 0x5d, 0x6e, 0x7a, 0x68, + 0x34, 0x2c, 0x85, 0x07, 0xeb, 0x1e, 0xc2, 0x40, 0x29, 0x7d, 0x9f, 0x68, 0xe6, 0xa0, 0x81, 0xae, + 0x22, 0x39, 0x4d, 0x62, 0x98, 0xfe, 0xf4, 0xa3, 0x60, 0x82, 0x18, 0xd4, 0x77, 0x70, 0xd5, 0x89, + 0x98, 0x3e, 0x65, 0xaa, 0xc1, 0x72, 0xc1, 0x30, 0x8d, 0x7e, 0x49, 0x95, 0x25, 0xbc, 0xe0, 0xd1, + 0xb2, 0xca, 0x5b, 0xc7, 0xd2, 0x3c, 0x48, 0x1a, 0x8f, 0x90, 0x9c, 0xe3, 0xb8, 0xc3, 0xc0, 0xbb, + 0x85, 0x6f, 0x58, 0x6e, 0xdb, 0x01, 0x7a, 0xad, 0xec, 0x00, 0xc9, 0xd9, 0x23, 0xde, 0x85, 0x2f, + 0xaa, 0xc2, 0xf2, 0xb8, 0x55, 0xcd, 0x9e, 0x23, 0x90, 0xa7, 0xaf, 0xd1, 0x8c, 0xad, 0x17, 0x93, + 0x7b, 0x45, 0x3d, 0x5e, 0xba, 0xf9, 0xa7, 0xf8, 0xec, 0x4d, 0x71, 0x8b, 0x6a, 0x6c, 0x0f, 0xc9, + 0xf1, 0x1a, 0x5a, 0xc8, 0xb5, 0x49, 0x39, 0x32, 0xf7, 0x46, 0x03, 0xd8, 0xe2, 0x22, 0x58, 0x76, + 0x8c, 0x60, 0x16, 0xed, 0x10, 0xa7, 0xc4, 0x7e, 0x0e, 0x71, 0x23, 0xf7, 0x7a, 0x9d, 0xee, 0x5a, + 0x65, 0x3f, 0xcb, 0xdf, 0xbd, 0xc8, 0xe8, 0x25, 0x03, 0xb1, 0x9f, 0xdb, 0xc7, 0x40, 0x1b, 0x32, + 0x30, 0x89, 0x3f, 0xc3, 0x6d, 0x0b, 0x00, 0x7d, 0x41, 0x30, 0x74, 0xaf, 0x3e, 0x0d, 0x09, 0x0a, + 0xde, 0xfc, 0x6a, 0x17, 0xf9, 0x36, 0x58, 0x6d, 0x17, 0x81, 0x0b, 0x81, 0x76, 0xed, 0x80, 0xa2, + 0xfa, 0x2c, 0x1f, 0x89, 0xd9, 0x7e, 0x85, 0x2c, 0xf2, 0x6e, 0xb2, 0x55, 0x45, 0x22, 0x21, 0x94, + 0xc6, 0xdf, 0x31, 0x68, 0x8f, 0xed, 0x25, 0x16, 0x35, 0x49, 0x03, 0x67, 0xbf, 0x47, 0x34, 0x1e, + 0x10, 0x3c, 0x31, 0x19, 0x9c, 0x4b, 0xaf, 0xbc, 0x5d, 0x2b, 0xae, 0xd0, 0x90, 0xba, 0x91, 0xcd, + 0xce, 0x71, 0xf4, 0x9a, 0xb4, 0x6d, 0x09, 0x4e, 0x2c, 0x69, 0x06, 0xcd, 0x77, 0x4c, 0x05, 0x96, + 0x33, 0xa8, 0x40, 0x92, 0x20, 0x6a, 0x22, 0x3d, 0xd2, 0xbc, 0xba, 0xbd, 0x90, 0x7d, 0x33, 0xd7, + 0xff, 0x40, 0x4e, 0x89, 0x39, 0xc7, 0x1f, 0xf5, 0x25, 0x8b, 0x8f, 0xa0, 0x81, 0x5f, 0x23, 0x03, + 0x55, 0x0d, 0xa8, 0x72, 0xe2, 0xcf, 0x54, 0x24, 0xaf, 0x4b, 0x68, 0x05, 0x0d, 0x03, 0xd5, 0x9b, + 0x3a, 0x40, 0xbf, 0xc2, 0x8d, 0xf2, 0x78, 0x53, 0x17, 0x99, 0x03, 0x6b, 0xfd, 0xcf, 0x54, 0xb1, + 0xe7, 0xc9, 0xa3, 0x9a, 0xa9, 0xbd, 0xf8, 0x76, 0xa9, 0xa9, 0x03, 0x70, 0x2c, 0x85, 0x21, 0x0c, + 0x75, 0xdc, 0x36, 0x17, 0x6e, 0xeb, 0x81, 0xb5, 0xfe, 0x81, 0xbe, 0x41, 0x2a, 0x69, 0x70, 0x40, + 0xe1, 0x85, 0x66, 0x2d, 0xcb, 0x3b, 0xa4, 0xc5, 0xcd, 0xe2, 0xdd, 0x62, 0xbe, 0xea, 0x10, 0x8e, + 0x60, 0x38, 0x4b, 0x2f, 0xed, 0x92, 0x3c, 0x19, 0xc4, 0xb6, 0x49, 0x2d, 0xe6, 0x6b, 0xec, 0xf3, + 0xd9, 0xb3, 0xe3, 0xf3, 0xe4, 0xaa, 0x01, 0x56, 0x4b, 0x2f, 0x3d, 0x5e, 0x60, 0xec, 0x33, 0xa0, + 0xb3, 0xb4, 0xbc, 0x59, 0x0d, 0x4a, 0x8a, 0x87, 0xce, 0xad, 0x01, 0xa2, 0x27, 0x4f, 0xa8, 0x1a, + 0xd3, 0x71, 0x0d, 0x66, 0x1a, 0x47, 0x20, 0x83, 0xbe, 0xb8, 0x91, 0x9f, 0xf3, 0x31, 0xe0, 0x35, + 0x97, 0xe1, 0xf6, 0x3b, 0xb8, 0x1b, 0xe8, 0x31, 0xaf, 0x39, 0xc8, 0x5d, 0x46, 0xd2, 0x22, 0xa3, + 0x9f, 0xdb, 0xb6, 0x51, 0x79, 0x97, 0x06, 0x32, 0x3b, 0xfc, 0x69, 0x0b, 0x53, 0x07, 0x3f, 0x9a, + 0x66, 0x0b, 0xfe, 0xce, 0x7c, 0xba, 0x90, 0xe1, 0xde, 0xb9, 0xe9, 0x0c, 0x7c, 0xd7, 0x81, 0x7c, + 0x24, 0x95, 0x07, 0x43, 0x09, 0x64, 0x06, 0x77, 0x81, 0x72, 0xba, 0x1c, 0x26, 0x7c, 0xc7, 0x5a, + 0x2b, 0x96, 0xab, 0x44, 0xf4, 0xaa, 0x78, 0x22, 0x61, 0xec, 0x12, 0x39, 0xf0, 0xbf, 0x62, 0x3a, + 0x2a, 0x8c, 0xe1, 0x14, 0xfa, 0x60, 0x0f, 0xee, 0xe1, 0x5c, 0xc5, 0xcd, 0x9d, 0xa5, 0xc7, 0x70, + 0xdf, 0x0c, 0x3c, 0xf2, 0x55, 0x6e, 0x69, 0x4e, 0x3b, 0xb8, 0x64, 0x6e, 0xe9, 0xd5, 0x14, 0xa1, + 0x31, 0x06, 0x51, 0x7a, 0xf9, 0x06, 0xf0, 0xe6, 0x38, 0x0b, 0x10, 0x28, 0x44, 0xd7, 0x53, 0x2a, + 0x25, 0xe3, 0x6f, 0x21, 0x5f, 0x3a, 0x7b, 0xfd, 0x0f, 0xb4, 0x44, 0x20, 0x0f, 0x6b, 0x1b, 0x52, + 0x22, 0x0d, 0x30, 0x47, 0xe7, 0x61, 0xd8, 0x24, 0xc1, 0x78, 0x26, 0x66, 0x11, 0xe4, 0x87, 0xae, + 0x1c, 0x3c, 0xd1, 0x4b, 0x4e, 0xc4, 0xdc, 0x22, 0x28, 0x5f, 0xd1, 0xd9, 0xac, 0xad, 0x1a, 0x40, + 0x69, 0x38, 0x23, 0xf0, 0x36, 0xf2, 0x66, 0xc9, 0xcc, 0x4a, 0xd1, 0x59, 0xe0, 0xf3, 0x0d, 0xce, + 0x73, 0x5a, 0xbe, 0x48, 0x8a, 0x3b, 0x29, 0x3a, 0xd4, 0x98, 0xb5, 0x3a, 0x34, 0xa0, 0x67, 0xe2, + 0xdc, 0x70, 0x78, 0x7c, 0x52, 0x35, 0x3e, 0xaf, 0x6b, 0xee, 0xea, 0x73, 0xaa, 0xb9, 0xf3, 0xce, + 0x81, 0x08, 0x53, 0x21, 0xbb, 0x0b, 0x77, 0x9e, 0x53, 0x8b, 0x79, 0x5d, 0xe3, 0x57, 0x9f, 0xd3, + 0x48, 0x2b, 0x4f, 0xaa, 0x61, 0x96, 0xc9, 0x1c, 0xc9, 0x1c, 0x06, 0x5e, 0xdb, 0x70, 0xc1, 0x71, + 0x4f, 0x42, 0xbd, 0x50, 0x3f, 0x20, 0xe6, 0xb4, 0x4b, 0xd1, 0xcd, 0xd2, 0x73, 0xb2, 0x42, 0x1f, + 0x10, 0x2a, 0xd4, 0xc5, 0x3c, 0xc6, 0x7f, 0x9b, 0x21, 0x9a, 0x5a, 0x20, 0x2a, 0x31, 0xf9, 0x93, + 0x08, 0x9d, 0x8a, 0x16, 0x43, 0x13, 0xb7, 0x6d, 0x4d, 0xe8, 0x16, 0xa2, 0x9f, 0xa8, 0x85, 0x23, + 0xab, 0x14, 0x07, 0xb0, 0x55, 0x39, 0xc6, 0xdb, 0x71, 0x72, 0x48, 0xac, 0x4d, 0x6f, 0xf7, 0x70, + 0xd5, 0xad, 0xe8, 0xf5, 0x2a, 0xd4, 0x42, 0xa3, 0x56, 0xcc, 0xf7, 0x8b, 0xb9, 0xdf, 0x8b, 0x8f, + 0xdf, 0x14, 0xd3, 0x7b, 0x41, 0xdc, 0xac, 0x17, 0x57, 0x48, 0x40, 0xd3, 0x34, 0xb6, 0x45, 0xe4, + 0x94, 0xf7, 0xa6, 0x20, 0x2a, 0x25, 0x39, 0x1d, 0x2f, 0x4e, 0x92, 0x32, 0x03, 0x42, 0xbf, 0x6a, + 0x53, 0xc9, 0xfe, 0x67, 0xc0, 0xa1, 0x06, 0xe2, 0xdf, 0x52, 0x43, 0x3a, 0xbf, 0x7f, 0x15, 0x7a, + 0xd5, 0x35, 0x55, 0x7d, 0xa8, 0x4a, 0x2f, 0xde, 0x5d, 0xd5, 0x47, 0x65, 0xb3, 0x48, 0x29, 0xf3, + 0xc9, 0xfb, 0x33, 0xf0, 0xd2, 0xd1, 0x4f, 0xab, 0xfa, 0xc2, 0x10, 0xfc, 0x00, 0x54, 0xeb, 0x00, + 0x7c, 0xff, 0xfe, 0xf9, 0xe8, 0xa5, 0x67, 0xec, 0x5f, 0x86, 0x2e, 0x6a, 0xea, 0xfe, 0xef, 0x10, + 0xc7, 0xc4, 0xfd, 0x0f, 0x20, 0x70, 0xfc, 0xfe, 0x9f, 0x21, 0x50, 0xb3, 0xbf, 0x97, 0xde, 0x53, + 0xda, 0xaf, 0xba, 0x82, 0x0e, 0x36, 0xc0, 0xfb, 0x71, 0x3b, 0x96, 0x5e, 0xa3, 0xe9, 0x80, 0xf8, + 0xdc, 0xb6, 0x1a, 0xf7, 0x81, 0xaa, 0x1a, 0xd1, 0xf1, 0xc7, 0xc3, 0x94, 0xaf, 0xe0, 0x5c, 0x73, + 0xe9, 0x9e, 0x5a, 0xf4, 0x60, 0x98, 0xc6, 0xab, 0x07, 0x9d, 0x23, 0xa4, 0x1c, 0xb3, 0x69, 0x44, + 0xaf, 0x7a, 0x0f, 0x8b, 0x24, 0xb2, 0x21, 0x02, 0xfd, 0x3f, 0xbb, 0x07, 0x5b, 0x0c, 0xd9, 0x8f, + 0x8c, 0x16, 0x96, 0x98, 0xb3, 0xc3, 0xf1, 0x30, 0xfe, 0x8f, 0x74, 0xb5, 0xdd, 0xb3, 0x12, 0x4a, + 0xe2, 0x3d, 0xb4, 0x8f, 0xa9, 0xbd, 0xea, 0x06, 0x9e, 0x46, 0x72, 0xd5, 0x7e, 0xba, 0xde, 0x7e, + 0x46, 0x7f, 0x12, 0xea, 0xcd, 0x65, 0x88, 0x62, 0x88, 0x0e, 0x89, 0xcf, 0xa9, 0xc4, 0x28, 0xd1, + 0xae, 0x66, 0x4f, 0xa0, 0x02, 0xd8, 0x35, 0x63, 0x07, 0x08, 0x44, 0xf3, 0xcf, 0xa1, 0x6f, 0xc4, + 0x8d, 0xe6, 0x7d, 0x63, 0xc0, 0xa4, 0xac, 0xe2, 0x38, 0xee, 0xf0, 0x69, 0x6e, 0xf7, 0x05, 0xcf, + 0x15, 0x55, 0x55, 0x3f, 0xf3, 0x6e, 0x20, 0xb0, 0x0f, 0x7f, 0x34, 0x87, 0x33, 0x5d, 0x30, 0x9c, + 0x87, 0xf5, 0x16, 0x5f, 0x5a, 0xda, 0xac, 0x8c, 0x1a, 0x1c, 0x46, 0x3c, 0xb0, 0xcc, 0x0c, 0x25, + 0xf1, 0x9f, 0x74, 0xb4, 0x7d, 0x90, 0xe1, 0x32, 0xa0, 0x64, 0x06, 0x8c, 0xd0, 0xf7, 0x29, 0x49, + 0x78, 0x47, 0x6c, 0xbf, 0x3d, 0x15, 0x2c, 0x1a, 0xe4, 0x0f, 0xb1, 0x0b, 0x2e, 0xc2, 0xdb, 0x5a, + 0x90, 0xbe, 0x57, 0x68, 0x0f, 0x54, 0xb9, 0x67, 0xd5, 0xa0, 0xc0, 0xe3, 0x33, 0x35, 0x60, 0xe2, + 0x60, 0xe3, 0xc2, 0x69, 0x88, 0x06, 0xd5, 0x78, 0xa1, 0x31, 0x53, 0xd3, 0x98, 0xac, 0x98, 0x39, + 0x8b, 0xab, 0xd1, 0x5e, 0x85, 0xc0, 0x91, 0xdc, 0xe1, 0x53, 0xdc, 0xee, 0x66, 0xcf, 0xe5, 0xe1, + 0x04, 0x36, 0xef, 0xa3, 0x2b, 0xd6, 0x35, 0x3a, 0x2a, 0xc4, 0x35, 0x30, 0x6a, 0xbe, 0xe1, 0xaa, + 0xb2, 0x7e, 0xf5, 0x39, 0x56, 0xfe, 0x1f, 0x9d, 0x83, 0x3c, 0x45, 0x07, 0x79, 0xca, 0x5a, 0x09, + 0xa5, 0x84, 0x1e, 0xf1, 0x9f, 0x59, 0xab, 0xe0, 0x61, 0x12, 0xef, 0x67, 0x4f, 0x12, 0x05, 0x88, + 0x6f, 0xe2, 0x2b, 0x50, 0x5f, 0xa4, 0xd2, 0x29, 0x85, 0xd0, 0x2f, 0x08, 0xce, 0x92, 0x05, 0x77, + 0xcc, 0xa2, 0xd1, 0x38, 0x4d, 0xdc, 0xb6, 0xf5, 0xf4, 0x17, 0x05, 0x06, 0xe7, 0xd9, 0x22, 0x67, + 0x28, 0x2f, 0xf2, 0xd0, 0xec, 0x6e, 0x2f, 0xfa, 0xb2, 0x7d, 0xbf, 0x7a, 0x21, 0x9c, 0x99, 0xda, + 0xcc, 0x36, 0x5b, 0x2c, 0xc5, 0xac, 0xc5, 0x6c, 0xc9, 0xb2, 0x98, 0x87, 0xac, 0x75, 0xdb, 0xe8, + 0x56, 0xdd, 0xcc, 0xa1, 0x18, 0x22, 0x15, 0x0c, 0x8a, 0x86, 0x30, 0x21, 0x95, 0xb2, 0xcd, 0x55, + 0x22, 0x8a, 0x82, 0xe3, 0xca, 0xa1, 0x38, 0x84, 0xf4, 0x94, 0x06, 0xa2, 0xa6, 0x3a, 0x12, 0xbc, + 0xe4, 0x4e, 0xbb, 0xf0, 0x7a, 0xd1, 0xa8, 0x89, 0x40, 0xcb, 0x99, 0x8e, 0x04, 0xe0, 0xf5, 0xd1, + 0xd3, 0x99, 0x59, 0x59, 0x59, 0x99, 0x59, 0xe6, 0xac, 0xe1, 0x2b, 0xad, 0x2a, 0x2c, 0xb8, 0xd2, + 0xd2, 0xc5, 0x1a, 0xb0, 0x87, 0x2b, 0xd8, 0x95, 0xdc, 0x94, 0x1e, 0x1a, 0xdc, 0x33, 0xe4, 0x75, + 0xda, 0xe0, 0xa6, 0x1c, 0x2e, 0xd7, 0x77, 0xbe, 0x66, 0x1b, 0xa3, 0xbc, 0x06, 0x23, 0xa5, 0xaa, + 0xf1, 0x88, 0x12, 0x12, 0xb1, 0xab, 0xb7, 0x62, 0x1c, 0xfc, 0xab, 0xe8, 0xbd, 0x67, 0xd9, 0x66, + 0x7b, 0x42, 0x0e, 0xae, 0x4e, 0xd2, 0xbc, 0xff, 0x4a, 0x45, 0x41, 0x3f, 0xc2, 0xf2, 0x3f, 0x13, + 0xd3, 0xfc, 0xa2, 0xe5, 0x7b, 0x71, 0x2b, 0x38, 0x80, 0x36, 0xb6, 0x51, 0x5c, 0x73, 0x73, 0x6c, + 0xab, 0xeb, 0xe2, 0xd6, 0x51, 0xfa, 0x80, 0xeb, 0x34, 0x9e, 0x79, 0x31, 0x2f, 0x2e, 0xc4, 0xd3, + 0xa9, 0x8a, 0x8e, 0x07, 0x2a, 0xfc, 0xc2, 0x45, 0x06, 0x13, 0x64, 0x6d, 0x76, 0x2e, 0xd8, 0x03, + 0xf8, 0x6c, 0xf9, 0xca, 0x48, 0xdc, 0xae, 0x6f, 0x87, 0x50, 0x3b, 0xa1, 0x31, 0xb6, 0x15, 0x34, + 0x6c, 0x51, 0x45, 0x1b, 0xf1, 0x4e, 0x02, 0xc7, 0x6d, 0x94, 0x2b, 0xc6, 0x09, 0x0d, 0xf2, 0xb2, + 0x2d, 0xbf, 0xb1, 0x35, 0x7f, 0x41, 0xb9, 0x9b, 0x7e, 0x2c, 0x10, 0x48, 0xe8, 0x8f, 0xf5, 0xea, + 0xbb, 0xf1, 0xfc, 0x3f, 0xc9, 0x62, 0x41, 0x16, 0x35, 0x99, 0x59, 0xd9, 0xb9, 0x43, 0x64, 0xe2, + 0xa7, 0x8c, 0xcd, 0x04, 0x8b, 0xb7, 0xe4, 0xca, 0xc7, 0xe9, 0xf6, 0x8a, 0x66, 0xe8, 0xf4, 0xac, + 0xef, 0x51, 0x12, 0xf8, 0x5e, 0xcc, 0xaf, 0x06, 0x03, 0x9d, 0x77, 0x07, 0x5f, 0x6a, 0x81, 0xa9, + 0x31, 0x69, 0x20, 0x2c, 0x31, 0xab, 0x42, 0xbf, 0xb6, 0xa0, 0xc5, 0x28, 0xb9, 0x96, 0xfe, 0x9a, + 0x91, 0x56, 0x68, 0xd0, 0x52, 0xfb, 0x09, 0x6e, 0x5c, 0xa0, 0x00, 0x36, 0x88, 0x5b, 0xfd, 0xe2, + 0x9a, 0xef, 0x0d, 0xfd, 0xca, 0x6b, 0x83, 0x5e, 0x31, 0xad, 0xd7, 0xbe, 0x80, 0xd4, 0xb1, 0xfd, + 0xe3, 0xd7, 0xdc, 0xac, 0xea, 0x47, 0x93, 0x02, 0xb6, 0xed, 0xf3, 0xc0, 0x3b, 0x27, 0xdc, 0xa8, + 0xaa, 0x47, 0x03, 0xc0, 0x6d, 0xd3, 0x3c, 0xbc, 0x62, 0x21, 0x56, 0x74, 0x49, 0x07, 0xee, 0x46, + 0x83, 0x22, 0x15, 0x5d, 0x64, 0xa1, 0xef, 0x1e, 0x00, 0x80, 0x08, 0xa1, 0x9a, 0x16, 0xc7, 0x83, + 0x2d, 0xb2, 0x75, 0x58, 0xc4, 0x83, 0x5b, 0x15, 0x1d, 0x58, 0x68, 0x88, 0x59, 0xc6, 0xd7, 0xe0, + 0xbb, 0x52, 0xca, 0xdb, 0x31, 0x75, 0xaa, 0x5b, 0x3f, 0x5f, 0x04, 0x09, 0x50, 0x1c, 0x9e, 0x85, + 0x18, 0x35, 0xa2, 0x16, 0x7b, 0x34, 0xe1, 0x86, 0xea, 0x7f, 0xe3, 0x65, 0x00, 0xe3, 0xcc, 0x69, + 0xc6, 0x44, 0xce, 0x75, 0x19, 0xe3, 0x6a, 0x93, 0x16, 0x32, 0xdc, 0xf1, 0x7c, 0x34, 0xf8, 0x17, + 0xd7, 0x69, 0x88, 0x5d, 0x9d, 0x4f, 0x89, 0xc6, 0x78, 0xc9, 0xa1, 0x85, 0xac, 0x4b, 0x65, 0x88, + 0xe4, 0x97, 0x83, 0xb3, 0x56, 0x18, 0x02, 0x3e, 0xd8, 0x5e, 0x71, 0xc5, 0xcd, 0xb1, 0x10, 0x39, + 0x6b, 0x0d, 0xfd, 0x62, 0x4e, 0x34, 0x39, 0x17, 0x7b, 0x89, 0x7b, 0xbd, 0xde, 0x60, 0xd4, 0xc6, + 0x36, 0x73, 0x6f, 0x34, 0x2e, 0x32, 0xca, 0x93, 0x60, 0x21, 0x7d, 0xdd, 0xad, 0xee, 0x55, 0x7b, + 0xf9, 0xc8, 0xca, 0x8a, 0x99, 0x8c, 0xf3, 0xd1, 0x6a, 0x37, 0xe0, 0xc4, 0xb5, 0xce, 0x18, 0xef, + 0x74, 0x02, 0xb1, 0x8f, 0xf2, 0x7f, 0xac, 0xac, 0x98, 0xc1, 0x38, 0xa1, 0x3a, 0x91, 0x71, 0x96, + 0xe0, 0x5f, 0x58, 0x45, 0x9c, 0x93, 0x25, 0x20, 0xf0, 0x7c, 0x80, 0xd7, 0x0d, 0x8d, 0x25, 0x65, + 0x8b, 0x62, 0x32, 0xbe, 0xfb, 0xf4, 0x6e, 0x9f, 0x01, 0x5a, 0x00, 0xaa, 0x3b, 0x1b, 0xe9, 0x95, + 0x46, 0xf8, 0x82, 0x5b, 0x05, 0xe0, 0xc0, 0x7d, 0x2d, 0xf9, 0x35, 0xe5, 0xb6, 0xa9, 0x38, 0x82, + 0x44, 0xee, 0x64, 0x41, 0x9b, 0x94, 0xa9, 0x7d, 0x50, 0xb9, 0xa5, 0xda, 0xd5, 0x34, 0xb8, 0x84, + 0xcd, 0xc3, 0x78, 0xa0, 0x42, 0x4b, 0xfa, 0xc2, 0x21, 0xd9, 0xcd, 0x8b, 0x26, 0xde, 0xf1, 0x46, + 0x0d, 0x81, 0xbc, 0x3f, 0x0c, 0xcf, 0xb5, 0xdc, 0x98, 0x95, 0x55, 0x8b, 0xf4, 0xa0, 0xb6, 0x83, + 0xbe, 0x65, 0x8a, 0xeb, 0xb6, 0xb1, 0x9d, 0x98, 0x8e, 0x48, 0x8b, 0x9b, 0xc4, 0x11, 0x10, 0xff, + 0x10, 0xbe, 0x85, 0x6d, 0x10, 0x1f, 0x57, 0x41, 0x1c, 0x44, 0xf2, 0x8e, 0x8f, 0x3d, 0xb6, 0x08, + 0x26, 0xa7, 0x79, 0x52, 0x8e, 0xc6, 0xfe, 0x13, 0x03, 0x0f, 0xe1, 0xd5, 0x99, 0xd8, 0x0b, 0xf6, + 0x7b, 0x60, 0xd6, 0x1e, 0x85, 0x90, 0x16, 0xf4, 0x1a, 0x42, 0xd9, 0x60, 0x84, 0x03, 0xfa, 0x9c, + 0xd1, 0xa9, 0x46, 0xcc, 0xbc, 0x37, 0xd4, 0x07, 0xd6, 0x68, 0x91, 0x3f, 0x22, 0xad, 0xbc, 0x06, + 0xf3, 0xdb, 0xc6, 0xbd, 0x61, 0xfc, 0x4e, 0xa4, 0x28, 0x48, 0x5e, 0x13, 0x69, 0xb5, 0xcf, 0x5a, + 0xc4, 0x7b, 0xc9, 0x85, 0x49, 0x39, 0x4d, 0x8e, 0x78, 0x83, 0x9f, 0x34, 0xc7, 0x5e, 0x70, 0xc4, + 0x91, 0x05, 0x24, 0x5f, 0x95, 0xe0, 0x8f, 0x3d, 0x1f, 0x6e, 0x6c, 0x19, 0x6f, 0x3a, 0x8e, 0x01, + 0x48, 0x18, 0x79, 0x54, 0xd5, 0xf9, 0x17, 0xea, 0x4c, 0xbd, 0x86, 0x0a, 0x35, 0x7d, 0xc5, 0xab, + 0x29, 0xf6, 0x1a, 0xf7, 0x46, 0x9d, 0x3a, 0xe7, 0x3b, 0xfe, 0x5e, 0x03, 0xe4, 0x9f, 0x17, 0x62, + 0xaf, 0xd9, 0x27, 0xc2, 0xaa, 0x8f, 0x0c, 0x1b, 0x8f, 0x03, 0xbb, 0x09, 0x7d, 0xb1, 0x7e, 0xec, + 0x1b, 0x45, 0x96, 0xaa, 0x7c, 0x76, 0x20, 0x66, 0x12, 0xbe, 0x25, 0xd7, 0xe1, 0x58, 0x2f, 0x9a, + 0xbc, 0x86, 0x26, 0xe2, 0xc5, 0x5a, 0xbf, 0x63, 0xb5, 0x58, 0xd1, 0x12, 0x5e, 0x11, 0xea, 0x72, + 0x52, 0xcc, 0x6b, 0x23, 0x8b, 0x62, 0xfd, 0x24, 0x05, 0x7d, 0x32, 0xf8, 0x1d, 0xc7, 0x6a, 0x72, + 0x9d, 0x9c, 0x00, 0x2e, 0xf1, 0x1e, 0x01, 0xf0, 0x61, 0x3a, 0x22, 0x2e, 0x6f, 0xd6, 0x92, 0xe5, + 0x75, 0x5a, 0x88, 0xbe, 0xc8, 0x0b, 0x75, 0x2a, 0x08, 0xdf, 0x88, 0xa6, 0x2e, 0xe1, 0x44, 0xec, + 0x0d, 0xb2, 0xba, 0x4e, 0x43, 0x9c, 0x75, 0x6a, 0x7c, 0xf9, 0xda, 0xcc, 0x42, 0xf0, 0x01, 0x3d, + 0x8c, 0x32, 0xa4, 0xed, 0x7e, 0x43, 0x0b, 0xb7, 0xed, 0x57, 0x78, 0xb6, 0x9a, 0xe3, 0xaf, 0xea, + 0xc7, 0x1f, 0xe9, 0xc1, 0x24, 0x3e, 0x2f, 0x7a, 0xe3, 0x98, 0x90, 0xbb, 0x9c, 0xd3, 0x88, 0x97, + 0x5c, 0x20, 0x93, 0x93, 0x34, 0x59, 0x22, 0xc4, 0xee, 0xa9, 0x27, 0xb4, 0xf8, 0x7b, 0x3b, 0xa9, + 0x27, 0x54, 0x68, 0x14, 0xaa, 0x13, 0x64, 0xf5, 0x09, 0x0d, 0x79, 0xe1, 0x84, 0x5a, 0x5a, 0xca, + 0xe2, 0x0f, 0xa7, 0xc0, 0xcc, 0xcf, 0xbb, 0xed, 0x9c, 0x29, 0x49, 0x32, 0x1e, 0xc7, 0x2b, 0xd6, + 0x66, 0xf9, 0x30, 0x1b, 0x3c, 0x3f, 0x3d, 0xcc, 0xc8, 0x10, 0x98, 0xe5, 0xfd, 0x13, 0xcc, 0x5b, + 0x27, 0xd3, 0x83, 0xe1, 0x18, 0x7a, 0x3e, 0xdc, 0x61, 0x4e, 0x36, 0x65, 0x93, 0xbc, 0x36, 0xbc, + 0xd5, 0x61, 0x2e, 0x9e, 0x20, 0x27, 0xec, 0x0c, 0xde, 0x89, 0x3f, 0xc3, 0xf2, 0x73, 0xf0, 0xd7, + 0x1f, 0xa4, 0xc3, 0x2b, 0x9f, 0x09, 0x2d, 0xd6, 0x9f, 0x43, 0x40, 0x85, 0xbb, 0xfd, 0x77, 0x91, + 0x86, 0x34, 0x88, 0xac, 0xc8, 0x39, 0x7e, 0xa7, 0xad, 0x40, 0xf1, 0x53, 0x15, 0x0d, 0xf8, 0xf6, + 0x4c, 0x1a, 0x79, 0x08, 0x1d, 0x7c, 0x28, 0xb3, 0x44, 0xb7, 0x5c, 0x89, 0x61, 0x56, 0x0d, 0xbe, + 0xc9, 0x69, 0x25, 0xaf, 0x30, 0xab, 0x4b, 0xc2, 0x99, 0xc6, 0x6a, 0x66, 0x0d, 0x7c, 0xc1, 0xf8, + 0xaf, 0x7a, 0x3a, 0xb5, 0x08, 0x7d, 0x1a, 0x8a, 0x82, 0x5b, 0xc5, 0x9f, 0xe3, 0xd8, 0x7b, 0x7e, + 0x55, 0xc9, 0x30, 0x41, 0x5e, 0x72, 0x2d, 0x7b, 0x07, 0x2a, 0x70, 0x6d, 0xd4, 0xef, 0x1b, 0xa8, + 0xd0, 0xc2, 0xf7, 0xc3, 0x3b, 0x59, 0xab, 0x61, 0x80, 0x8f, 0xb6, 0x8a, 0x6c, 0x4f, 0x8b, 0x95, + 0xb0, 0xf0, 0x6d, 0x15, 0xa3, 0xac, 0x10, 0xcd, 0xbd, 0xe5, 0x0e, 0xed, 0x8e, 0xe0, 0x0d, 0xf3, + 0x8f, 0x59, 0xf9, 0x20, 0x0c, 0x5b, 0xed, 0xe6, 0x9f, 0x93, 0xf0, 0xcd, 0x15, 0xbc, 0xde, 0xfc, + 0x09, 0x13, 0xba, 0x6c, 0xce, 0xc6, 0xd3, 0xcb, 0xe6, 0x13, 0xe4, 0x25, 0x3b, 0x28, 0xaf, 0xd0, + 0xfa, 0xe7, 0x4a, 0xeb, 0xe5, 0x1d, 0xeb, 0x3e, 0x0c, 0xe3, 0xfb, 0x77, 0xfe, 0xed, 0xab, 0x75, + 0xc1, 0xa7, 0x16, 0xfa, 0xc4, 0x3a, 0xfb, 0x3b, 0x9e, 0x22, 0x9e, 0x20, 0xec, 0x54, 0xa8, 0xb6, + 0xf2, 0x68, 0x18, 0x69, 0xfa, 0xea, 0xa9, 0x20, 0xf8, 0x3f, 0x43, 0x60, 0x39, 0x99, 0xbe, 0xad, + 0x81, 0xdb, 0x9e, 0x33, 0xd6, 0x84, 0xe6, 0x05, 0xa6, 0x8e, 0xae, 0x42, 0xd1, 0x81, 0x79, 0xae, + 0x38, 0x08, 0x02, 0x6d, 0x3f, 0x55, 0xe4, 0x66, 0xfc, 0x2f, 0xbc, 0x4c, 0x1e, 0xfc, 0x67, 0xc9, + 0xc5, 0xdf, 0x4f, 0x24, 0x8d, 0xa2, 0x33, 0x5e, 0x2b, 0x2e, 0x8f, 0x8f, 0xbe, 0xfe, 0x41, 0xfe, + 0x9a, 0xfa, 0x5c, 0x71, 0x65, 0xbc, 0x1a, 0x22, 0x6f, 0xa8, 0x01, 0x98, 0xf6, 0xfa, 0x47, 0xa4, + 0x25, 0xbf, 0xfe, 0xf6, 0x96, 0x86, 0x9e, 0x4d, 0xaa, 0xeb, 0x1f, 0xf8, 0x34, 0xd2, 0x0b, 0x5c, + 0x40, 0x72, 0x8e, 0x09, 0xfc, 0x70, 0xfd, 0x83, 0x9b, 0xd0, 0x17, 0x9a, 0xbd, 0x00, 0xdd, 0x42, + 0xe8, 0xfe, 0xe8, 0x1c, 0x55, 0x9b, 0x1e, 0xb8, 0x3f, 0x2d, 0xc0, 0x1d, 0x72, 0xe3, 0xeb, 0x92, + 0xd0, 0x02, 0xc3, 0xbe, 0xdc, 0xe2, 0x19, 0xf4, 0x9a, 0x80, 0xa0, 0x8a, 0x8f, 0x38, 0x96, 0x01, + 0x31, 0x6e, 0x06, 0xf8, 0x7c, 0x88, 0x99, 0x63, 0xae, 0x7f, 0x50, 0x1c, 0x43, 0x6b, 0x32, 0xa0, + 0x6a, 0x44, 0xc6, 0xb1, 0xfc, 0x35, 0xc8, 0xcd, 0xe0, 0xbf, 0x9b, 0x7a, 0x37, 0xfe, 0x94, 0x05, + 0xb0, 0x31, 0x11, 0x58, 0x7e, 0xed, 0xb5, 0x50, 0x99, 0x95, 0xa3, 0x86, 0x95, 0xc3, 0xe4, 0xfe, + 0x81, 0xa1, 0xe5, 0x70, 0xf9, 0x9b, 0x61, 0x65, 0x95, 0x7c, 0x61, 0x58, 0x39, 0x42, 0x6e, 0x1e, + 0x56, 0x8e, 0x94, 0xf7, 0x0d, 0x2b, 0x8f, 0x90, 0x3f, 0x52, 0xca, 0xab, 0x68, 0x51, 0x2d, 0xff, + 0x86, 0x16, 0x6f, 0xd5, 0x8f, 0x94, 0x6b, 0x87, 0xd6, 0x47, 0xc9, 0xcf, 0x07, 0xeb, 0x95, 0xf2, + 0x28, 0xf9, 0x99, 0x61, 0x65, 0x8d, 0xfc, 0xe4, 0xb0, 0xf2, 0x68, 0xf9, 0xb1, 0x61, 0xe5, 0x31, + 0xf2, 0xa2, 0xe1, 0xf8, 0x39, 0xfa, 0xbb, 0x66, 0x83, 0x65, 0xad, 0x3c, 0x71, 0xe8, 0x78, 0x63, + 0xe5, 0x51, 0xc3, 0xdb, 0x8f, 0x93, 0xfb, 0x7f, 0x18, 0xda, 0x7e, 0xbc, 0xfc, 0xcd, 0xb0, 0x72, + 0xb4, 0x7c, 0x61, 0x58, 0x79, 0x82, 0xdc, 0x3c, 0xac, 0x7c, 0x97, 0xbc, 0x6f, 0x58, 0xf9, 0x6e, + 0xf9, 0xa3, 0x61, 0xe5, 0x18, 0xf9, 0x9d, 0x61, 0xe5, 0x58, 0xb9, 0x76, 0x58, 0x79, 0xa2, 0xfc, + 0xdc, 0xb0, 0xf2, 0x24, 0xb9, 0x78, 0x58, 0x39, 0x4e, 0xfe, 0xd9, 0xb0, 0xf2, 0x3d, 0xf2, 0xa3, + 0x3f, 0x0c, 0xe1, 0x67, 0xb2, 0xbc, 0xe8, 0x87, 0x61, 0xfc, 0x4c, 0x91, 0xef, 0x07, 0x40, 0x8e, + 0xfe, 0x28, 0xda, 0x68, 0x36, 0xfe, 0x46, 0x64, 0x0e, 0xbe, 0x72, 0x4c, 0xf0, 0x44, 0x0e, 0x5c, + 0x61, 0xb4, 0xeb, 0xf4, 0x96, 0x31, 0x3d, 0xde, 0x46, 0x63, 0x1b, 0xbe, 0x46, 0x20, 0x9c, 0x66, + 0x7a, 0x2e, 0xb8, 0x02, 0x5b, 0xa2, 0x7a, 0x72, 0xda, 0x7a, 0x3e, 0x17, 0xce, 0x31, 0x3d, 0x97, + 0xc0, 0x8b, 0x1a, 0x3b, 0xc8, 0x59, 0x57, 0x80, 0x7f, 0x10, 0x72, 0x37, 0x55, 0xcf, 0x75, 0xdf, + 0x64, 0xd7, 0x45, 0x7e, 0x14, 0x3d, 0xb2, 0xa2, 0xb7, 0x91, 0xea, 0x89, 0x9f, 0x7e, 0x37, 0xe0, + 0x0f, 0x2a, 0xf5, 0x20, 0xa0, 0xf3, 0x2d, 0x7c, 0x97, 0xb6, 0x1f, 0x52, 0x78, 0x0c, 0xcf, 0x0e, + 0x36, 0x0a, 0xfe, 0x29, 0x90, 0x63, 0xf2, 0x13, 0x0c, 0xe7, 0xe8, 0xab, 0xf4, 0xf6, 0x08, 0x43, + 0x5e, 0x87, 0xbd, 0x94, 0x86, 0x5e, 0xbe, 0x0d, 0xb0, 0x08, 0xd1, 0xdf, 0xa9, 0x25, 0x17, 0x12, + 0x9a, 0x6a, 0x4d, 0xed, 0xad, 0x5f, 0x13, 0x4f, 0xeb, 0x37, 0xf0, 0x30, 0xb2, 0x1f, 0xb2, 0xe6, + 0xd6, 0x2b, 0xe4, 0x5a, 0xb0, 0x4c, 0xf2, 0xce, 0x40, 0xae, 0xdb, 0xfa, 0x15, 0x14, 0x7d, 0x23, + 0xaf, 0x13, 0x6f, 0x0f, 0xac, 0x3e, 0x67, 0xf1, 0xa5, 0xa5, 0x9e, 0xc2, 0x33, 0xc4, 0xe8, 0x35, + 0x9c, 0xb5, 0x4f, 0x80, 0x56, 0x40, 0x43, 0x6b, 0x3b, 0xf1, 0x40, 0x9a, 0x6b, 0xf0, 0x38, 0x38, + 0xc3, 0x59, 0x7e, 0x94, 0x94, 0x1f, 0xf0, 0x45, 0xea, 0x03, 0xfa, 0x8b, 0x60, 0x44, 0x01, 0xa1, + 0xa2, 0x8d, 0x21, 0x5e, 0xd2, 0xef, 0x1c, 0xd1, 0xe3, 0xa5, 0xbc, 0x42, 0x24, 0xf6, 0x24, 0xf5, + 0x46, 0x77, 0xca, 0x07, 0x52, 0xf6, 0x0e, 0xa1, 0xa2, 0x03, 0x8f, 0xde, 0xf0, 0x9d, 0x4a, 0xa3, + 0x76, 0xcb, 0xd8, 0x9e, 0xbc, 0x76, 0x21, 0xa7, 0x83, 0xe9, 0xc9, 0xeb, 0x68, 0x34, 0x7a, 0x51, + 0x5e, 0x20, 0xbc, 0x10, 0x26, 0xe0, 0xbf, 0xa2, 0x1d, 0x28, 0x24, 0x2d, 0xae, 0xd3, 0xfc, 0x4c, + 0xc3, 0x75, 0x47, 0x24, 0xe9, 0x25, 0x17, 0x7c, 0x77, 0xbb, 0xba, 0x07, 0x85, 0xd5, 0x8d, 0x3f, + 0xdb, 0x41, 0xbf, 0x7b, 0xe1, 0xd3, 0xa2, 0x3f, 0xdd, 0xf9, 0x7b, 0x94, 0x54, 0xab, 0x03, 0x96, + 0x9e, 0x76, 0xfa, 0x0a, 0x30, 0x77, 0xf0, 0xb4, 0x70, 0x6d, 0x0a, 0xc9, 0x69, 0xe7, 0xa3, 0x0d, + 0x5e, 0xbb, 0xca, 0xd0, 0x63, 0x57, 0x93, 0xfe, 0x84, 0x6b, 0xb1, 0xf5, 0x90, 0x7c, 0xf5, 0xfa, + 0x78, 0x10, 0xd6, 0x3e, 0x14, 0x56, 0x42, 0x33, 0x4a, 0x04, 0x96, 0xc8, 0x6b, 0xad, 0xed, 0xb5, + 0xa6, 0x36, 0x45, 0x60, 0x75, 0xa4, 0xb7, 0xf5, 0x2b, 0x28, 0x11, 0x58, 0xa1, 0x2f, 0x40, 0x18, + 0x40, 0xa1, 0xad, 0x57, 0xb0, 0x5e, 0xc6, 0x7a, 0x10, 0xd9, 0x19, 0xd2, 0xda, 0xfa, 0x75, 0xcf, + 0x57, 0x40, 0xc6, 0x29, 0x43, 0xab, 0x3d, 0x92, 0x1f, 0x63, 0x68, 0x74, 0x44, 0x3d, 0x60, 0x6c, + 0x99, 0x04, 0x5d, 0x4e, 0x91, 0x86, 0x84, 0x9e, 0xd8, 0x63, 0x88, 0xe0, 0x73, 0xc4, 0xd3, 0xde, + 0xfa, 0x1d, 0x48, 0x7a, 0x00, 0xd6, 0x95, 0x0b, 0x42, 0x85, 0x97, 0xb9, 0x53, 0x64, 0x41, 0x81, + 0xc5, 0x61, 0x40, 0x0a, 0x02, 0xc2, 0xdf, 0x1a, 0xd2, 0xe2, 0x6f, 0x2c, 0x9c, 0x02, 0x01, 0xcc, + 0x36, 0xf4, 0x80, 0x00, 0xfa, 0x7b, 0xba, 0x7d, 0x93, 0xc9, 0xf5, 0xe1, 0x22, 0xc0, 0x1b, 0x96, + 0xbd, 0xa0, 0x26, 0x30, 0x49, 0xed, 0x54, 0x5b, 0xf0, 0xbe, 0xa0, 0xe1, 0x1c, 0xb7, 0x8d, 0x50, + 0xfe, 0xeb, 0xa8, 0xb6, 0x74, 0x80, 0xb6, 0xf4, 0xef, 0x0d, 0x6a, 0x0b, 0xdf, 0x6e, 0xb7, 0x2b, + 0xda, 0xf2, 0x2c, 0xde, 0x10, 0x33, 0x75, 0x50, 0x85, 0x49, 0xf0, 0xb6, 0x7e, 0x01, 0xca, 0x09, + 0xea, 0x40, 0xce, 0x8b, 0x79, 0xed, 0x23, 0x4f, 0x81, 0x4a, 0x43, 0x1d, 0x6a, 0x08, 0x84, 0x30, + 0x64, 0xa0, 0xf5, 0x5b, 0x28, 0x81, 0x80, 0xbe, 0xa6, 0x1a, 0x73, 0x03, 0x35, 0xa6, 0x2d, 0xa4, + 0x31, 0x5e, 0xc8, 0x04, 0x14, 0x8d, 0x81, 0x30, 0xb0, 0x5d, 0xd1, 0x98, 0xb6, 0xe1, 0x1a, 0x73, + 0x94, 0x6a, 0xcc, 0x51, 0xd0, 0x96, 0x66, 0xe1, 0x48, 0xdc, 0x6d, 0x1c, 0xe7, 0x69, 0xf0, 0x77, + 0x3b, 0x79, 0x35, 0x06, 0x6a, 0x79, 0xed, 0x2e, 0x37, 0x3f, 0xcb, 0x70, 0xc3, 0xa1, 0x46, 0x51, + 0xf5, 0xf4, 0xf8, 0x26, 0x91, 0x3e, 0xb0, 0x96, 0x5b, 0x1c, 0xf7, 0x00, 0x3d, 0x38, 0xe9, 0x57, + 0x89, 0xbf, 0xe7, 0xaa, 0xfe, 0x62, 0x27, 0xee, 0x67, 0x91, 0x6b, 0x86, 0x7a, 0x6e, 0xdb, 0x6f, + 0x28, 0xc3, 0x4d, 0xc2, 0xd5, 0x29, 0x10, 0xcd, 0xf1, 0x77, 0xd1, 0x09, 0x1f, 0xb0, 0x8f, 0x24, + 0xd7, 0x20, 0x89, 0xa8, 0x07, 0xbc, 0xc0, 0x95, 0xef, 0x39, 0x08, 0x53, 0x6e, 0x4d, 0x39, 0xfe, + 0xec, 0x2a, 0xe9, 0xc7, 0x29, 0x6f, 0xc7, 0xc9, 0xed, 0x80, 0x29, 0xf7, 0xb7, 0x7e, 0x1d, 0x54, + 0x85, 0x0b, 0xa4, 0xc2, 0x4b, 0xa1, 0x38, 0xe5, 0xed, 0x38, 0xe5, 0x1d, 0x23, 0xbd, 0xad, 0x5f, + 0x21, 0xd7, 0xa7, 0xc0, 0x2e, 0x81, 0xe7, 0x06, 0xd2, 0x6a, 0x38, 0x35, 0x38, 0xeb, 0xde, 0x49, + 0xd0, 0xab, 0x15, 0xa0, 0x68, 0x77, 0x30, 0xf5, 0x5e, 0x52, 0xd7, 0xfa, 0x05, 0xf6, 0xff, 0xae, + 0x55, 0x06, 0xeb, 0xab, 0x27, 0x2d, 0xf8, 0xc2, 0xb0, 0xc2, 0x3a, 0x83, 0xa7, 0x24, 0x4c, 0x34, + 0x86, 0xe7, 0x78, 0xa5, 0x7e, 0x45, 0x0e, 0xb3, 0xbc, 0x3e, 0xfe, 0xcf, 0xfb, 0xff, 0x95, 0xb7, + 0xf2, 0xa3, 0x35, 0x9d, 0x62, 0xd2, 0x86, 0xfc, 0xb3, 0xf5, 0x26, 0xb1, 0x0e, 0xe7, 0x8b, 0x0d, + 0xfd, 0x38, 0xad, 0xf6, 0xb6, 0xdf, 0xaa, 0xc5, 0x03, 0xf1, 0x77, 0x82, 0xdf, 0xaf, 0x87, 0xca, + 0x27, 0x83, 0xe5, 0x93, 0x4a, 0xa3, 0x77, 0x74, 0xca, 0x37, 0xd4, 0x9b, 0x57, 0x19, 0x75, 0x45, + 0xa5, 0x0e, 0x67, 0x41, 0x49, 0x49, 0x81, 0xb3, 0xa8, 0xac, 0x54, 0xb7, 0xd6, 0x56, 0xb8, 0xf6, + 0x19, 0x9d, 0xb5, 0xa0, 0xa8, 0xa4, 0x70, 0x1d, 0x93, 0xed, 0x2c, 0x58, 0xfb, 0xcc, 0x83, 0x8e, + 0xc2, 0xf5, 0x1b, 0x0a, 0x4b, 0x9d, 0x00, 0xe4, 0x4b, 0x9c, 0x4c, 0xf6, 0x32, 0x53, 0xaa, 0xae, + 0x70, 0xf3, 0xda, 0xc2, 0x72, 0x6c, 0xcf, 0xe4, 0x1b, 0x53, 0x32, 0xf2, 0xd3, 0xb2, 0xf3, 0x99, + 0x94, 0x32, 0xbe, 0x64, 0x9d, 0xae, 0xb4, 0xcc, 0xa9, 0x5b, 0x57, 0xe8, 0x2c, 0xb4, 0x6f, 0x28, + 0x2a, 0x2d, 0xd4, 0x3d, 0x5d, 0x86, 0x45, 0x7b, 0xd1, 0xc6, 0x42, 0x26, 0x39, 0xc5, 0xbc, 0x4c, + 0x67, 0x2f, 0x5c, 0x5b, 0x52, 0x50, 0xb4, 0xa1, 0xe0, 0xe9, 0x92, 0x42, 0x66, 0x29, 0xd4, 0x95, + 0x94, 0x15, 0xac, 0x2b, 0xb4, 0x0f, 0x03, 0x2f, 0x2b, 0xdd, 0x58, 0x50, 0x52, 0xb4, 0x4e, 0xb7, + 0x32, 0x3b, 0x9b, 0x99, 0x51, 0xca, 0x97, 0x94, 0x24, 0x30, 0x85, 0x9b, 0x9d, 0x73, 0x1e, 0xd6, + 0x19, 0x4b, 0x9d, 0x45, 0xce, 0x2d, 0xba, 0x22, 0x07, 0x1d, 0xc3, 0x5e, 0xb8, 0x9e, 0x2f, 0x29, + 0xb0, 0xeb, 0xac, 0x40, 0x27, 0x00, 0xec, 0x3a, 0xc7, 0x96, 0x0d, 0x25, 0x45, 0xa5, 0xcf, 0x44, + 0x05, 0x5b, 0x9b, 0x0b, 0xec, 0x4e, 0x5d, 0x99, 0x55, 0x57, 0x5e, 0xe0, 0xb4, 0x85, 0xfa, 0xac, + 0x2b, 0x82, 0x81, 0x9c, 0x65, 0xf6, 0x2d, 0xc3, 0x3b, 0x98, 0x0a, 0x37, 0x20, 0x0c, 0x24, 0x50, + 0xb6, 0x96, 0xca, 0xc0, 0x01, 0x2d, 0x1d, 0x58, 0xdc, 0x04, 0x22, 0x08, 0xa1, 0x03, 0x34, 0xeb, + 0xca, 0x0a, 0x15, 0x44, 0x20, 0x2e, 0xc0, 0x5e, 0x54, 0xaa, 0x9b, 0xcd, 0x38, 0xad, 0xce, 0xf2, + 0x87, 0x75, 0x59, 0x85, 0x05, 0xeb, 0xa8, 0xcc, 0x78, 0x7b, 0x61, 0x08, 0xdf, 0x86, 0x82, 0x72, + 0x10, 0x93, 0xad, 0x80, 0x77, 0x38, 0x0b, 0xd7, 0xcd, 0x62, 0xd2, 0x92, 0x57, 0xa2, 0xb0, 0x57, + 0x58, 0x1e, 0x60, 0x74, 0xba, 0x27, 0xa7, 0x97, 0xe7, 0x45, 0x31, 0x4f, 0x4e, 0x5f, 0xa5, 0x7b, + 0xf0, 0x11, 0xdd, 0xf4, 0x55, 0x79, 0xba, 0x87, 0xe1, 0xaf, 0x4e, 0xb7, 0x78, 0xba, 0x43, 0x37, + 0x63, 0xfa, 0xe6, 0x84, 0x47, 0xa2, 0x98, 0xd4, 0xa2, 0x8d, 0x45, 0x0e, 0x9c, 0x8f, 0xa7, 0xb7, + 0xe8, 0x12, 0x99, 0x15, 0xa6, 0x65, 0xb7, 0x04, 0x53, 0x56, 0xbe, 0xb6, 0x6c, 0x5d, 0x61, 0x90, + 0x2e, 0xbe, 0x14, 0x58, 0x2a, 0xdb, 0x58, 0x68, 0x47, 0xd9, 0xe9, 0x0a, 0xed, 0xf6, 0x32, 0xbb, + 0x43, 0x67, 0x2d, 0xe3, 0x4b, 0xd7, 0x45, 0x31, 0x45, 0xc1, 0x0e, 0xd0, 0x12, 0x67, 0x6f, 0x43, + 0xc1, 0xfa, 0xa2, 0xb5, 0xcc, 0xac, 0x59, 0xb3, 0x99, 0x44, 0xfd, 0x9c, 0xb9, 0xf3, 0x1e, 0x9a, + 0xbf, 0x60, 0xa1, 0xa1, 0xe0, 0xe9, 0xb5, 0xeb, 0x0a, 0xad, 0x8c, 0xb1, 0x74, 0x1d, 0xca, 0xca, + 0x69, 0x2f, 0x58, 0x5b, 0x38, 0x4b, 0xc7, 0xcc, 0xc6, 0x69, 0x9b, 0x5d, 0x52, 0x84, 0x53, 0x38, + 0xcb, 0xb1, 0xc5, 0xc1, 0x2c, 0xcb, 0x7e, 0xdc, 0x30, 0x7f, 0x7e, 0xe2, 0xc3, 0xc0, 0xbc, 0xae, + 0xdc, 0x0e, 0x13, 0x05, 0xec, 0x6d, 0x2c, 0x2b, 0xe1, 0x37, 0x14, 0xc2, 0x5c, 0x3b, 0xd6, 0xda, + 0x8b, 0xca, 0x41, 0xaa, 0xcc, 0x52, 0x90, 0xc1, 0x06, 0xca, 0x3b, 0x63, 0x4c, 0x1b, 0x3e, 0xd7, + 0xa9, 0x85, 0x4f, 0xf3, 0xeb, 0x99, 0xc4, 0xcd, 0x4c, 0x90, 0x6c, 0x9c, 0x35, 0x40, 0xec, 0x2c, + 0xdc, 0xa0, 0xb3, 0x15, 0x38, 0x80, 0x0b, 0x07, 0x5f, 0x5e, 0x5e, 0x66, 0x07, 0x41, 0xe9, 0xac, + 0x85, 0x05, 0x4e, 0x90, 0xa2, 0x43, 0x37, 0x7d, 0x73, 0x14, 0x93, 0x0c, 0x23, 0x96, 0x3e, 0xc8, + 0x3b, 0x28, 0x77, 0x1b, 0x86, 0xc8, 0xb5, 0xd4, 0x69, 0xa7, 0xaa, 0x00, 0xaa, 0x5b, 0xb4, 0xae, + 0x50, 0x57, 0xa0, 0x0b, 0xb6, 0x71, 0xc0, 0x04, 0x83, 0xd4, 0x66, 0x31, 0xc9, 0x4e, 0x40, 0x5e, + 0x8e, 0x08, 0x15, 0x42, 0xf3, 0xed, 0x30, 0x45, 0x33, 0x12, 0x74, 0x20, 0xd1, 0xf2, 0xcd, 0x85, + 0x4c, 0x1a, 0xd5, 0x6f, 0x9d, 0xb3, 0x4c, 0x87, 0x2a, 0x88, 0x13, 0xba, 0xbe, 0x50, 0x37, 0x77, + 0x16, 0x13, 0x94, 0x15, 0x4e, 0x32, 0x95, 0x22, 0x13, 0x35, 0x75, 0xaa, 0x2e, 0x5b, 0xa9, 0x0d, + 0xe9, 0x5a, 0xb0, 0x6a, 0x2a, 0x56, 0x65, 0x14, 0x6c, 0x2c, 0xd4, 0x6d, 0x29, 0xe3, 0x75, 0x6b, + 0xcb, 0xca, 0x8b, 0x00, 0xe1, 0xa0, 0xd0, 0x10, 0xb7, 0xd3, 0x56, 0xa8, 0xb3, 0x83, 0x2c, 0x67, + 0xea, 0xa8, 0x48, 0xe1, 0x4b, 0xa9, 0x9f, 0xa9, 0x03, 0xfd, 0x1b, 0x2a, 0x65, 0x44, 0x15, 0xd2, + 0xce, 0x22, 0xe0, 0x1d, 0x26, 0xa3, 0x0c, 0xcc, 0x07, 0xe7, 0x04, 0x50, 0x94, 0x83, 0xb6, 0x15, + 0x29, 0x8a, 0x09, 0xe4, 0x23, 0x44, 0xb1, 0xab, 0xc2, 0x8d, 0x45, 0x6b, 0x0b, 0x7f, 0x1a, 0x15, + 0xc5, 0x0c, 0x9d, 0x2b, 0x4b, 0x91, 0xdd, 0xc9, 0xc3, 0xbc, 0x3b, 0xa8, 0x2a, 0x33, 0x66, 0x10, + 0xa5, 0x43, 0x97, 0xb2, 0x32, 0x6b, 0xf9, 0x03, 0xc9, 0xcb, 0x57, 0x3e, 0x90, 0x6a, 0x5c, 0x8e, + 0x84, 0xd9, 0x0b, 0x11, 0xc3, 0xac, 0x21, 0x36, 0x5b, 0x58, 0x4a, 0xe5, 0x97, 0x3c, 0x27, 0x11, + 0x78, 0x28, 0x2d, 0x0c, 0xea, 0x75, 0x48, 0xe7, 0x96, 0x99, 0x37, 0xce, 0xd3, 0x15, 0xac, 0x5b, + 0x87, 0xc8, 0x1e, 0xd6, 0x4d, 0x9b, 0xee, 0x98, 0xc6, 0x64, 0x07, 0x9d, 0x02, 0x76, 0x2e, 0x07, + 0x38, 0x3c, 0x83, 0xd2, 0x3a, 0x9e, 0x51, 0x94, 0x10, 0x66, 0x6f, 0x96, 0x2e, 0x15, 0x0d, 0x1f, + 0x9e, 0x50, 0xf7, 0xe7, 0xce, 0x51, 0x9c, 0x09, 0xd5, 0xb1, 0x87, 0xa3, 0x98, 0x49, 0x4f, 0xce, + 0xd5, 0x6f, 0x30, 0x27, 0xaf, 0x58, 0x96, 0x02, 0x4f, 0x0b, 0x16, 0xe9, 0x37, 0x4c, 0x7a, 0x32, + 0x71, 0xc3, 0xc3, 0xba, 0x10, 0x2b, 0x43, 0x39, 0x1a, 0x22, 0xd3, 0xa7, 0xf9, 0x22, 0xa0, 0x77, + 0x59, 0xaa, 0x6e, 0x43, 0x91, 0x63, 0x43, 0x81, 0x73, 0xad, 0x6d, 0x56, 0x14, 0xf0, 0x50, 0xba, + 0x11, 0x06, 0x07, 0x66, 0x0b, 0x4a, 0x42, 0x3a, 0x32, 0x68, 0xcb, 0x21, 0x4f, 0x96, 0x4a, 0x85, + 0x45, 0xa9, 0x2d, 0xd8, 0x08, 0x20, 0xaa, 0x96, 0xe9, 0x85, 0xa5, 0x60, 0x39, 0x25, 0x40, 0x7f, + 0x99, 0x53, 0x51, 0x9c, 0xa0, 0x8f, 0x33, 0x15, 0xac, 0xb5, 0xa1, 0xff, 0xa2, 0xce, 0x70, 0x98, + 0x3d, 0x0c, 0x25, 0x0c, 0x14, 0xe9, 0x61, 0x5d, 0x36, 0xbf, 0x76, 0x2d, 0x48, 0xc5, 0x0a, 0x9e, + 0x0a, 0x34, 0xb2, 0x14, 0xe6, 0x09, 0x24, 0xf6, 0x5c, 0x21, 0xd8, 0x9f, 0x22, 0xc2, 0x41, 0x4d, + 0x2b, 0x2b, 0x2f, 0x2c, 0x55, 0x74, 0x68, 0xfa, 0x66, 0x54, 0x80, 0xa7, 0x41, 0xf1, 0xca, 0x41, + 0x24, 0x85, 0xe0, 0x50, 0xa0, 0x07, 0x93, 0xa3, 0xa8, 0x70, 0x56, 0xb2, 0x89, 0x59, 0xb1, 0x32, + 0x2d, 0x1b, 0xcc, 0x25, 0x31, 0x88, 0x23, 0xbb, 0xd0, 0x0e, 0x26, 0x8e, 0xfa, 0x0e, 0x62, 0x5e, + 0x57, 0x54, 0xba, 0x9e, 0x2a, 0x01, 0x45, 0x05, 0x4e, 0x08, 0x24, 0x51, 0x52, 0x02, 0xd5, 0x0a, + 0x2e, 0xf0, 0x20, 0x45, 0x4e, 0x6c, 0xe7, 0xd4, 0x4d, 0x5f, 0x07, 0xde, 0xc3, 0x59, 0xe8, 0x48, + 0x98, 0x09, 0xae, 0x6b, 0x53, 0x29, 0xd5, 0xf4, 0x0d, 0x45, 0xeb, 0x6d, 0x4e, 0x9d, 0xb3, 0xe0, + 0x99, 0x42, 0xd0, 0xfc, 0xd2, 0xf5, 0x85, 0xf6, 0x59, 0x51, 0x21, 0xa5, 0xb7, 0x22, 0x4d, 0x20, + 0xb7, 0x67, 0x86, 0xa8, 0xff, 0x52, 0xfc, 0xab, 0xb3, 0x17, 0x40, 0x4b, 0xea, 0xf3, 0x0b, 0xd7, + 0x81, 0x2c, 0xa7, 0x02, 0xe3, 0x40, 0xf2, 0xfa, 0x52, 0x6a, 0xa9, 0xa0, 0xfa, 0x76, 0x3b, 0x8f, + 0xa6, 0x76, 0xa7, 0x27, 0x0d, 0xf9, 0xc4, 0x59, 0xcc, 0x63, 0x85, 0xf6, 0xd2, 0xc2, 0x92, 0xd9, + 0xa6, 0xb2, 0x75, 0x3c, 0x98, 0x7e, 0x50, 0x2d, 0x0b, 0x74, 0xcf, 0x14, 0x6e, 0x51, 0xf4, 0x11, + 0x30, 0x95, 0xe2, 0x13, 0xe8, 0x14, 0x3f, 0x8b, 0x59, 0x95, 0x66, 0xcc, 0x0a, 0xba, 0x8a, 0xdc, + 0xe4, 0xac, 0x15, 0xcb, 0x56, 0xa4, 0x83, 0x20, 0x91, 0xdd, 0x21, 0x4e, 0xa3, 0xb0, 0x74, 0xad, + 0x7d, 0x4b, 0xb9, 0x32, 0x8a, 0xe2, 0x33, 0xc0, 0x3a, 0x66, 0x82, 0x29, 0xf3, 0x56, 0x2b, 0x38, + 0x89, 0x2d, 0xa8, 0x23, 0x4f, 0x17, 0xda, 0xc0, 0x40, 0xa3, 0x98, 0xc7, 0x41, 0x7c, 0x56, 0xf0, + 0xe7, 0xa0, 0xb5, 0x6b, 0x79, 0x3b, 0x2c, 0x23, 0x4c, 0x4a, 0x6a, 0x62, 0xa2, 0x5e, 0x71, 0xc8, + 0xd0, 0xc5, 0xce, 0xaf, 0xa5, 0x08, 0xfe, 0x1d, 0x53, 0x51, 0x43, 0x15, 0x10, 0x27, 0x0a, 0x64, + 0x8e, 0xb5, 0xa0, 0x30, 0x20, 0x3b, 0x14, 0x05, 0x20, 0xa7, 0xae, 0xda, 0x59, 0x06, 0x93, 0x5c, + 0xb2, 0x8e, 0x01, 0x17, 0x4b, 0x35, 0x1b, 0xd4, 0x19, 0x1c, 0x7a, 0x81, 0x93, 0x1a, 0x83, 0x91, + 0x9a, 0x05, 0xba, 0xee, 0x87, 0xd1, 0x24, 0xb2, 0xc0, 0x62, 0x60, 0x52, 0xd7, 0x31, 0x2b, 0xa1, + 0xd3, 0x86, 0x82, 0xd2, 0x2d, 0x3a, 0xe3, 0x42, 0xb0, 0x3d, 0x74, 0x6c, 0xe0, 0x00, 0xa6, 0x06, + 0xd7, 0x08, 0x5c, 0x11, 0x1e, 0x98, 0x5e, 0x0e, 0xeb, 0xc1, 0x54, 0x20, 0x95, 0xba, 0x55, 0x73, + 0x72, 0xd6, 0x4a, 0xe6, 0x96, 0x50, 0x56, 0x3e, 0xfe, 0xb8, 0xce, 0x94, 0xbc, 0xe2, 0x09, 0x9d, + 0xe5, 0xf1, 0xe5, 0x39, 0x26, 0x63, 0xf6, 0x54, 0x46, 0x99, 0x41, 0x65, 0x6e, 0x41, 0x4d, 0x0a, + 0x4a, 0x83, 0xfe, 0x9f, 0xc9, 0x80, 0x99, 0xbf, 0xcd, 0x3e, 0xca, 0xec, 0x0f, 0xeb, 0x1e, 0xe7, + 0xe9, 0x3a, 0x19, 0x74, 0xdc, 0x74, 0x95, 0x5e, 0x61, 0xc9, 0x66, 0xd2, 0xec, 0x05, 0x1b, 0xc0, + 0x69, 0x5b, 0xad, 0x85, 0x76, 0xe6, 0xa7, 0x3f, 0xfd, 0x29, 0x63, 0x46, 0x17, 0xa8, 0x18, 0xc7, + 0xe6, 0x85, 0x0b, 0x86, 0xac, 0xff, 0x8a, 0x86, 0xa6, 0x94, 0x94, 0x39, 0x0a, 0x6f, 0xad, 0x80, + 0xca, 0xcc, 0x65, 0x2b, 0x0b, 0xac, 0x43, 0xb7, 0xa9, 0x08, 0x17, 0xcf, 0x42, 0x87, 0xb3, 0xa8, + 0x54, 0xd1, 0x0a, 0x5c, 0x94, 0x1d, 0xc3, 0x68, 0x9c, 0x9f, 0x08, 0xb6, 0x56, 0x60, 0x1f, 0xb6, + 0x14, 0x44, 0x31, 0x4b, 0xc1, 0x69, 0x3f, 0x53, 0x5e, 0x56, 0x84, 0xae, 0xa5, 0x8c, 0x47, 0xe3, + 0x50, 0x28, 0x48, 0x2e, 0x81, 0x79, 0xa2, 0xde, 0x47, 0x31, 0x50, 0xfc, 0x17, 0xa6, 0xc4, 0x42, + 0xb3, 0x18, 0xba, 0xc4, 0x31, 0xf4, 0x03, 0x85, 0x71, 0x10, 0xe3, 0x98, 0xde, 0x50, 0x3e, 0xb6, + 0xe0, 0xf7, 0x7b, 0x6f, 0x0c, 0xc2, 0xb0, 0x9e, 0x0d, 0x76, 0xc7, 0xd8, 0x4b, 0x1d, 0x0c, 0xa9, + 0x70, 0x2b, 0x08, 0xa3, 0xa4, 0x44, 0xf8, 0x24, 0xc1, 0xe7, 0xd8, 0x51, 0x86, 0xb1, 0xc2, 0xe7, + 0x7d, 0xf8, 0x7c, 0x04, 0x9f, 0xbf, 0xc3, 0x67, 0x73, 0x1b, 0xc3, 0xa4, 0x7d, 0xc6, 0x30, 0x2f, + 0xc1, 0x77, 0xc5, 0x15, 0x86, 0x69, 0xb8, 0x0c, 0xf1, 0x56, 0x3b, 0xc3, 0xd4, 0x42, 0xb9, 0xe9, + 0x12, 0xc3, 0xd4, 0x00, 0x0c, 0x0f, 0x9e, 0x9e, 0xbd, 0xc8, 0x30, 0xb1, 0x50, 0x1e, 0xb8, 0xc0, + 0x30, 0x9f, 0xc1, 0xb7, 0x3a, 0x08, 0x1f, 0x0f, 0x9f, 0xb7, 0xe0, 0xd3, 0x0d, 0xb0, 0x71, 0xd0, + 0xe6, 0xef, 0x9f, 0x2b, 0xf0, 0xff, 0xeb, 0x73, 0x12, 0xc6, 0x49, 0x3c, 0x03, 0x74, 0xc1, 0xc7, + 0x0c, 0x9f, 0xa7, 0xe0, 0x53, 0x0e, 0x9f, 0x4a, 0xf8, 0xbc, 0x06, 0x9f, 0x77, 0xe1, 0xb3, 0x13, + 0x3e, 0x6e, 0xf8, 0xb4, 0xc0, 0xa7, 0x1d, 0x3e, 0x7e, 0xf8, 0x30, 0xff, 0x02, 0xde, 0xe0, 0xa3, + 0x83, 0x4f, 0x22, 0x7c, 0x92, 0xe0, 0x63, 0x86, 0xcf, 0x53, 0xf0, 0x29, 0x87, 0x4f, 0x25, 0x7c, + 0x5e, 0x83, 0xcf, 0xbb, 0xf0, 0xd9, 0x09, 0x1f, 0x37, 0x7c, 0x5a, 0xe0, 0xd3, 0x0e, 0x1f, 0x3f, + 0xde, 0x70, 0x38, 0x0b, 0xfd, 0xe1, 0xa3, 0x3b, 0x8b, 0x57, 0x59, 0x19, 0x26, 0x9f, 0xc6, 0x41, + 0xcc, 0xbd, 0xb7, 0x1e, 0x67, 0xd9, 0x0b, 0xd1, 0xc9, 0xe4, 0xaf, 0x75, 0x30, 0x49, 0x00, 0x74, + 0x94, 0x17, 0x95, 0xf2, 0xe5, 0x0c, 0x6e, 0x5f, 0x15, 0xad, 0x73, 0x32, 0xf8, 0x5f, 0xa6, 0xac, + 0x87, 0x6f, 0xfc, 0xaf, 0x47, 0x60, 0x4a, 0xf3, 0x13, 0x17, 0xe6, 0x3b, 0x8b, 0xd6, 0x3e, 0xe3, + 0xc8, 0x5f, 0x0b, 0x4e, 0x06, 0x22, 0x46, 0xe6, 0x2f, 0x83, 0x15, 0x6b, 0x41, 0x43, 0x9f, 0x06, + 0x7f, 0x46, 0xff, 0xc7, 0x92, 0x20, 0xac, 0xc8, 0x61, 0x67, 0x0e, 0xe0, 0x58, 0xe5, 0x45, 0xce, + 0x7c, 0x47, 0x49, 0x61, 0x61, 0x79, 0x7e, 0x41, 0xe9, 0xba, 0xfc, 0x67, 0x79, 0x28, 0x96, 0x95, + 0xe6, 0x83, 0x2b, 0xc1, 0x75, 0xc8, 0xc1, 0xe0, 0x7f, 0x5c, 0xf2, 0x7f, 0xb4, 0x99, 0xf5, 0x74, + 0x91, 0xd3, 0xa1, 0x9f, 0xcf, 0x4c, 0xfc, 0x11, 0x4d, 0xd7, 0x3a, 0x9e, 0x2b, 0xb4, 0x97, 0x31, + 0xf3, 0x7f, 0x44, 0xd3, 0x92, 0xb2, 0xb2, 0x72, 0xe6, 0x99, 0x1f, 0xd1, 0x70, 0x1d, 0xac, 0xee, + 0x4c, 0xcd, 0x8f, 0xa4, 0x13, 0x96, 0xce, 0xe6, 0x1f, 0xd1, 0x14, 0x8d, 0x8f, 0xf9, 0xf4, 0x47, + 0x34, 0xc4, 0x39, 0xf8, 0xd7, 0x8f, 0x68, 0x87, 0x73, 0xf6, 0xd9, 0x8f, 0x68, 0x67, 0xdf, 0x90, + 0x8f, 0x4d, 0xaf, 0xfc, 0x88, 0xa6, 0x1b, 0xca, 0xd6, 0x39, 0x98, 0xaf, 0x7f, 0x44, 0xc3, 0x02, + 0xc7, 0xda, 0xa2, 0x22, 0xc6, 0xf7, 0x23, 0x5a, 0x3a, 0xd6, 0x16, 0x94, 0x32, 0x7e, 0x68, 0x08, + 0x64, 0xd8, 0xd6, 0x5a, 0x99, 0x81, 0x5b, 0x8f, 0xb7, 0x26, 0x79, 0xdb, 0x2d, 0x48, 0x70, 0x2e, + 0x7f, 0x32, 0x08, 0x01, 0xe7, 0xb3, 0x9e, 0xb9, 0x7f, 0xb0, 0x1c, 0x64, 0x26, 0x51, 0x81, 0xa0, + 0xe7, 0x79, 0xe6, 0xd6, 0x63, 0x08, 0xa1, 0x6b, 0x10, 0x12, 0x44, 0xf8, 0xf1, 0x20, 0x04, 0x95, + 0xb5, 0xb4, 0x8c, 0x19, 0x18, 0xde, 0x0b, 0xe6, 0x50, 0x27, 0xdc, 0x82, 0x14, 0x3a, 0xca, 0x99, + 0xf8, 0xc1, 0x62, 0x19, 0xef, 0x84, 0xf0, 0x74, 0xbd, 0x83, 0x99, 0x21, 0x0c, 0x41, 0xa3, 0x80, + 0x66, 0x0e, 0x82, 0x70, 0xde, 0xe6, 0x0d, 0x69, 0x01, 0xc5, 0xc5, 0x83, 0xc5, 0x20, 0xe1, 0x66, + 0x80, 0x38, 0x0a, 0x9d, 0xf9, 0xb0, 0x2e, 0xe5, 0x5b, 0xcb, 0x99, 0x9f, 0x41, 0x11, 0x1f, 0xd1, + 0x9e, 0x98, 0xad, 0x43, 0x0a, 0x21, 0x5e, 0xde, 0x1e, 0x0a, 0x0b, 0x72, 0x73, 0xfa, 0xf6, 0x76, + 0x40, 0xbd, 0x7f, 0x28, 0x2c, 0x88, 0xfc, 0xfb, 0xa1, 0x30, 0xa4, 0xee, 0xe6, 0x50, 0x00, 0x52, + 0x83, 0xff, 0xa5, 0xcb, 0x2d, 0x40, 0x90, 0x42, 0x0d, 0xc0, 0x86, 0xc4, 0xe4, 0xcc, 0x7f, 0xbb, + 0xc0, 0x2d, 0x94, 0x3b, 0xf3, 0xd7, 0x03, 0xd5, 0xeb, 0xf9, 0xa2, 0x75, 0x4c, 0xe5, 0x4b, 0x60, + 0xf2, 0x8e, 0x7c, 0x1a, 0x79, 0xe6, 0x6f, 0x78, 0xda, 0xce, 0x18, 0x6b, 0x18, 0x06, 0xbe, 0x69, + 0x0b, 0xac, 0x87, 0x22, 0x86, 0xc4, 0x58, 0x66, 0x34, 0xaf, 0xde, 0xc2, 0x86, 0xd5, 0x4f, 0x6f, + 0x51, 0x70, 0x3c, 0x77, 0x07, 0xd8, 0xea, 0x80, 0x98, 0xaf, 0xb0, 0x84, 0x69, 0xbc, 0xa3, 0x66, + 0x6d, 0x59, 0x99, 0x7d, 0x1d, 0xa3, 0xfa, 0xf9, 0xed, 0xf0, 0xa7, 0x8b, 0xca, 0x1c, 0xf9, 0x4a, + 0xf2, 0x3a, 0x13, 0x2a, 0x21, 0x55, 0xca, 0x77, 0x96, 0x51, 0x95, 0x48, 0xfb, 0xb9, 0xe2, 0x94, + 0xa0, 0x08, 0x50, 0xa6, 0x10, 0x8a, 0xeb, 0x8a, 0xd6, 0x17, 0x39, 0x43, 0xf5, 0xdb, 0x01, 0x00, + 0x41, 0x85, 0xb3, 0x8c, 0x2f, 0x62, 0x26, 0xbf, 0x06, 0xec, 0x01, 0xc2, 0x82, 0xa7, 0x1d, 0x80, + 0xdd, 0x59, 0x98, 0x8f, 0x8b, 0x1f, 0x63, 0xfb, 0x05, 0xf0, 0x60, 0xc7, 0xb6, 0x55, 0xf0, 0xb4, + 0x51, 0x79, 0x7c, 0xed, 0x2d, 0x04, 0x96, 0xe6, 0x17, 0x31, 0xcf, 0xfe, 0x52, 0x79, 0x82, 0xfe, + 0x71, 0xbf, 0x52, 0x1e, 0x37, 0x33, 0x31, 0x6f, 0x07, 0xfb, 0xa0, 0xb3, 0x0d, 0x46, 0xd2, 0xcc, + 0x01, 0x04, 0x16, 0x94, 0x42, 0x66, 0x57, 0xfe, 0x6b, 0x14, 0x5b, 0x10, 0x29, 0x7d, 0x76, 0x94, + 0x63, 0x8b, 0x77, 0xe0, 0x19, 0x28, 0x29, 0x2f, 0x07, 0x0f, 0xfb, 0x9f, 0xf4, 0x19, 0x13, 0x5b, + 0x3b, 0x53, 0xf7, 0x6b, 0x4a, 0xe4, 0xda, 0xf2, 0x2d, 0xcc, 0x97, 0xca, 0x63, 0x29, 0x3e, 0xe3, + 0xb5, 0x73, 0x04, 0x6f, 0x28, 0x67, 0x72, 0x82, 0x8f, 0x05, 0x90, 0x55, 0x41, 0xb1, 0xe1, 0x9d, + 0x60, 0x2b, 0x78, 0x8e, 0xfc, 0x0d, 0x7d, 0x2e, 0x29, 0x2c, 0x65, 0x26, 0xfd, 0x06, 0x85, 0x81, + 0x2a, 0xe7, 0x84, 0x70, 0xe0, 0x15, 0x28, 0xd1, 0xec, 0x8c, 0xd9, 0xfc, 0x5b, 0x6c, 0x02, 0x61, + 0xc3, 0xdc, 0x7c, 0x0c, 0x8b, 0x99, 0x92, 0xed, 0xa8, 0x99, 0x76, 0x08, 0x8f, 0xf3, 0x41, 0xe1, + 0x19, 0x71, 0xb0, 0x18, 0x8a, 0x9a, 0x1d, 0x85, 0x8c, 0x77, 0x28, 0x94, 0xe9, 0xdd, 0x8e, 0x82, + 0x75, 0x3c, 0x43, 0x15, 0x25, 0xdf, 0x41, 0xd3, 0x23, 0x07, 0xf3, 0xd9, 0x7f, 0x04, 0xa1, 0x6b, + 0x01, 0x0c, 0x02, 0x2d, 0x2a, 0x5d, 0x57, 0xb8, 0x99, 0x59, 0xfe, 0x81, 0xa2, 0x6a, 0x4f, 0x43, + 0x31, 0x5f, 0x99, 0x48, 0x26, 0x3f, 0x08, 0xa3, 0xe3, 0xcf, 0xfc, 0x03, 0x43, 0x83, 0x95, 0x7c, + 0x8c, 0xc3, 0x99, 0x92, 0x0f, 0x61, 0x3d, 0x77, 0xd0, 0xa9, 0x56, 0xf4, 0xe2, 0xa5, 0x41, 0x00, + 0xd5, 0xa0, 0x9f, 0x63, 0x99, 0x36, 0xbd, 0xe7, 0xcf, 0xf0, 0xb4, 0x16, 0x03, 0x1c, 0xe6, 0x69, + 0x7c, 0xa4, 0x5a, 0x5b, 0x13, 0x7a, 0x42, 0xc3, 0x52, 0x7f, 0x84, 0xe2, 0x2e, 0xc3, 0x1c, 0x59, + 0xc1, 0x8e, 0xef, 0xc6, 0x87, 0x00, 0xb4, 0xf9, 0xa6, 0x21, 0x00, 0x05, 0xd5, 0x2b, 0x00, 0xb1, + 0x16, 0x38, 0xe7, 0xce, 0x19, 0x42, 0xc3, 0xdf, 0x6e, 0xc1, 0x90, 0x62, 0x50, 0x4c, 0x58, 0x16, + 0x37, 0x3b, 0x99, 0xda, 0x1d, 0x21, 0x30, 0x45, 0x9e, 0xf1, 0xf7, 0xa1, 0x45, 0x94, 0xd3, 0xfb, + 0xff, 0x64, 0x98, 0xb5, 0x90, 0xb2, 0x80, 0xa1, 0x95, 0xe0, 0x76, 0x83, 0x3d, 0x1f, 0x82, 0x29, + 0x80, 0xe3, 0x3b, 0xba, 0x4a, 0x4b, 0x4a, 0x84, 0xe3, 0x56, 0x51, 0x21, 0x61, 0x3b, 0x94, 0xa9, + 0x64, 0x87, 0xf7, 0xfa, 0x70, 0xd7, 0x6d, 0x60, 0xab, 0xbd, 0x6c, 0x43, 0x3e, 0xa4, 0xdd, 0xcc, + 0xaa, 0xdd, 0x4a, 0x02, 0xaf, 0xd0, 0xb1, 0x76, 0x6f, 0xb0, 0x44, 0xed, 0xb2, 0x14, 0x37, 0x25, + 0x26, 0xef, 0x0f, 0x82, 0xc0, 0x3e, 0x1d, 0x85, 0xf9, 0x98, 0xd2, 0x82, 0x36, 0xbe, 0x77, 0x10, + 0x26, 0x55, 0x09, 0x06, 0x15, 0x0b, 0xc1, 0xa6, 0x4f, 0x1d, 0x02, 0x9a, 0x95, 0x09, 0xa4, 0xd1, + 0x28, 0x1d, 0x00, 0xdf, 0x30, 0xa2, 0xfd, 0x29, 0xbd, 0x2f, 0x87, 0x4a, 0x0a, 0xb9, 0x87, 0xdd, + 0x43, 0xc6, 0xa3, 0x73, 0x34, 0xcb, 0x33, 0x04, 0xa2, 0x88, 0xf0, 0xf7, 0x1e, 0xd4, 0x46, 0x18, + 0x40, 0x41, 0x11, 0x73, 0x04, 0x46, 0x81, 0x04, 0x31, 0x1f, 0xe2, 0x5b, 0x65, 0x1c, 0x66, 0xc4, + 0x7f, 0x81, 0x37, 0x29, 0xdc, 0x00, 0xc3, 0x05, 0x07, 0xa6, 0xd9, 0x0d, 0x93, 0x02, 0x60, 0x47, + 0x01, 0x6a, 0x22, 0x10, 0x1e, 0x0c, 0xc3, 0x99, 0xd1, 0x47, 0x43, 0x26, 0xa7, 0xf4, 0x60, 0x6e, + 0x1e, 0x55, 0xac, 0x39, 0x58, 0x7c, 0xb0, 0x19, 0x47, 0x2b, 0xba, 0x55, 0x7e, 0xf2, 0xb8, 0x52, + 0x6d, 0x2f, 0xd8, 0x14, 0x02, 0x95, 0x03, 0xa8, 0x7c, 0xc3, 0x06, 0x10, 0x62, 0x61, 0x21, 0xb3, + 0xf7, 0x38, 0x25, 0x78, 0x08, 0x35, 0x2d, 0xb7, 0x43, 0xf2, 0x9d, 0x5b, 0xca, 0x0b, 0x19, 0xf9, + 0xdf, 0x82, 0xe1, 0x11, 0x02, 0x5f, 0x48, 0x1a, 0xfc, 0x9f, 0x02, 0x0f, 0xc8, 0x81, 0x22, 0x7a, + 0x6b, 0x19, 0xf3, 0xd7, 0x13, 0xff, 0x8e, 0x2b, 0x54, 0x8e, 0x96, 0x93, 0x0a, 0x01, 0xa5, 0x85, + 0x9b, 0xf2, 0x15, 0xcb, 0x74, 0x9f, 0x0a, 0x52, 0x5d, 0x08, 0x29, 0x07, 0x63, 0x3e, 0x1d, 0x2c, + 0xa1, 0x73, 0x7e, 0x05, 0x0a, 0x05, 0x73, 0x40, 0x47, 0x69, 0x5c, 0xed, 0x0e, 0x96, 0x94, 0xcd, + 0x01, 0x66, 0x55, 0x2b, 0x12, 0x85, 0x81, 0x3f, 0x53, 0xd9, 0x4a, 0x87, 0x43, 0x87, 0xf1, 0x96, + 0xf2, 0x08, 0x8b, 0x0e, 0xf3, 0x91, 0xf2, 0xb8, 0xa1, 0x0c, 0x5c, 0x66, 0xdd, 0xe0, 0xf3, 0xac, + 0xb5, 0x65, 0xe5, 0xe0, 0x4e, 0xc1, 0x67, 0x6d, 0x2a, 0xb0, 0x43, 0x00, 0x70, 0x62, 0x48, 0x15, + 0x8d, 0x84, 0x4e, 0x05, 0xb1, 0x81, 0x63, 0xf9, 0xf6, 0xd6, 0xe3, 0xac, 0xc2, 0x67, 0xf9, 0x82, + 0x12, 0xc6, 0x3f, 0x08, 0xa0, 0x6d, 0xaf, 0xdf, 0x1a, 0x79, 0xee, 0x1c, 0x67, 0xd9, 0xfc, 0x79, + 0x4c, 0xe0, 0x36, 0xc0, 0xac, 0x72, 0x3d, 0xa3, 0x3a, 0x73, 0x1b, 0x0c, 0x02, 0x8f, 0x42, 0x68, + 0x1b, 0x7b, 0x3b, 0xbc, 0x1c, 0xd6, 0xe5, 0x7f, 0xd7, 0x16, 0x56, 0x3c, 0x8c, 0xb0, 0xed, 0x05, + 0x54, 0x1b, 0x42, 0xc9, 0x4e, 0x7e, 0x22, 0x8d, 0xb9, 0x6f, 0x87, 0xea, 0x69, 0x14, 0x7e, 0x3b, + 0x74, 0x0e, 0x8d, 0xcb, 0x6f, 0x87, 0xce, 0xa5, 0x91, 0xfa, 0xed, 0xd0, 0x79, 0x34, 0x76, 0xbf, + 0x1d, 0xfa, 0x10, 0x8d, 0xe6, 0x6f, 0x87, 0xce, 0xa7, 0xf1, 0xfd, 0xed, 0xd0, 0x05, 0x34, 0xe2, + 0xbf, 0x1d, 0xba, 0x90, 0xe6, 0x00, 0xb7, 0x43, 0x0d, 0x34, 0x2b, 0xb8, 0x83, 0x8b, 0x44, 0x9a, + 0x28, 0xdc, 0x01, 0xd6, 0xd3, 0xdc, 0xe1, 0x0e, 0xf0, 0x1c, 0x9a, 0x4e, 0xdc, 0x01, 0x9e, 0x4b, + 0x33, 0x8c, 0x3b, 0xc0, 0xf3, 0x68, 0xd2, 0x71, 0x07, 0xf8, 0x21, 0x9a, 0x87, 0xdc, 0x01, 0x9e, + 0x4f, 0x53, 0x93, 0x3b, 0xc0, 0x0b, 0x68, 0xb6, 0x72, 0x07, 0x78, 0x21, 0x4d, 0x60, 0xee, 0x00, + 0x1b, 0x68, 0x4e, 0x73, 0xc7, 0xac, 0x24, 0xd2, 0x34, 0xe7, 0x0e, 0xb0, 0x9e, 0x66, 0x3e, 0x77, + 0x80, 0xe7, 0xd0, 0x64, 0xe8, 0x0e, 0xf0, 0x5c, 0x9a, 0x1f, 0xdd, 0x01, 0x9e, 0x47, 0x53, 0xa6, + 0x3b, 0xc0, 0x0f, 0xd1, 0x2c, 0xea, 0x0e, 0xf0, 0x7c, 0x9a, 0x58, 0xdd, 0x01, 0x5e, 0x40, 0x73, + 0xad, 0x3b, 0xc0, 0x0b, 0x69, 0xfa, 0x75, 0x07, 0xd8, 0x40, 0x33, 0xb2, 0x3b, 0xb4, 0x2c, 0x91, + 0x26, 0x69, 0x77, 0x80, 0xf5, 0xcc, 0x2c, 0x00, 0xe7, 0x83, 0x63, 0xde, 0xb8, 0xae, 0x68, 0x2e, + 0xb3, 0xe2, 0x1c, 0x16, 0x40, 0xdf, 0xb1, 0x50, 0xe5, 0xc5, 0x02, 0x1f, 0xac, 0x72, 0x9c, 0xa7, + 0xa5, 0x60, 0x1d, 0x1e, 0xc2, 0x29, 0xff, 0xd3, 0xdf, 0xff, 0x03, 0x02, 0xe9, 0xe6, 0x52, 0x10, + 0x73, 0x00, 0x00, +}; + +pub fn install(device: []u8, force_mbr: bool, partition_number: ?u32) !void { + if (@sizeOf(usize) != @sizeOf(u64)) return print_error_and_exit(InstallerError.not_64_bit); + + // Point to the second block where the GPT header might be + var do_gpt = false; + var gpt_header: *GPT.Header = undefined; + const lb_guesses = [_]u64{ 512, 4096 }; + var lb_size: u64 = 0; + + for (lb_guesses) |guess| { + gpt_header = @as(*GPT.Header, @ptrCast(@alignCast(&device[guess]))); + if (gpt_header.signature == gpt_header_signature) { + lb_size = guess; + do_gpt = !force_mbr; + if (force_mbr) { + gpt_header.* = std.mem.zeroes(GPT.Header); + } + break; + } + } + + const secondary_GPT_header: *GPT.Header = @ptrCast(@alignCast(&device[lb_size * gpt_header.alternate_LBA])); + if (do_gpt) { + //print("Installing to GPT. Logical block size of {}\nSecondary header at LBA 0x{x}\n", .{lb_size, gpt_header.alternate_LBA}); + if (secondary_GPT_header.signature != gpt_header_signature) { + return print_error_and_exit(InstallerError.secondary_GPT_header_invalid); + } + + //stdout_write("Secondary header valid\n"); + } else { + var mbr = true; + + // Do MBR sanity checks + + { + const hint = @as(*u8, @ptrCast(&device[446])); + if (hint.* != 0 and hint.* != 0x80) { + if (!force_mbr) mbr = false else hint.* = if (hint.* & 0x80 != 0) 0x80 else 0; + } + } + + { + const hint = @as(*u8, @ptrCast(&device[462])); + if (hint.* != 0 and hint.* != 0x80) { + if (!force_mbr) mbr = false else hint.* = if (hint.* & 0x80 != 0) 0x80 else 0; + } + } + + { + const hint = @as(*u8, @ptrCast(&device[478])); + if (hint.* != 0 and hint.* != 0x80) { + if (!force_mbr) mbr = false else hint.* = if (hint.* & 0x80 != 0) 0x80 else 0; + } + } + + { + const hint = @as(*u8, @ptrCast(&device[494])); + if (hint.* != 0 and hint.* != 0x80) { + if (!force_mbr) mbr = false else hint.* = if (hint.* & 0x80 != 0) 0x80 else 0; + } + } + + { + const hint = @as(*[8]u8, @ptrCast(&device[4])); + if (std.mem.eql(u8, hint, "_ECH_FS_")) { + if (!force_mbr) mbr = false else hint.* = std.mem.zeroes([8]u8); + } + } + + { + const hint = @as(*[4]u8, @ptrCast(&device[3])); + if (std.mem.eql(u8, hint, "NTFS")) { + if (!force_mbr) mbr = false else hint.* = std.mem.zeroes([4]u8); + } + } + + { + const hint = @as(*[5]u8, @ptrCast(&device[54])); + if (std.mem.eql(u8, hint[0..3], "FAT")) { + if (!force_mbr) mbr = false else hint.* = std.mem.zeroes([5]u8); + } + } + + { + const hint = @as(*[5]u8, @ptrCast(&device[82])); + if (std.mem.eql(u8, hint[0..3], "FAT")) { + if (!force_mbr) mbr = false else hint.* = std.mem.zeroes([5]u8); + } + } + + { + const hint = @as(*[5]u8, @ptrCast(&device[3])); + if (std.mem.eql(u8, hint, "FAT32")) { + if (!force_mbr) mbr = false else hint.* = std.mem.zeroes([5]u8); + } + } + + { + const hint = @as(*align(1) u16, @ptrCast(&device[1080])); + if (hint.* == 0xef53) { + if (!force_mbr) mbr = false else hint.* = 0; + } + } + + if (!mbr) return print_error_and_exit(InstallerError.invalid_partition_table); + } + + const stage2_size = hdd.len - 512; + const stage2_sections = div_roundup(stage2_size, 512); + var stage2_size_a = @as(u16, @intCast((stage2_sections / 2) * 512 + @as(u64, if (stage2_sections % 2 != 0) 512 else 0))); + const stage2_size_b = @as(u16, @intCast((stage2_sections / 2) * 512)); + + var stage2_loc_a: u64 = 512; + var stage2_loc_b = stage2_loc_a + stage2_size_a; + + if (do_gpt) { + if (partition_number != null) { + return error.TODO; + } else { + const partition_entry_count = gpt_header.partition_entry_count; + if (partition_entry_count == 0) return error.TODO; + const partition_entry_base_address = @intFromPtr(device.ptr) + (gpt_header.partition_entry_LBA * lb_size); + var partition_entry_address = partition_entry_base_address; + + var max_partition_entry_used: u64 = 0; + + var partition_entry_i: u64 = 0; + while (partition_entry_i < partition_entry_count) : (partition_entry_i += 1) { + const partition_entry = @as(*GPT.Entry, @ptrFromInt(partition_entry_address)); + defer partition_entry_address += gpt_header.partition_entry_size; + + if (partition_entry.unique_partition_guid0 != 0 or partition_entry.unique_partition_guid1 != 0) { + if (partition_entry_i > max_partition_entry_used) max_partition_entry_used = partition_entry_i; + } + } + + stage2_loc_a = (gpt_header.partition_entry_LBA + 32) * lb_size; + stage2_loc_a -= stage2_size_a; + stage2_loc_a &= ~(lb_size - 1); + + stage2_loc_b = (secondary_GPT_header.partition_entry_LBA + 32) * lb_size; + stage2_loc_b -= stage2_size_b; + stage2_loc_b &= ~(lb_size - 1); + + const partition_entry_per_lb_count = lb_size / gpt_header.partition_entry_size; + const new_partition_array_lba_size = stage2_loc_a / lb_size - gpt_header.partition_entry_LBA; + const new_partition_entry_count = new_partition_array_lba_size * partition_entry_per_lb_count; + + if (new_partition_entry_count <= max_partition_entry_used) { + return error.TODO; + } + + //print("New maximum count of partition entries: {}\n", .{new_partition_entry_count}); + var partition_i: usize = max_partition_entry_used + 1; + while (partition_i < new_partition_entry_count) : (partition_i += 1) { + const entry_offset = partition_i * gpt_header.partition_entry_size; + const primary_entry_base = gpt_header.partition_entry_LBA * lb_size + entry_offset; + const secondary_entry_base = secondary_GPT_header.partition_entry_LBA * lb_size + entry_offset; + @memset(device[primary_entry_base .. primary_entry_base + gpt_header.partition_entry_size], 0); + @memset(device[secondary_entry_base .. secondary_entry_base + gpt_header.partition_entry_size], 0); + } + + assert(gpt_header.partition_entry_count * @sizeOf(GPT.Entry) == gpt_header.partition_entry_count * gpt_header.partition_entry_size); + assert(secondary_GPT_header.partition_entry_count * @sizeOf(GPT.Entry) == secondary_GPT_header.partition_entry_count * secondary_GPT_header.partition_entry_size); + + gpt_header.partition_entry_array_CRC32 = crc32(@as([*]u8, @ptrFromInt(partition_entry_base_address))[0 .. new_partition_entry_count * gpt_header.partition_entry_size]); + gpt_header.partition_entry_count = @as(u32, @intCast(new_partition_entry_count)); + gpt_header.CRC32 = 0; + gpt_header.CRC32 = crc32(std.mem.asBytes(gpt_header)); + + secondary_GPT_header.partition_entry_array_CRC32 = gpt_header.partition_entry_array_CRC32; + secondary_GPT_header.partition_entry_count = @as(u32, @intCast(new_partition_entry_count)); + secondary_GPT_header.CRC32 = 0; + secondary_GPT_header.CRC32 = crc32(std.mem.asBytes(secondary_GPT_header)); + } + } else { + //stdout_write("Installing to MBR\n"); + } + + const original_timestamp = @as(*[6]u8, @ptrCast(&device[218])).*; + const original_partition_table = @as(*[70]u8, @ptrCast(&device[440])).*; + @memcpy(device[0..512], hdd[0..512]); + + { + const dst = device[stage2_loc_a .. stage2_loc_a + stage2_size_a]; + const src = hdd[512 .. 512 + stage2_size_a]; + @memcpy(dst, src); + } + + { + const size_left = stage2_size - stage2_size_a; + const dst = device[stage2_loc_b .. stage2_loc_b + size_left]; + const src = hdd[512 + stage2_size_a .. 512 + stage2_size_a + size_left]; + @memcpy(dst, src); + } + + @as(*align(1) u16, @ptrCast(&device[0x1a4 + 0])).* = stage2_size_a; + @as(*align(1) u16, @ptrCast(&device[0x1a4 + 2])).* = stage2_size_b; + @as(*align(1) u64, @ptrCast(&device[0x1a4 + 4])).* = stage2_loc_a; + @as(*align(1) u64, @ptrCast(&device[0x1a4 + 12])).* = stage2_loc_b; + + @as(*[6]u8, @ptrCast(&device[218])).* = original_timestamp; + @as(*[70]u8, @ptrCast(&device[440])).* = original_partition_table; +} diff --git a/src/bootloader/limine/main.zig b/src/bootloader/limine/main.zig new file mode 100644 index 0000000..88536a1 --- /dev/null +++ b/src/bootloader/limine/main.zig @@ -0,0 +1,332 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.LIMINE); + +const bootloader = @import("bootloader"); + +const limine = @import("limine"); + +const privileged = @import("privileged"); +const ACPI = privileged.ACPI; +const Mapping = privileged.Mapping; +const PageAllocator = privileged.PageAllocator; +const PhysicalAddress = lib.PhysicalAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const VirtualAddress = lib.VirtualAddress; +const stopCPU = privileged.arch.stopCPU; +const paging = privileged.arch.x86_64.paging; + +const writer = privileged.E9Writer{ .context = {} }; + +const Request = extern struct { + information: limine.BootloaderInfo.Request = .{ .revision = 0 }, + hhdm: limine.HHDM.Request = .{ .revision = 0 }, + framebuffer: limine.Framebuffer.Request = .{ .revision = 0 }, + smp: limine.SMPInfoRequest = .{ .revision = 0, .flags = .{ .x2apic = false } }, + memory_map: limine.MemoryMap.Request = .{ .revision = 0 }, + modules: limine.Module.Request = .{ .revision = 0 }, + rsdp: limine.RSDP.Request = .{ .revision = 0 }, + smbios: limine.SMBIOS.Request = .{ .revision = 0 }, + efi_system_table: limine.EFISystemTable.Request = .{ .revision = 0 }, + kernel_address: limine.KernelAddress.Request = .{ .revision = 0 }, +}; + +var request = Request{}; + +comptime { + @export(request, .{ .linkage = .Strong, .name = "request" }); +} + +pub fn panic(message: []const u8, _: ?*lib.StackTrace, _: ?usize) noreturn { + privileged.arch.disableInterrupts(); + + writer.writeAll("[PANIC] ") catch {}; + writer.writeAll(message) catch {}; + writer.writeByte('\n') catch {}; + + privileged.shutdown(.failure); +} + +pub const std_options = struct { + pub const log_level = lib.std.log.Level.debug; + + pub fn logFn(comptime level: lib.std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype) void { + _ = level; + // _ = level; + writer.writeByte('[') catch stopCPU(); + writer.writeAll(@tagName(scope)) catch stopCPU(); + writer.writeAll("] ") catch stopCPU(); + lib.format(writer, format, args) catch stopCPU(); + writer.writeByte('\n') catch stopCPU(); + } +}; + +const Filesystem = struct { + modules: []const limine.File, + + pub fn deinitialize(filesystem: *Filesystem) !void { + _ = filesystem; + } + + pub fn readFile(filesystem: *Filesystem, file_path: []const u8, file_buffer: []u8) ![]const u8 { + const module = try filesystem.getModule(file_path); + assert(file_buffer.len >= module.size); + @memcpy(file_buffer[0..module.size], module.getContent()); + return file_buffer; + } + + pub fn sneakFile(filesystem: *Filesystem, file_path: []const u8, size: usize) ![]const u8 { + _ = size; + const file = try filesystem.getModule(file_path); + return file.getContent(); + } + + fn getModule(filesystem: *Filesystem, file_path: []const u8) !*const limine.File { + for (filesystem.modules) |*module| { + const path = module.path[0..lib.length(module.path)]; + if (lib.equal(u8, file_path, path)) { + return module; + } + } + + return Error.file_not_found; + } + + pub fn getFileSize(filesystem: *Filesystem, file_path: []const u8) !u32 { + const file = try filesystem.getModule(file_path); + return @as(u32, @intCast(file.size)); + } + + pub fn getSectorSize(filesystem: *Filesystem) u16 { + _ = filesystem; + return lib.default_sector_size; + } +}; + +const MemoryMap = struct { + entries: []const limine.MemoryMap.Entry, + index: usize = 0, + + pub fn getEntryCount(memory_map: *const MemoryMap) u32 { + return @as(u32, @intCast(memory_map.entries.len)); + } + + pub fn next(memory_map: *MemoryMap) !?bootloader.MemoryMapEntry { + if (memory_map.index < memory_map.entries.len) { + const entry = memory_map.entries[memory_map.index]; + memory_map.index += 1; + + return .{ + .region = entry.region, + .type = switch (entry.type) { + .usable => .usable, + .framebuffer, .kernel_and_modules, .bootloader_reclaimable, .reserved, .acpi_reclaimable, .acpi_nvs => .reserved, + .bad_memory => @panic("Bad memory"), + }, + }; + } + + return null; + } +}; + +const Initialization = struct { + framebuffer: bootloader.Framebuffer, + memory_map: MemoryMap, + filesystem: Filesystem, + architecture: switch (lib.cpu.arch) { + .x86_64 => struct { + rsdp: *ACPI.RSDP.Descriptor1, + }, + else => @compileError("Architecture not supported"), + }, + + early_initialized: bool = false, + framebuffer_initialized: bool = false, + memory_map_initialized: bool = false, + filesystem_initialized: bool = false, + + pub fn ensureLoaderIsMapped(init: *Initialization, minimal_paging: privileged.arch.paging.Specific, page_allocator: PageAllocator, bootloader_information: *bootloader.Information) !void { + const Section = enum { + text, + rodata, + data, + }; + const physical_offset = request.kernel_address.response.?.physical_address; + const virtual_offset = request.kernel_address.response.?.virtual_address; + + inline for (comptime lib.enumValues(Section)) |section| { + const section_name = @tagName(section); + const section_start = @intFromPtr(@extern(*const u8, .{ .name = section_name ++ "_section_start" })); + const section_end = @intFromPtr(@extern(*const u8, .{ .name = section_name ++ "_section_end" })); + + const offset = section_start - virtual_offset; + const physical_address = PhysicalAddress.new(physical_offset + offset); + const virtual_address = VirtualAddress.new(section_start); + const size = section_end - section_start; + + log.debug("Trying to map {s}: 0x{x} -> 0x{x} for 0x{x} bytes...", .{ section_name, virtual_address.value(), physical_address.value(), size }); + + if (section == .text) { + const address = @intFromPtr(&ensureLoaderIsMapped); + assert(address >= section_start and address <= section_end); + } + + try minimal_paging.map(physical_address, virtual_address, size, switch (section) { + .text => .{ .write = false, .execute = true }, + .rodata => .{ .write = false, .execute = false }, + .data => .{ .write = true, .execute = false }, + }, page_allocator); + log.debug("Mapped {s}...", .{section_name}); + } + + _ = init; + _ = bootloader_information; + } + + pub fn ensureStackIsMapped(init: *Initialization, minimal_paging: paging.Specific, page_allocator: PageAllocator) !void { + _ = init; + const rsp = switch (lib.cpu.arch) { + .x86_64 => asm volatile ( + \\mov %rsp, %[result] + : [result] "=r" (-> u64), + ), + .aarch64 => @panic("TODO ensureStackIsMapped"), + else => @compileError("Architecture not supported"), + }; + + const memory_map = request.memory_map.response.?; + const memory_map_entries = memory_map.entries.*[0..memory_map.entry_count]; + for (memory_map_entries) |entry| { + if (entry.type == .bootloader_reclaimable) { + if (entry.region.address.toHigherHalfVirtualAddress().value() < rsp and entry.region.address.offset(entry.region.size).toHigherHalfVirtualAddress().value() > rsp) { + try minimal_paging.map(entry.region.address, entry.region.address.toHigherHalfVirtualAddress(), entry.region.size, .{ .write = true, .execute = false }, page_allocator); + break; + } + } + } else @panic("Can't find memory map region for RSP"); + } + + pub fn getCPUCount(init: *Initialization) !u32 { + return switch (lib.cpu.arch) { + .x86_64 => blk: { + const rsdp = init.architecture.rsdp; + const madt_header = try rsdp.findTable(.APIC); + const madt = @as(*align(1) const ACPI.MADT, @ptrCast(madt_header)); + const cpu_count = madt.getCPUCount(); + break :blk cpu_count; + }, + else => @compileError("Architecture not supported"), + }; + } + + pub fn getRSDPAddress(init: *Initialization) usize { + return @intFromPtr(init.architecture.rsdp); + } + + pub fn deinitializeMemoryMap(init: *Initialization) !void { + init.memory_map.index = 0; + } + + fn initialize(init: *Initialization) !void { + init.* = .{ + .framebuffer = blk: { + if (request.framebuffer.response) |response| { + const framebuffers = response.framebuffers.*; + if (response.framebuffer_count > 0) { + const framebuffer = framebuffers[0]; + break :blk .{ + .address = framebuffer.address, + .pitch = @as(u32, @intCast(framebuffer.pitch)), + .width = @as(u32, @intCast(framebuffer.width)), + .height = @as(u32, @intCast(framebuffer.height)), + .bpp = framebuffer.bpp, + .red_mask = .{ + .shift = framebuffer.red_mask_shift, + .size = framebuffer.red_mask_size, + }, + .green_mask = .{ + .shift = framebuffer.green_mask_shift, + .size = framebuffer.green_mask_size, + }, + .blue_mask = .{ + .shift = framebuffer.blue_mask_shift, + .size = framebuffer.blue_mask_size, + }, + .memory_model = framebuffer.memory_model, + }; + } + } + + return Error.framebuffer_not_found; + }, + .memory_map = blk: { + if (request.memory_map.response) |response| { + if (response.entry_count > 0) { + const entries = response.entries.*[0..response.entry_count]; + break :blk .{ + .entries = entries, + }; + } + } + + return Error.memory_map_not_found; + }, + .filesystem = blk: { + if (request.modules.response) |response| { + if (response.module_count > 0) { + const modules = response.modules.*[0..response.module_count]; + break :blk .{ + .modules = modules, + }; + } + } + return Error.filesystem_not_found; + }, + .architecture = switch (lib.cpu.arch) { + .x86_64 => .{ + .rsdp = blk: { + if (request.rsdp.response) |response| { + break :blk @as(?*ACPI.RSDP.Descriptor1, @ptrFromInt(response.address)) orelse return Error.rsdp_not_found; + } + + return Error.rsdp_not_found; + }, + }, + else => @compileError("Architecture not supported"), + }, + }; + + init.early_initialized = true; + init.framebuffer_initialized = true; + init.filesystem_initialized = true; + init.memory_map_initialized = true; + } +}; + +const Error = error{ + not_implemented, + file_not_found, + framebuffer_not_found, + memory_map_not_found, + filesystem_not_found, + rsdp_not_found, + protocol_not_found, +}; + +var initialization: Initialization = undefined; + +export fn _start() callconv(.C) noreturn { + main() catch |err| @panic(@errorName(err)); +} + +pub fn main() !noreturn { + log.debug("Hello Limine!", .{}); + const limine_protocol: bootloader.Protocol = if (request.efi_system_table.response != null) .uefi else if (request.smbios.response != null) .bios else return Error.protocol_not_found; + + try initialization.initialize(); + + switch (limine_protocol) { + inline else => |protocol| try bootloader.Information.initialize(&initialization, .limine, protocol), + } +} diff --git a/src/bootloader/limine/test.zig b/src/bootloader/limine/test.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/bootloader/rise/bios/linker_script.ld b/src/bootloader/rise/bios/linker_script.ld new file mode 100644 index 0000000..c0837c7 --- /dev/null +++ b/src/bootloader/rise/bios/linker_script.ld @@ -0,0 +1,27 @@ +PHDRS { + none PT_NULL FLAGS(0); + text PT_LOAD FLAGS((1 << 2) | (1 << 0) /* Readable | Executable */); + rodata PT_LOAD FLAGS((1 << 2) /* Readable */); + data PT_LOAD FLAGS((1 << 2) | (1 << 1) /* Readable | Writeable */); +} + +SECTIONS { + . = 0x1000; + loader_start = .; + .text . : { + *(.smp_trampoline*) + *(.realmode*) + *(.text*) + }:text + . = ALIGN(0x10); + .rodata . : { + *(.rodata*) + }:rodata + . = ALIGN(0x10); + .data . : { + *(.data*) + *(.bss*) + }:data + . = ALIGN(0x10); + loader_end = .; +} diff --git a/src/bootloader/rise/bios/main.zig b/src/bootloader/rise/bios/main.zig new file mode 100644 index 0000000..5202703 --- /dev/null +++ b/src/bootloader/rise/bios/main.zig @@ -0,0 +1,275 @@ +const lib = @import("lib"); +const Allocator = lib.Allocator; +const assert = lib.assert; +const log = lib.log; +const privileged = @import("privileged"); +const ACPI = privileged.ACPI; +const MemoryManager = privileged.MemoryManager; +const PhysicalHeap = privileged.PhyicalHeap; +const writer = privileged.writer; + +const stopCPU = privileged.arch.stopCPU; +const GDT = privileged.arch.x86_64.GDT; +const Mapping = privileged.Mapping; +const PageAllocator = privileged.PageAllocator; +const PhysicalAddress = lib.PhysicalAddress; +const VirtualAddress = lib.VirtualAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const VirtualMemoryRegion = lib.VirtualMemoryRegion; + +const bootloader = @import("bootloader"); +const bios = @import("bios"); + +extern const loader_start: u8; +extern const loader_end: u8; + +const FATAllocator = extern struct { + buffer: [0x2000]u8 = undefined, + allocated: usize = 0, + allocator: Allocator = .{ + .callbacks = .{ + .allocate = allocate, + }, + }, + + pub fn allocate(allocator: *Allocator, size: u64, alignment: u64) Allocator.Allocate.Error!Allocator.Allocate.Result { + const fat = @fieldParentPtr(FATAllocator, "allocator", allocator); + const aligned_allocated = lib.alignForward(usize, fat.allocated, @as(usize, @intCast(alignment))); + if (aligned_allocated + size > fat.buffer.len) @panic("no alloc"); + fat.allocated = aligned_allocated; + const result = Allocator.Allocate.Result{ + .address = @intFromPtr(&fat.buffer) + fat.allocated, + .size = size, + }; + fat.allocated += @as(usize, @intCast(size)); + return result; + } +}; + +pub const std_options = struct { + pub const log_level = lib.std.log.Level.debug; + + pub fn logFn(comptime level: lib.std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype) void { + _ = args; + _ = format; + _ = scope; + _ = level; + // _ = level; + // writer.writeByte('[') catch stopCPU(); + // writer.writeAll(@tagName(scope)) catch stopCPU(); + // writer.writeAll("] ") catch stopCPU(); + // lib.format(writer, format, args) catch stopCPU(); + // writer.writeByte('\n') catch stopCPU(); + } +}; + +pub fn panic(message: []const u8, _: ?*lib.StackTrace, _: ?usize) noreturn { + privileged.arch.disableInterrupts(); + writer.writeAll("[PANIC] ") catch stopCPU(); + writer.writeAll(message) catch stopCPU(); + writer.writeByte('\n') catch stopCPU(); + + privileged.shutdown(.failure); +} + +const Filesystem = extern struct { + fat_allocator: FATAllocator = .{}, + fat_cache: lib.Filesystem.FAT32.Cache, + disk: bios.Disk = .{}, + cache_index: usize = 0, + + pub fn deinitialize(filesystem: *Filesystem) !void { + filesystem.fat_allocator.allocated = filesystem.cache_index; + } + + pub fn readFile(filesystem: *Filesystem, file_path: []const u8, file_buffer: []u8) ![]const u8 { + log.debug("File {s} read started", .{file_path}); + assert(filesystem.fat_allocator.allocated <= filesystem.fat_allocator.buffer.len); + const file = try filesystem.fat_cache.readFileToBuffer(file_path, file_buffer); + log.debug("File read succeeded", .{}); + return file; + } + + pub fn sneakFile(filesystem: *Filesystem, file_path: []const u8, size: usize) ![]const u8 { + log.debug("File {s} read started", .{file_path}); + const file = try filesystem.fat_cache.readFileToCache(file_path, size); + log.debug("File read succeeded", .{}); + return file; + } + + pub fn getFileSize(filesystem: *Filesystem, file_path: []const u8) !u32 { + const file_size = try filesystem.fat_cache.getFileSize(file_path); + filesystem.fat_allocator.allocated = filesystem.cache_index; + return file_size; + } + + pub fn getSectorSize(filesystem: *Filesystem) u16 { + return filesystem.disk.disk.sector_size; + } +}; + +const MemoryMap = extern struct { + iterator: bios.E820Iterator, + entry_count: u32, + + pub fn getEntryCount(memory_map: *const MemoryMap) u32 { + return memory_map.entry_count; + } + + pub fn next(memory_map: *MemoryMap) !?bootloader.MemoryMapEntry { + if (memory_map.iterator.next()) |bios_entry| { + return .{ + .region = bios_entry.toPhysicalMemoryRegion(), + .type = switch (bios_entry.type) { + .usable => if (bios_entry.isUsable()) .usable else .reserved, + .bad_memory => .bad_memory, + else => .reserved, + }, + }; + } + + return null; + } +}; + +const Initialization = struct { + filesystem: Filesystem, + memory_map: MemoryMap, + framebuffer: bootloader.Framebuffer, + architecture: switch (lib.cpu.arch) { + .x86, .x86_64 => struct { + rsdp: u32, + }, + else => @compileError("Architecture not supported"), + }, + early_initialized: bool = false, + framebuffer_initialized: bool = false, + memory_map_initialized: bool = false, + filesystem_initialized: bool = false, + + pub fn getCPUCount(init: *Initialization) !u32 { + return switch (lib.cpu.arch) { + .x86, .x86_64 => blk: { + const rsdp = @as(*ACPI.RSDP.Descriptor1, @ptrFromInt(init.architecture.rsdp)); + const madt_header = try rsdp.findTable(.APIC); + const madt = @as(*align(1) const ACPI.MADT, @ptrCast(madt_header)); + break :blk madt.getCPUCount(); + }, + else => @compileError("Architecture not supported"), + }; + } + + pub fn getRSDPAddress(init: *Initialization) u32 { + return init.architecture.rsdp; + } + + pub fn deinitializeMemoryMap(init: *Initialization) !void { + init.memory_map.iterator = bios.E820Iterator{}; + } + + pub fn ensureLoaderIsMapped(init: *Initialization, paging: privileged.arch.paging.Specific, page_allocator: PageAllocator, bootloader_information: *bootloader.Information) !void { + _ = init; + _ = bootloader_information; + const loader_physical_start = PhysicalAddress.new(lib.alignBackward(usize, @intFromPtr(&loader_start), lib.arch.valid_page_sizes[0])); + const loader_size = lib.alignForward(u64, @intFromPtr(&loader_end) - @intFromPtr(&loader_start) + @intFromPtr(&loader_start) - loader_physical_start.value(), lib.arch.valid_page_sizes[0]); + // Not caring about safety here + try paging.map(loader_physical_start, loader_physical_start.toIdentityMappedVirtualAddress(), lib.alignForward(u64, loader_size, lib.arch.valid_page_sizes[0]), .{ .write = true, .execute = true }, page_allocator); + } + + pub fn ensureStackIsMapped(init: *Initialization, paging: privileged.arch.paging.Specific, page_allocator: PageAllocator) !void { + _ = init; + const loader_stack_size = bios.stack_size; + const loader_stack = PhysicalAddress.new(lib.alignForward(u32, bios.stack_top, lib.arch.valid_page_sizes[0]) - loader_stack_size); + try paging.map(loader_stack, loader_stack.toIdentityMappedVirtualAddress(), loader_stack_size, .{ .write = true, .execute = false }, page_allocator); + } + + pub fn initialize(init: *Initialization) !void { + // assert(!init.filesystem.initialized); + // defer init.filesystem.initialized = true; + init.* = .{ + .filesystem = .{ + .fat_cache = undefined, + }, + .memory_map = .{ + .iterator = .{}, + .entry_count = bios.getMemoryMapEntryCount(), + }, + .architecture = switch (lib.cpu.arch) { + .x86, .x86_64 => .{ + .rsdp = @intFromPtr(try bios.findRSDP()), + }, + else => @compileError("Architecture not supported"), + }, + .framebuffer = blk: { + var vbe_info: bios.VBE.Information = undefined; + + const edid_info = bios.VBE.getEDIDInfo() catch @panic("No EDID"); + const edid_width = edid_info.getWidth(); + const edid_height = edid_info.getHeight(); + const edid_bpp = 32; + const preferred_resolution = if (edid_width != 0 and edid_height != 0) .{ .x = edid_width, .y = edid_height } else @panic("No EDID"); + _ = preferred_resolution; + bios.VBE.getControllerInformation(&vbe_info) catch @panic("No VBE information"); + + if (!lib.equal(u8, &vbe_info.signature, "VESA")) { + @panic("VESA signature"); + } + + if (vbe_info.version_major != 3 and vbe_info.version_minor != 0) { + @panic("VESA version"); + } + + const edid_video_mode = vbe_info.getVideoMode(bios.VBE.Mode.defaultIsValid, edid_width, edid_height, edid_bpp) orelse @panic("No video mode"); + const framebuffer_region = PhysicalMemoryRegion.fromRaw(.{ + .raw_address = edid_video_mode.framebuffer_address, + .size = edid_video_mode.linear_bytes_per_scanline * edid_video_mode.resolution_y, + }); + + const framebuffer = .{ + .address = framebuffer_region.address.value(), + .pitch = edid_video_mode.linear_bytes_per_scanline, + .width = edid_video_mode.resolution_x, + .height = edid_video_mode.resolution_y, + .bpp = edid_video_mode.bpp, + .red_mask = .{ + .shift = edid_video_mode.linear_red_mask_shift, + .size = edid_video_mode.linear_red_mask_size, + }, + .green_mask = .{ + .shift = edid_video_mode.linear_green_mask_shift, + .size = edid_video_mode.linear_green_mask_size, + }, + .blue_mask = .{ + .shift = edid_video_mode.linear_blue_mask_shift, + .size = edid_video_mode.linear_blue_mask_size, + }, + .memory_model = 0x06, + }; + + break :blk framebuffer; + }, + }; + + const gpt_cache = try lib.PartitionTable.GPT.Partition.Cache.fromPartitionIndex(&init.filesystem.disk.disk, 0, &init.filesystem.fat_allocator.allocator); + init.filesystem.fat_cache = try lib.Filesystem.FAT32.Cache.fromGPTPartitionCache(&init.filesystem.fat_allocator.allocator, gpt_cache); + init.filesystem.cache_index = init.filesystem.fat_allocator.allocated; + try init.deinitializeMemoryMap(); + + init.memory_map_initialized = true; + init.filesystem_initialized = true; + init.framebuffer_initialized = true; + + init.early_initialized = true; + } +}; + +var initialization: Initialization = undefined; + +export fn _start() callconv(.C) noreturn { + bios.A20Enable() catch @panic("A20 is not enabled"); + + initialization.initialize() catch |err| @panic(@errorName(err)); + bootloader.Information.initialize(&initialization, .rise, .bios) catch |err| { + @panic(@errorName(err)); + }; +} diff --git a/src/bootloader/rise/bios/test.zig b/src/bootloader/rise/bios/test.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/bootloader/rise/bios/unreal_mode.S b/src/bootloader/rise/bios/unreal_mode.S new file mode 100644 index 0000000..5bebc1c --- /dev/null +++ b/src/bootloader/rise/bios/unreal_mode.S @@ -0,0 +1,113 @@ +.code32 +.align 0x10 +.global interrupt +interrupt: +movb 4(%esp), %al +movb %al, (.interrupt_number) + +movl 8(%esp), %eax +movl %eax, (.out_registers) + +movl 12(%esp), %eax +movl %eax, (.in_registers) + +sgdt [.protected_mode_gdt] +sidt [.protected_mode_idt] +lidt [.real_mode_idt] + +push %ebx +push %esi +push %edi +push %ebp + +jmp $0x08, $.bits16 + +.code16 +.bits16: +mov $0x10, %ax +mov %ax, %ds +mov %ax, %es +mov %ax, %fs +mov %ax, %gs +mov %ax, %ss +mov %cr0, %eax +and $0xfe, %al +mov %eax, %cr0 +jmp $0x00, $.cs_zero +.cs_zero: +xor %ax, %ax +mov %ax, %ss +mov %esp, %ss:(.esp) +mov %ss:(.in_registers), %esp +pop %gs +pop %fs +pop %es +pop %ds +popfd +pop %ebp +pop %edi +pop %esi +pop %edx +pop %ecx +pop %ebx +pop %eax +mov %ss:(.esp), %esp +sti +.byte 0xcd +.interrupt_number: .byte 0 +cli + +mov %esp, %ss:(.esp) +mov %ss:(.out_registers), %esp +lea 0x28(%esp), %esp +push %eax +push %ebx +push %ecx +push %edx +push %esi +push %ebp +pushfd +push %ds +push %es +push %fs +push %gs +mov %ss:(.esp), %esp + +lgdtl %ss:(.protected_mode_gdt) +lidtl %ss:(.protected_mode_idt) + +mov %cr0, %eax +or $0x1, %al +mov %eax, %cr0 +jmp $0x18, $.bits32 + +err: +cli +hlt + +.bits32: +.code32 +mov $0x20, %eax +mov %eax, %ds +mov %eax, %es +mov %eax, %fs +mov %eax, %gs +mov %eax, %ss + +pop %ebp +pop %edi +pop %esi +pop %ebx + + +ret + +.align 0x10 +.esp: .long 0 +.out_registers: .long 0 +.in_registers: .long 0 +.protected_mode_gdt: .quad 0 +.protected_mode_idt: .quad 0 +.real_mode_idt: + .word 0x3ff + .long 0 diff --git a/src/bootloader/rise/uefi/main.zig b/src/bootloader/rise/uefi/main.zig new file mode 100644 index 0000000..eec4563 --- /dev/null +++ b/src/bootloader/rise/uefi/main.zig @@ -0,0 +1,436 @@ +const lib = @import("lib"); +const assert = lib.assert; +const config = lib.config; +const Allocator = lib.Allocator; +const ELF = lib.ELF(64); +const log = lib.log.scoped(.uefi); +const PhysicalAddress = lib.PhysicalAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const VirtualAddress = lib.VirtualAddress; +const VirtualMemoryRegion = lib.VirtualMemoryRegion; + +const bootloader = @import("bootloader"); +const uefi = @import("uefi"); +const BootloaderInformation = uefi.BootloaderInformation; +const BootServices = uefi.BootServices; +const ConfigurationTable = uefi.ConfigurationTable; +const FileProtocol = uefi.FileProtocol; +const Handle = uefi.Handle; +const LoadedImageProtocol = uefi.LoadedImageProtocol; +const LoadKernelFunction = uefi.LoadKernelFunction; +const MemoryCategory = uefi.MemoryCategory; +const MemoryDescriptor = uefi.MemoryDescriptor; +const ProgramSegment = uefi.ProgramSegment; +const Protocol = uefi.Protocol; +const page_table_estimated_size = uefi.page_table_estimated_size; +const SimpleFilesystemProtocol = uefi.SimpleFilesystemProtocol; +const SystemTable = uefi.SystemTable; + +const privileged = @import("privileged"); +const ACPI = privileged.ACPI; +const PageAllocator = privileged.PageAllocator; +pub const writer = privileged.writer; + +const CPU = privileged.arch.CPU; +const GDT = privileged.arch.x86_64.GDT; +const paging = privileged.arch.paging; + +const Stage = enum { + boot_services, + after_boot_services, + trampoline, +}; + +pub const std_options = struct { + pub const log_level = lib.std.log.Level.debug; + + pub fn logFn(comptime level: lib.std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype) void { + const scope_prefix = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): "; + const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix; + switch (lib.cpu.arch) { + .x86_64 => { + lib.format(writer, prefix ++ format ++ "\n", args) catch {}; + }, + else => @compileError("Unsupported CPU architecture"), + } + } +}; + +const practical_memory_map_descriptor_size = 0x30; +const practical_memory_map_descriptor_count = 256; + +pub fn panic(message: []const u8, _: ?*lib.StackTrace, _: ?usize) noreturn { + writer.writeAll("[uefi] [PANIC] ") catch {}; + writer.writeAll(message) catch {}; + writer.writeAll("\r\n") catch {}; + + privileged.shutdown(.failure); +} + +const Filesystem = extern struct { + root: *FileProtocol, + buffer: [0x200 * 10]u8 = undefined, + + pub fn deinitialize(filesystem: *Filesystem) !void { + _ = filesystem; + } + + pub fn readFile(filesystem: *Filesystem, file_path: []const u8, file_buffer: []u8) ![]const u8 { + const file = try filesystem.openFile(file_path); + var size: u64 = file_buffer.len; + try uefi.Try(file.handle.read(&size, file_buffer.ptr)); + if (file_buffer.len < size) @panic("readFileFast"); + return file_buffer[0..size]; + } + + pub fn sneakFile(filesystem: *Filesystem, file_path: []const u8, size: usize) ![]const u8 { + _ = size; + + const file = try filesystem.readFile(file_path, &filesystem.buffer); + return file; + } + + const FileDescriptor = struct { + handle: *FileProtocol, + path_size: u32, + }; + + pub fn getFileSize(filesystem: *Filesystem, file_path: []const u8) !u32 { + const file = try filesystem.openFile(file_path); + log.debug("File size", .{}); + var file_info_buffer: [@sizeOf(uefi.FileInfo) + 0x100]u8 align(@alignOf(uefi.FileInfo)) = undefined; + var file_info_size = file_info_buffer.len; + try uefi.Try(file.handle.getInfo(&uefi.FileInfo.guid, &file_info_size, &file_info_buffer)); + if (file_info_buffer.len < file_info_size) @panic("getFileSize"); + const file_info = @as(*uefi.FileInfo, @ptrCast(&file_info_buffer)); + return @as(u32, @intCast(file_info.file_size)); + } + + fn openFile(filesystem: *Filesystem, file_path: []const u8) !FileDescriptor { + const init = @fieldParentPtr(Initialization, "filesystem", filesystem); + if (init.exited_boot_services) { + return Error.boot_services_exited; + } + + log.debug("opening file: {s}", .{file_path}); + var file: *FileProtocol = undefined; + var path_buffer: [256:0]u16 = undefined; + const length = try lib.unicode.utf8ToUtf16Le(&path_buffer, file_path); + path_buffer[length] = 0; + const path = path_buffer[0..length :0]; + const uefi_path = if (path[0] == '/') path[1..] else path; + log.debug("uefi path: {any}", .{uefi_path}); + + try uefi.Try(filesystem.root.open(&file, uefi_path, FileProtocol.efi_file_mode_read, 0)); + log.debug("Opened", .{}); + + const result = FileDescriptor{ + .handle = file, + .path_size = @as(u32, @intCast(path.len * @sizeOf(u16))), + }; + + log.debug("opened file: {s}", .{file_path}); + + return result; + } + + pub fn getSectorSize(filesystem: *Filesystem) u16 { + _ = filesystem; + return lib.default_sector_size; + } +}; + +const MemoryMap = extern struct { + size: usize = buffer_len, + key: usize, + descriptor_size: usize, + descriptor_version: u32, + buffer: [buffer_len]u8 align(@alignOf(MemoryDescriptor)) = undefined, + offset: usize = 0, + entry_count: u32, + + const buffer_len = practical_memory_map_descriptor_size * practical_memory_map_descriptor_count; + + pub fn getEntryCount(memory_map: *const MemoryMap) u32 { + return memory_map.entry_count; + } + + pub fn next(memory_map: *MemoryMap) !?bootloader.MemoryMapEntry { + if (memory_map.offset < memory_map.size) { + const entry = @as(*MemoryDescriptor, @ptrCast(@alignCast(@alignOf(MemoryDescriptor), memory_map.buffer[memory_map.offset..].ptr))).*; + memory_map.offset += memory_map.descriptor_size; + const result = bootloader.MemoryMapEntry{ + .region = PhysicalMemoryRegion.new(.{ + .address = PhysicalAddress.new(entry.physical_start), + .size = entry.number_of_pages << uefi.page_shifter, + }), + .type = switch (entry.type) { + .ReservedMemoryType, .LoaderCode, .LoaderData, .BootServicesCode, .BootServicesData, .RuntimeServicesCode, .RuntimeServicesData, .ACPIReclaimMemory, .ACPIMemoryNVS, .MemoryMappedIO, .MemoryMappedIOPortSpace, .PalCode, .PersistentMemory => .reserved, + .ConventionalMemory => .usable, + .UnusableMemory => .bad_memory, + else => @panic("Unknown type"), + }, + }; + + return result; + } + + return null; + } + + fn getHostRegion(memory_map: *MemoryMap, length_size_tuples: bootloader.LengthSizeTuples) !PhysicalMemoryRegion { + var memory: []align(uefi.page_size) u8 = undefined; + const memory_size = length_size_tuples.getAlignedTotalSize(); + try uefi.Try(memory_map.boot_services.allocatePages(.AllocateAnyPages, .LoaderData, memory_size >> uefi.page_shifter, &memory.ptr)); + memory.len = memory_size; + @memset(memory, 0); + + return PhysicalMemoryRegion.fromByteSlice(.{ .slice = memory }); + } +}; + +const Initialization = struct { + filesystem: Filesystem, + framebuffer: bootloader.Framebuffer, + architecture: switch (lib.cpu.arch) { + .x86_64 => struct { + rsdp: *ACPI.RSDP.Descriptor1, + }, + else => @compileError("Architecture not supported"), + }, + boot_services: *uefi.BootServices, + handle: uefi.Handle, + // system_table: *uefi.SystemTable, + early_initialized: bool = false, + filesystem_initialized: bool = false, + memory_map_initialized: bool = false, + framebuffer_initialized: bool = false, + exited_boot_services: bool = false, + memory_map: MemoryMap, + + pub fn getRSDPAddress(init: *Initialization) u32 { + return @as(u32, @intCast(@intFromPtr(init.architecture.rsdp))); + } + + pub fn getCPUCount(init: *Initialization) !u32 { + return switch (lib.cpu.arch) { + .x86_64 => blk: { + const madt_header = try init.architecture.rsdp.findTable(.APIC); + const madt = @as(*align(1) const ACPI.MADT, @ptrCast(madt_header)); + break :blk madt.getCPUCount(); + }, + else => @compileError("Architecture not supported"), + }; + } + + pub fn initialize(init: *Initialization) !void { + defer init.early_initialized = true; + + const system_table = uefi.getSystemTable(); + const handle = uefi.getHandle(); + const boot_services = system_table.boot_services orelse @panic("boot services"); + const out = system_table.con_out orelse @panic("con out"); + try uefi.Try(out.reset(true)); + try uefi.Try(out.clearScreen()); + + init.* = .{ + .memory_map = .{ + .key = 0, + .descriptor_size = 0, + .descriptor_version = 0, + .entry_count = 0, + }, + .filesystem = .{ + .root = blk: { + const loaded_image = try Protocol.open(LoadedImageProtocol, boot_services, handle); + const filesystem_protocol = try Protocol.open(SimpleFilesystemProtocol, boot_services, loaded_image.device_handle orelse @panic("No device handle")); + + var root: *FileProtocol = undefined; + try uefi.Try(filesystem_protocol.openVolume(&root)); + break :blk root; + }, + }, + .framebuffer = blk: { + log.debug("Locating GOP", .{}); + const gop = try Protocol.locate(uefi.GraphicsOutputProtocol, boot_services); + log.debug("Located GOP", .{}); + + const pixel_format_info: struct { + red_color_mask: bootloader.Framebuffer.ColorMask, + blue_color_mask: bootloader.Framebuffer.ColorMask, + green_color_mask: bootloader.Framebuffer.ColorMask, + bpp: u8, + } = switch (gop.mode.info.pixel_format) { + .PixelRedGreenBlueReserved8BitPerColor => .{ + .red_color_mask = .{ .size = 8, .shift = 0 }, + .green_color_mask = .{ .size = 8, .shift = 8 }, + .blue_color_mask = .{ .size = 8, .shift = 16 }, + .bpp = 32, + }, + .PixelBlueGreenRedReserved8BitPerColor => .{ + .red_color_mask = .{ .size = 8, .shift = 16 }, + .green_color_mask = .{ .size = 8, .shift = 8 }, + .blue_color_mask = .{ .size = 8, .shift = 0 }, + .bpp = 32, + }, + .PixelBitMask, .PixelBltOnly => @panic("Unsupported pixel format"), + .PixelFormatMax => @panic("Corrupted pixel format"), + }; + + break :blk bootloader.Framebuffer{ + .address = gop.mode.frame_buffer_base, + .pitch = @divExact(gop.mode.info.pixels_per_scan_line * pixel_format_info.bpp, @bitSizeOf(u8)), + .width = gop.mode.info.horizontal_resolution, + .height = gop.mode.info.vertical_resolution, + .bpp = pixel_format_info.bpp, + .red_mask = pixel_format_info.red_color_mask, + .green_mask = pixel_format_info.green_color_mask, + .blue_mask = pixel_format_info.blue_color_mask, + .memory_model = 0x06, + }; + }, + .boot_services = boot_services, + .handle = handle, + .architecture = switch (lib.cpu.arch) { + .x86_64 => .{ + .rsdp = for (system_table.configuration_table[0..system_table.number_of_table_entries]) |configuration_table| { + if (configuration_table.vendor_guid.eql(ConfigurationTable.acpi_20_table_guid)) { + break @as(*ACPI.RSDP.Descriptor1, @ptrCast(@alignCast(@alignOf(ACPI.RSDP.Descriptor1), configuration_table.vendor_table))); + } + } else return Error.rsdp_not_found, + }, + else => @compileError("Architecture not supported"), + }, + }; + + log.debug("Memory map size: {}", .{init.memory_map.size}); + _ = boot_services.getMemoryMap(&init.memory_map.size, @as([*]MemoryDescriptor, @ptrCast(&init.memory_map.buffer)), &init.memory_map.key, &init.memory_map.descriptor_size, &init.memory_map.descriptor_version); + init.memory_map.entry_count = @as(u32, @intCast(@divExact(init.memory_map.size, init.memory_map.descriptor_size))); + assert(init.memory_map.entry_count > 0); + + init.filesystem_initialized = true; + init.memory_map_initialized = true; + init.framebuffer_initialized = true; + } + + pub fn deinitializeMemoryMap(init: *Initialization) !void { + if (!init.exited_boot_services) { + // Add the region for the bootloader information + init.memory_map.size += init.memory_map.descriptor_size; + const expected_memory_map_size = init.memory_map.size; + const expected_memory_map_descriptor_size = init.memory_map.descriptor_size; + const expected_memory_map_descriptor_version = init.memory_map.descriptor_version; + + log.debug("Getting memory map before exiting boot services...", .{}); + + blk: while (init.memory_map.size < MemoryMap.buffer_len) : (init.memory_map.size += init.memory_map.descriptor_size) { + uefi.Try(init.boot_services.getMemoryMap(&init.memory_map.size, @as([*]MemoryDescriptor, @ptrCast(&init.memory_map.buffer)), &init.memory_map.key, &init.memory_map.descriptor_size, &init.memory_map.descriptor_version)) catch continue; + init.exited_boot_services = true; + break :blk; + } else { + @panic("Cannot satisfy memory map requirements"); + } + + if (expected_memory_map_size != init.memory_map.size) { + log.warn("Old memory map size: {}. New memory map size: {}", .{ expected_memory_map_size, init.memory_map.size }); + } + if (expected_memory_map_descriptor_size != init.memory_map.descriptor_size) { + @panic("Descriptor size change"); + } + if (expected_memory_map_descriptor_version != init.memory_map.descriptor_version) { + @panic("Descriptor version change"); + } + const real_memory_map_entry_count = @divExact(init.memory_map.size, init.memory_map.descriptor_size); + const expected_memory_map_entry_count = @divExact(expected_memory_map_size, expected_memory_map_descriptor_size); + const diff = @as(i16, @intCast(expected_memory_map_entry_count)) - @as(i16, @intCast(real_memory_map_entry_count)); + if (diff < 0) { + @panic("Memory map entry count diff < 0"); + } + + // bootloader_information.configuration.memory_map_diff = @intCast(u8, diff); + + log.debug("Exiting boot services...", .{}); + try uefi.Try(init.boot_services.exitBootServices(init.handle, init.memory_map.key)); + log.debug("Exited boot services...", .{}); + + privileged.arch.disableInterrupts(); + } + + init.memory_map.offset = 0; + } + + pub fn ensureLoaderIsMapped(init: *Initialization, minimal_paging: paging.Specific, page_allocator: PageAllocator, bootloader_information: *bootloader.Information) !void { + _ = bootloader_information; + // Actually mapping the whole uefi executable so we don't have random problems with code being dereferenced by the trampoline + switch (lib.cpu.arch) { + .x86_64 => { + const trampoline_code_start = @intFromPtr(&bootloader.arch.x86_64.jumpToKernel); + + try init.deinitializeMemoryMap(); + while (try init.memory_map.next()) |entry| { + if (entry.region.address.value() < trampoline_code_start and trampoline_code_start < entry.region.address.offset(entry.region.size).value()) { + const code_physical_region = entry.region; + const code_virtual_address = code_physical_region.address.toIdentityMappedVirtualAddress(); + try minimal_paging.map(code_physical_region.address, code_virtual_address, code_physical_region.size, .{ .write = false, .execute = true }, page_allocator); + return; + } + } + }, + else => @compileError("Architecture not supported"), + } + + return Error.map_failed; + } + + pub fn ensureStackIsMapped(init: *Initialization, minimal_paging: paging.Specific, page_allocator: PageAllocator) !void { + const rsp = asm volatile ( + \\mov %rsp, %[rsp] + : [rsp] "=r" (-> u64), + ); + + while (try init.memory_map.next()) |entry| { + if (entry.region.address.value() < rsp and rsp < entry.region.address.offset(entry.region.size).value()) { + const rsp_region_physical_address = entry.region.address; + const rsp_region_virtual_address = rsp_region_physical_address.toIdentityMappedVirtualAddress(); + if (entry.region.size == 0) return Error.region_empty; + try minimal_paging.map(rsp_region_physical_address, rsp_region_virtual_address, entry.region.size, .{ .write = true, .execute = false }, page_allocator); + return; + } + } + + return Error.map_failed; + } +}; + +var initialization: Initialization = undefined; + +const Error = error{ + map_failed, + region_empty, + rsdp_not_found, + boot_services_exited, +}; + +pub fn main() noreturn { + // var filesystem: Filesystem = .{ + // .boot_services = boot_services, + // .handle = handle, + // .root = undefined, + // }; + // var mmap: MemoryMap = .{ + // .boot_services = boot_services, + // .handle = handle, + // }; + // var fb = Framebuffer{ + // .boot_services = boot_services, + // }; + // var vas = VAS{ + // .mmap = &mmap, + // }; + initialization.initialize() catch |err| { + @panic(@errorName(err)); + }; + bootloader.Information.initialize(&initialization, .rise, .uefi) catch |err| { + @panic(@errorName(err)); + }; +} diff --git a/src/bootloader/rise/uefi/test.zig b/src/bootloader/rise/uefi/test.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/bootloader/todo/draw_context.zig b/src/bootloader/todo/draw_context.zig new file mode 100644 index 0000000..5b03423 --- /dev/null +++ b/src/bootloader/todo/draw_context.zig @@ -0,0 +1,48 @@ +pub const DrawContext = extern struct { + x: u32 = 0, + y: u32 = 0, + color: u32 = 0xff_ff_ff_ff, + reserved: u32 = 0, + + pub const Error = error{}; + pub const Writer = lib.Writer(*DrawContext, DrawContext.Error, DrawContext.write); + + pub fn write(draw_context: *DrawContext, bytes: []const u8) DrawContext.Error!usize { + const bootloader_information = @fieldParentPtr(Information, "draw_context", draw_context); + const color = draw_context.color; + for (bytes) |byte| { + if (byte != '\n') { + bootloader_information.font.draw(&bootloader_information.font, &bootloader_information.framebuffer, byte, color, draw_context.x, draw_context.y); + if (draw_context.x + 8 < bootloader_information.framebuffer.width) { + draw_context.x += @bitSizeOf(u8); + continue; + } + } + + if (draw_context.y < bootloader_information.framebuffer.width) { + draw_context.y += bootloader_information.font.character_size; + draw_context.x = 0; + } else { + asm volatile ( + \\cli + \\hlt + ); + } + } + + return bytes.len; + } + + pub inline fn clearScreen(draw_context: *DrawContext, color: u32) void { + const bootloader_information = @fieldParentPtr(Information, "draw_context", draw_context); + const pixels_per_scanline = @divExact(bootloader_information.framebuffer.pitch, @divExact(bootloader_information.framebuffer.bpp, @bitSizeOf(u8))); + const framebuffer_pixels = @as([*]u32, @ptrFromInt(bootloader_information.framebuffer.address))[0 .. pixels_per_scanline * bootloader_information.framebuffer.height]; + var y: u32 = 0; + while (y < bootloader_information.framebuffer.height) : (y += 1) { + const line = framebuffer_pixels[y * pixels_per_scanline .. y * pixels_per_scanline + pixels_per_scanline]; + for (line) |*pixel| { + pixel.* = color; + } + } + } +}; diff --git a/src/bootloader/todo/font.zig b/src/bootloader/todo/font.zig new file mode 100644 index 0000000..5e179bb --- /dev/null +++ b/src/bootloader/todo/font.zig @@ -0,0 +1,51 @@ +pub const Font = extern struct { + file: PhysicalMemoryRegion align(8), // so 32-bit doesn't whine + glyph_buffer_size: u32, + character_size: u8, + draw: *const fn (font: *const Font, framebuffer: *const Framebuffer, character: u8, color: u32, offset_x: u32, offset_y: u32) void, + + pub fn fromPSF1(file: []const u8) !Font { + const header = @as(*const lib.PSF1.Header, @ptrCast(file.ptr)); + if (!lib.equal(u8, &header.magic, &lib.PSF1.Header.magic)) { + return lib.PSF1.Error.invalid_magic; + } + + const glyph_buffer_size = @as(u32, header.character_size) * (lib.maxInt(u8) + 1) * (1 + @intFromBool(header.mode == 1)); + + return .{ + .file = PhysicalMemoryRegion.new(PhysicalAddress.new(@intFromPtr(file.ptr)), file.len), + .glyph_buffer_size = glyph_buffer_size, + .character_size = header.character_size, + .draw = drawPSF1, + }; + } + + fn drawPSF1(font: *const Font, framebuffer: *const Framebuffer, character: u8, color: u32, offset_x: u32, offset_y: u32) void { + const bootloader_information = @fieldParentPtr(Information, "framebuffer", framebuffer); + const glyph_buffer_virtual_region = if (bootloader_information.stage == .trampoline) font.file.toHigherHalfVirtualAddress() else font.file.toIdentityMappedVirtualAddress(); + const glyph_buffer = glyph_buffer_virtual_region.access(u8)[@sizeOf(lib.PSF1.Header)..][0..font.glyph_buffer_size]; + const glyph_offset = @as(usize, character) * font.character_size; + const glyph = glyph_buffer[glyph_offset .. glyph_offset + font.character_size]; + + var glyph_index: usize = 0; + _ = glyph_index; + + const pixels_per_scanline = @divExact(framebuffer.pitch, @divExact(framebuffer.bpp, @bitSizeOf(u8))); + const fb = @as([*]u32, @ptrFromInt(framebuffer.address))[0 .. pixels_per_scanline * framebuffer.height]; + var y = offset_y; + + for (glyph) |byte| { + const base_index = y * pixels_per_scanline + offset_x; + if (byte & 1 << 7 != 0) fb[base_index + 0] = color; + if (byte & 1 << 6 != 0) fb[base_index + 1] = color; + if (byte & 1 << 5 != 0) fb[base_index + 2] = color; + if (byte & 1 << 4 != 0) fb[base_index + 3] = color; + if (byte & 1 << 3 != 0) fb[base_index + 4] = color; + if (byte & 1 << 2 != 0) fb[base_index + 5] = color; + if (byte & 1 << 1 != 0) fb[base_index + 6] = color; + if (byte & 1 << 0 != 0) fb[base_index + 7] = color; + + y += 1; + } + } +}; diff --git a/src/bootloader/todo/smp.zig b/src/bootloader/todo/smp.zig new file mode 100644 index 0000000..ec9d34e --- /dev/null +++ b/src/bootloader/todo/smp.zig @@ -0,0 +1,136 @@ +// TODO: legacy stuff; refactor when SMP is implemented + +// pub fn initializeSMP(bootloader_information: *Information, madt: *const ACPI.MADT) void { +// if (bootloader_information.bootloader != .rise) @panic("Protocol not supported"); +// +// const smp_records = bootloader_information.getSlice(.smps); +// +// switch (lib.cpu.arch) { +// .x86, .x86_64 => { +// const cr3 = bootloader_information.virtual_address_space.arch.cr3; +// if (@bitCast(u64, cr3) > lib.maxInt(u32)) { +// lib.log.err("CR3: 0x{x}, {}", .{ @bitCast(u64, cr3), cr3 }); +// @panic("CR3 overflow"); +// } +// +// const cpuid = lib.arch.x86_64.cpuid; +// const lapicWrite = privileged.arch.x86_64.APIC.lapicWrite; +// +// if (cpuid(1).edx & (1 << 9) == 0) { +// @panic("No APIC detected"); +// } +// +// var iterator = madt.getIterator(); +// var smp_index: usize = 0; +// +// const smp_trampoline_physical_address = PhysicalAddress.new(@ptrToInt(&arch.x86_64.smp_trampoline)); +// // Sanity checks +// const trampoline_argument_symbol = @extern(*SMP.Trampoline.Argument, .{ .name = "smp_trampoline_arg_start" }); +// const smp_core_booted_symbol = @extern(*bool, .{ .name = "smp_core_booted" }); +// const trampoline_argument_start = @ptrToInt(trampoline_argument_symbol); +// const trampoline_argument_offset = @intCast(u32, trampoline_argument_start - smp_trampoline_physical_address.value()); +// const smp_core_booted_offset = @intCast(u32, @ptrToInt(smp_core_booted_symbol) - smp_trampoline_physical_address.value()); +// if (!lib.isAligned(trampoline_argument_start, @alignOf(SMP.Trampoline.Argument))) @panic("SMP trampoline argument alignment must match"); +// const trampoline_argument_end = @ptrToInt(@extern(*u8, .{ .name = "smp_trampoline_arg_end" })); +// const trampoline_argument_size = trampoline_argument_end - trampoline_argument_start; +// if (trampoline_argument_size != @sizeOf(SMP.Trampoline.Argument)) { +// @panic("SMP trampoline argument size must match"); +// } +// +// const smp_trampoline_size = @ptrToInt(@extern(*u8, .{ .name = "smp_trampoline_end" })) - smp_trampoline_physical_address.value(); +// if (smp_trampoline_size > lib.arch.valid_page_sizes[0]) { +// @panic("SMP trampoline too big"); +// } +// +// const smp_trampoline = @intCast(u32, switch (lib.cpu.arch) { +// .x86 => smp_trampoline_physical_address.toIdentityMappedVirtualAddress().value(), +// .x86_64 => blk: { +// const page_counters = bootloader_information.getPageCounters(); +// for (bootloader_information.getMemoryMapEntries(), 0..) |memory_map_entry, index| { +// if (memory_map_entry.type == .usable and memory_map_entry.region.address.value() < lib.mb and lib.isAligned(memory_map_entry.region.address.value(), lib.arch.valid_page_sizes[0])) { +// const page_counter = &page_counters[index]; +// const offset = page_counter.* * lib.arch.valid_page_sizes[0]; +// if (offset < memory_map_entry.region.size) { +// page_counter.* += 1; +// const smp_trampoline_buffer_region = memory_map_entry.region.offset(offset).toIdentityMappedVirtualAddress(); +// +// privileged.arch.x86_64.paging.setMappingFlags(&bootloader_information.virtual_address_space, smp_trampoline_buffer_region.address.value(), .{ +// .write = true, +// .execute = true, +// .global = true, +// }) catch @panic("can't set smp trampoline flags"); +// +// const smp_trampoline_buffer = smp_trampoline_buffer_region.access(u8); +// const smp_trampoline_region = PhysicalMemoryRegion.new(smp_trampoline_physical_address, smp_trampoline_size); +// const smp_trampoline_source = smp_trampoline_region.toIdentityMappedVirtualAddress().access(u8); +// +// @memcpy(smp_trampoline_buffer, smp_trampoline_source); +// break :blk smp_trampoline_buffer_region.address.value(); +// } +// } +// } +// +// @panic("No suitable region found for SMP trampoline"); +// }, +// else => @compileError("Architecture not supported"), +// }); +// +// const trampoline_argument = @intToPtr(*SMP.Trampoline.Argument, smp_trampoline + trampoline_argument_offset); +// trampoline_argument.* = .{ +// .hhdm = bootloader_information.higher_half, +// .cr3 = @intCast(u32, @bitCast(u64, cr3)), +// .gdt_descriptor = undefined, +// .gdt = .{}, +// }; +// +// trampoline_argument.gdt_descriptor = trampoline_argument.gdt.getDescriptor(); +// +// const smp_core_booted = @intToPtr(*bool, smp_trampoline + smp_core_booted_offset); +// +// while (iterator.next()) |entry| { +// switch (entry.type) { +// .LAPIC => { +// const lapic_entry = @fieldParentPtr(ACPI.MADT.LAPIC, "record", entry); +// const lapic_id = @as(u32, lapic_entry.APIC_ID); +// smp_records[smp_index] = .{ +// .acpi_id = lapic_entry.ACPI_processor_UID, +// .lapic_id = lapic_id, +// .entry_point = 0, +// .argument = 0, +// }; +// +// if (lapic_entry.APIC_ID == bootloader_information.smp.bsp_lapic_id) { +// smp_index += 1; +// continue; +// } +// +// lapicWrite(.icr_high, lapic_id << 24); +// lapicWrite(.icr_low, 0x4500); +// +// arch.x86_64.delay(10_000_000); +// +// const icr_low = (smp_trampoline >> 12) | 0x4600; +// lapicWrite(.icr_high, lapic_id << 24); +// lapicWrite(.icr_low, icr_low); +// +// for (0..100) |_| { +// if (@cmpxchgStrong(bool, smp_core_booted, true, false, .SeqCst, .SeqCst) == null) { +// lib.log.debug("Booted core #{}", .{lapic_id}); +// break; +// } +// +// arch.x86_64.delay(10_000_000); +// } else @panic("SMP not booted"); +// }, +// .x2APIC => @panic("x2APIC"), +// else => { +// lib.log.warn("Unhandled {s} entry", .{@tagName(entry.type)}); +// }, +// } +// } +// +// lib.log.debug("Enabled all cores!", .{}); +// }, +// else => @compileError("Architecture not supported"), +// } +// } diff --git a/src/bootloader/uefi.zig b/src/bootloader/uefi.zig new file mode 100644 index 0000000..2e1e46d --- /dev/null +++ b/src/bootloader/uefi.zig @@ -0,0 +1,88 @@ +const lib = @import("lib"); +const alignForward = lib.alignForward; +const assert = lib.assert; +const CustomAllocator = lib.CustomAllocator; +const log = lib.log.scoped(.UEFI); +const uefi = lib.uefi; + +pub const BootServices = uefi.tables.BootServices; +pub const ConfigurationTable = uefi.tables.ConfigurationTable; +pub const Error = Status.EfiError; +pub const FileInfo = uefi.protocols.FileInfo; +pub const FileProtocol = uefi.protocols.FileProtocol; +pub const GraphicsOutputProtocol = uefi.protocols.GraphicsOutputProtocol; +pub const LoadedImageProtocol = uefi.protocols.LoadedImageProtocol; +pub const Handle = uefi.Handle; +pub const MemoryDescriptor = uefi.tables.MemoryDescriptor; +pub const SimpleFilesystemProtocol = uefi.protocols.SimpleFileSystemProtocol; +pub const Status = uefi.Status; +pub const SystemTable = uefi.tables.SystemTable; +pub const Try = Status.err; + +const str16 = lib.std.unicode.utf8ToUtf16LeStringLiteral; + +pub const page_size = 0x1000; +pub const page_shifter = lib.arch.page_shifter(page_size); + +const privileged = @import("privileged"); +const PhysicalAddress = privileged.PhysicalAddress; +const VirtualAddress = privileged.VirtualAddress; +const VirtualMemoryRegion = privileged.VirtualMemoryRegion; +const stopCPU = privileged.arch.stopCPU; + +pub fn panic(comptime format: []const u8, arguments: anytype) noreturn { + privileged.arch.disableInterrupts(); + lib.log.scoped(.PANIC).err(format, arguments); + privileged.arch.stopCPU(); +} + +pub fn result(src: lib.SourceLocation, status: Status) void { + Try(status) catch |err| { + panic("UEFI error {} at {s}:{}:{} in function {s}", .{ err, src.file, src.line, src.column, src.fn_name }); + }; +} + +pub inline fn getSystemTable() *SystemTable { + return uefi.system_table; +} + +pub inline fn getHandle() Handle { + return uefi.handle; +} + +pub const Protocol = struct { + pub fn locate(comptime ProtocolT: type, boot_services: *BootServices) !*ProtocolT { + var pointer_buffer: ?*anyopaque = null; + try Try(boot_services.locateProtocol(&ProtocolT.guid, null, &pointer_buffer)); + return cast(ProtocolT, pointer_buffer); + } + + pub fn handle(comptime ProtocolT: type, boot_services: *BootServices, efi_handle: Handle) !*ProtocolT { + var interface_buffer: ?*anyopaque = null; + try Try(boot_services.handleProtocol(efi_handle, &ProtocolT.guid, &interface_buffer)); + return cast(ProtocolT, interface_buffer); + } + + pub fn open(comptime ProtocolT: type, boot_services: *BootServices, efi_handle: Handle) !*ProtocolT { + var interface_buffer: ?*anyopaque = null; + try Try(boot_services.openProtocol(efi_handle, &ProtocolT.guid, &interface_buffer, efi_handle, null, .{ .get_protocol = true })); + return cast(ProtocolT, interface_buffer); + } + + fn cast(comptime ProtocolT: type, ptr: ?*anyopaque) *ProtocolT { + return @as(*ProtocolT, @ptrCast(@alignCast(@alignOf(ProtocolT), ptr))); + } +}; + +pub const ProgramSegment = extern struct { + physical: u64, + virtual: u64, + size: u32, + file_offset: u32, + mappings: extern struct { + write: bool, + execute: bool, + }, +}; + +//pub const LoadKernelFunction = fn (bootloader_information: *BootloaderInformation, kernel_start_address: u64, cr3: privileged.arch.x86_64.registers.cr3, stack: u64, gdt_descriptor: *privileged.arch.x86_64.GDT.Descriptor) callconv(.SysV) noreturn; diff --git a/src/common.zig b/src/common.zig new file mode 100644 index 0000000..62d63d7 --- /dev/null +++ b/src/common.zig @@ -0,0 +1,502 @@ +const compiler_builtin = @import("builtin"); +pub const cpu = compiler_builtin.cpu; +pub const os = compiler_builtin.os.tag; +pub const build_mode = compiler_builtin.mode; +pub const is_test = compiler_builtin.is_test; + +pub const kb = 1024; +pub const mb = kb * 1024; +pub const gb = mb * 1024; +pub const tb = gb * 1024; + +pub const SizeUnit = enum(u64) { + byte = 1, + kilobyte = 1024, + megabyte = 1024 * 1024, + gigabyte = 1024 * 1024 * 1024, + terabyte = 1024 * 1024 * 1024 * 1024, +}; + +pub const std = @import("std"); +pub const Target = std.Target; +pub const Cpu = Target.Cpu; +pub const CrossTarget = std.zig.CrossTarget; + +pub const log = std.log; + +pub const Atomic = std.atomic.Atomic; + +pub const Reader = std.io.Reader; +pub const Writer = std.io.Writer; + +pub const FixedBufferStream = std.io.FixedBufferStream; +pub const fixedBufferStream = std.io.fixedBufferStream; + +pub fn assert(ok: bool) void { + if (!ok) { + if (@inComptime()) { + @compileError("Assert failed!"); + } else { + @panic("Assert failed!"); + } + } +} + +pub const deflate = std.compress.deflate; + +const debug = std.debug; +pub const print = debug.print; +pub const StackIterator = debug.StackIterator; +pub const dwarf = std.dwarf; +pub const ModuleDebugInfo = std.debug.ModuleDebugInfo; + +pub const elf = std.elf; + +const fmt = std.fmt; +pub const format = std.fmt.format; +pub const FormatOptions = fmt.FormatOptions; +pub const bufPrint = fmt.bufPrint; +pub const allocPrint = fmt.allocPrint; +pub const comptimePrint = fmt.comptimePrint; +pub const parseUnsigned = fmt.parseUnsigned; + +const heap = std.heap; +pub const FixedBufferAllocator = heap.FixedBufferAllocator; + +pub const json = std.json; + +const mem = std.mem; +pub const ZigAllocator = mem.Allocator; +pub const equal = mem.eql; +pub const length = mem.len; +pub const startsWith = mem.startsWith; +pub const endsWith = mem.endsWith; +pub const indexOf = mem.indexOf; +// Ideal for small inputs +pub const indexOfPosLinear = mem.indexOfPosLinear; +pub const lastIndexOf = mem.lastIndexOf; +pub const asBytes = mem.asBytes; +pub const readIntBig = mem.readIntBig; +pub const readIntSliceBig = mem.readIntSliceBig; +pub const concat = mem.concat; +pub const sliceAsBytes = mem.sliceAsBytes; +pub const bytesAsSlice = mem.bytesAsSlice; +pub const alignForward = mem.alignForward; +pub const alignBackward = mem.alignBackward; +pub const isAligned = mem.isAligned; +pub const isAlignedGeneric = mem.isAlignedGeneric; +pub const reverse = mem.reverse; +pub const tokenize = mem.tokenize; +pub const containsAtLeast = mem.containsAtLeast; +pub const sliceTo = mem.sliceTo; +pub const swap = mem.swap; + +pub const random = std.rand; + +pub const testing = std.testing; + +pub const sort = std.sort; + +pub fn fieldSize(comptime T: type, field_name: []const u8) comptime_int { + var foo: T = undefined; + return @sizeOf(@TypeOf(@field(foo, field_name))); +} + +const DiffError = error{ + diff, +}; + +pub fn diff(file1: []const u8, file2: []const u8) !void { + assert(file1.len == file2.len); + var different_bytes: u64 = 0; + for (file1, 0..) |byte1, index| { + const byte2 = file2[index]; + const is_different_byte = byte1 != byte2; + different_bytes += @intFromBool(is_different_byte); + if (is_different_byte) { + log.debug("Byte [0x{x}] is different: 0x{x} != 0x{x}", .{ index, byte1, byte2 }); + } + } + + if (different_bytes != 0) { + log.debug("Total different bytes: 0x{x}", .{different_bytes}); + return DiffError.diff; + } +} + +pub fn zeroes(comptime T: type) T { + var result: T = undefined; + const slice = asBytes(&result); + @memset(slice, 0); + return result; +} + +const ascii = std.ascii; +pub const upperString = ascii.upperString; +pub const isUpper = ascii.isUpper; +pub const isAlphabetic = ascii.isAlphabetic; + +const std_builtin = std.builtin; +pub const AtomicRmwOp = std_builtin.AtomicRmwOp; +pub const AtomicOrder = std_builtin.AtomicOrder; +pub const Type = std_builtin.Type; +pub const StackTrace = std_builtin.StackTrace; +pub const SourceLocation = std_builtin.SourceLocation; + +pub fn FieldType(comptime T: type, comptime name: []const u8) type { + return @TypeOf(@field(@as(T, undefined), name)); +} + +// META PROGRAMMING +pub const AutoEnumArray = std.enums.EnumArray; +pub const fields = std.meta.fields; +pub const IntType = std.meta.Int; +pub const enumFromInt = std.meta.enumFromInt; +pub const stringToEnum = std.meta.stringToEnum; +pub const Tag = std.meta.Tag; + +const math = std.math; +pub const maxInt = math.maxInt; +pub const min = math.min; +pub const divCeil = math.divCeil; +pub const clamp = math.clamp; +pub const isPowerOfTwo = math.isPowerOfTwo; +pub const mul = math.mul; +pub const cast = math.cast; + +pub const unicode = std.unicode; + +pub const uefi = std.os.uefi; + +pub const DiskType = enum(u32) { + virtio = 0, + nvme = 1, + ahci = 2, + ide = 3, + memory = 4, + bios = 5, + + pub const count = enumCount(@This()); +}; + +pub const FilesystemType = enum(u32) { + rise = 0, + ext2 = 1, + fat32 = 2, + + pub const count = enumCount(@This()); +}; + +pub fn enumFields(comptime E: type) []const Type.EnumField { + return @typeInfo(E).Enum.fields; +} + +pub const enumValues = std.enums.values; + +pub fn enumCount(comptime E: type) usize { + return enumFields(E).len; +} + +pub const PartitionTableType = enum { + mbr, + gpt, +}; + +pub const supported_architectures = [_]Cpu.Arch{ + .x86_64, + //.aarch64, + //.riscv64, +}; + +pub fn architectureIndex(comptime arch: Cpu.Arch) comptime_int { + inline for (supported_architectures, 0..) |architecture, index| { + if (arch == architecture) return index; + } + + @compileError("Architecture not found"); +} + +pub const architecture_bootloader_map = blk: { + var array: [supported_architectures.len][]const ArchitectureBootloader = undefined; + + array[architectureIndex(.x86_64)] = &.{ + .{ + .id = .rise, + .protocols = &.{ .bios, .uefi }, + }, + .{ + .id = .limine, + .protocols = &.{ .bios, .uefi }, + }, + }; + + // array[architectureIndex(.aarch64)] = &.{ + // .{ + // .id = .rise, + // .protocols = &.{.uefi}, + // }, + // .{ + // .id = .limine, + // .protocols = &.{.uefi}, + // }, + // }; + + // array[architectureIndex(.riscv64)] = &.{ + // .{ + // .id = .rise, + // .protocols = &.{.uefi}, + // }, + // }; + + break :blk array; +}; + +pub const Bootloader = enum(u32) { + rise, + limine, + + pub const Protocol = enum(u32) { + bios, + uefi, + }; +}; + +pub const ArchitectureBootloader = struct { + id: Bootloader, + protocols: []const Bootloader.Protocol, +}; + +pub const TraditionalExecutionMode = enum(u1) { + privileged = 0, + user = 1, +}; + +pub const ExecutionEnvironment = enum { + qemu, +}; + +pub const ImageConfig = struct { + image_name: []const u8, + sector_count: u64, + sector_size: u16, + partition_table: PartitionTableType, + partition: PartitionConfig, + + pub const default_path = "config/image_config.json"; + + pub fn get(allocator: ZigAllocator, path: []const u8) !ImageConfig { + const image_config_file = try std.fs.cwd().readFileAlloc(allocator, path, maxInt(usize)); + const parsed_image_configuration = try std.json.parseFromSlice(ImageConfig, allocator, image_config_file, .{}); + return parsed_image_configuration.value; + } +}; + +pub const PartitionConfig = struct { + name: []const u8, + filesystem: FilesystemType, + first_lba: u64, +}; + +pub const QEMU = extern struct { + pub const isa_debug_exit = ISADebugExit{}; + + pub const ISADebugExit = extern struct { + io_base: u8 = 0xf4, + io_size: u8 = @sizeOf(u32), + }; + + pub const ExitCode = enum(u32) { + success = 0x10, + failure = 0x11, + _, + }; +}; + +pub const OptimizeMode = std.builtin.OptimizeMode; + +pub const Configuration = struct { + architecture: Cpu.Arch, + bootloader: Bootloader, + boot_protocol: Bootloader.Protocol, + execution_environment: ExecutionEnvironment, + optimize_mode: OptimizeMode, + execution_type: ExecutionType, + executable_kind: std.Build.CompileStep.Kind, +}; + +pub const QEMUOptions = packed struct { + is_test: bool, + is_debug: bool, +}; + +pub const ExecutionType = enum { + emulated, + accelerated, +}; + +pub const Suffix = enum { + bootloader, + cpu_driver, + image, + complete, + + pub fn fromConfiguration(suffix: Suffix, allocator: ZigAllocator, configuration: Configuration, prefix: ?[]const u8) ![]const u8 { + const cpu_driver_suffix = [_][]const u8{ + @tagName(configuration.optimize_mode), + "_", + @tagName(configuration.architecture), + "_", + @tagName(configuration.executable_kind), + }; + + const bootloader_suffix = [_][]const u8{ + @tagName(configuration.architecture), + "_", + @tagName(configuration.bootloader), + "_", + @tagName(configuration.boot_protocol), + }; + + const image_suffix = [_][]const u8{ + @tagName(configuration.optimize_mode), + "_", + } ++ bootloader_suffix ++ [_][]const u8{ + "_", + @tagName(configuration.executable_kind), + }; + + const complete_suffix = image_suffix ++ [_][]const u8{ + "_", + @tagName(configuration.execution_type), + "_", + @tagName(configuration.execution_environment), + }; + + return try std.mem.concat(allocator, u8, &switch (suffix) { + .cpu_driver => if (prefix) |pf| [1][]const u8{pf} ++ cpu_driver_suffix else cpu_driver_suffix, + .bootloader => if (prefix) |pf| [1][]const u8{pf} ++ bootloader_suffix else bootloader_suffix, + .image => if (prefix) |pf| [1][]const u8{pf} ++ image_suffix else image_suffix, + .complete => if (prefix) |pf| [1][]const u8{pf} ++ complete_suffix else complete_suffix, + }); + } +}; + +pub const Module = struct { + program: UserProgram, + name: []const u8, +}; +pub const UserProgram = struct { + kind: Kind, + dependencies: []const Dependency, + + pub const Kind = enum { + zig_exe, + }; + + pub const Dependency = struct { + foo: u64 = 0, + }; +}; + +pub const RiseProgram = enum { + bootloader, + cpu, + user, + host, +}; + +pub fn canVirtualizeWithQEMU(architecture: Cpu.Arch, ci: bool) bool { + if (architecture != cpu.arch) return false; + if (ci) return false; + + return switch (os) { + .linux => blk: { + const uname = std.os.uname(); + const release = &uname.release; + break :blk !containsAtLeast(u8, release, 1, "WSL") and !containsAtLeast(u8, release, 1, "microsoft"); + }, + else => false, + }; +} + +pub const default_cpu_name = "/cpu"; +pub const default_init_file = "/init"; + +pub const ArgumentParser = struct { + pub const null_specifier = "-"; + + pub const DiskImageBuilder = struct { + argument_index: usize = 0, + + pub const ArgumentType = enum { + disk_image_path, + configuration, + image_configuration_path, + bootloader, + cpu, + user_programs, + }; + + pub const Result = struct { + bootloader: []const u8, + disk_image_path: []const u8, + image_configuration_path: []const u8, + cpu: []const u8, + user_programs: []const []const u8, + configuration: Configuration, + }; + + pub fn next(argument_parser: *ArgumentParser.DiskImageBuilder) ?ArgumentType { + if (argument_parser.argument_index < enumCount(ArgumentType)) { + const result: ArgumentType = @enumFromInt(argument_parser.argument_index); + argument_parser.argument_index += 1; + return result; + } + + return null; + } + }; + + pub const Runner = struct { + argument_index: usize = 0, + + pub fn next(argument_parser: *ArgumentParser.Runner) ?ArgumentType { + if (argument_parser.argument_index < enumCount(ArgumentType)) { + const result: ArgumentType = @enumFromInt(argument_parser.argument_index); + argument_parser.argument_index += 1; + return result; + } + + return null; + } + + pub const ArgumentType = enum { + configuration, + disk_image_path, + image_configuration_path, + cpu_driver, + loader_path, + qemu_options, + ci, + debug_user, + debug_loader, + init, + }; + + pub const Result = struct { + configuration: Configuration, + disk_image_path: []const u8, + image_configuration_path: []const u8, + cpu_driver: []const u8, + loader_path: []const u8, + qemu_options: QEMUOptions, + ci: bool, + debug_user: bool, + debug_loader: bool, + init: []const u8, + }; + }; +}; + +pub const default_disk_size = 64 * 1024 * 1024; +pub const default_sector_size = 0x200; diff --git a/src/cpu.zig b/src/cpu.zig new file mode 100644 index 0000000..5b15f49 --- /dev/null +++ b/src/cpu.zig @@ -0,0 +1,435 @@ +const lib = @import("lib"); +const Allocator = lib.Allocator; +const assert = lib.assert; +const log = lib.log; + +const bootloader = @import("bootloader"); + +const privileged = @import("privileged"); +const CPUPageTables = privileged.arch.CPUPageTables; +const Mapping = privileged.Mapping; +const PageAllocatorInterface = privileged.PageAllocator; +const PhysicalAddress = lib.PhysicalAddress; +const PhysicalAddressSpace = lib.PhysicalAddressSpace; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const stopCPU = privileged.arch.stopCPU; +const VirtualAddress = privileged.VirtualAddress; +const VirtualMemoryRegion = privileged.VirtualMemoryRegion; + +const rise = @import("rise"); + +pub const test_runner = @import("cpu/test_runner.zig"); +pub const arch = @import("cpu/arch.zig"); +pub const capabilities = @import("cpu/capabilities.zig"); + +pub export var stack: [0x8000]u8 align(0x1000) = undefined; +pub export var page_allocator = PageAllocator{ + .head = null, + .list_allocator = .{ + .u = .{ + .primitive = .{ + .backing_4k_page = undefined, + .allocated = 0, + }, + }, + .primitive = true, + }, +}; + +pub var bundle: []const u8 = &.{}; +pub var bundle_files: []const u8 = &.{}; + +pub export var user_scheduler: *UserScheduler = undefined; +pub export var driver: *align(lib.arch.valid_page_sizes[0]) Driver = undefined; +pub export var page_tables: CPUPageTables = undefined; +pub var file: []align(lib.default_sector_size) const u8 = undefined; +pub export var core_id: u32 = 0; +pub export var bsp = false; +var panic_lock = lib.Spinlock.released; + +/// This data structure holds the information needed to run a core +pub const Driver = extern struct { + init_root_capability: capabilities.RootDescriptor, + valid: bool, + padding: [padding_byte_count]u8 = .{0} ** padding_byte_count, + const padding_byte_count = lib.arch.valid_page_sizes[0] - @sizeOf(bool) - @sizeOf(capabilities.RootDescriptor); + + pub inline fn getRootCapability(drv: *Driver) *capabilities.Root { + return drv.init_root_capability.value; + } + + comptime { + // @compileLog(@sizeOf(Driver)); + assert(lib.isAligned(@sizeOf(Driver), lib.arch.valid_page_sizes[0])); + } +}; + +/// This data structure holds the information needed to run a program in a core (cpu side) +pub const UserScheduler = extern struct { + capability_root_node: capabilities.Root, + common: *rise.UserScheduler, + padding: [padding_byte_count]u8 = .{0} ** padding_byte_count, + + const total_size = @sizeOf(capabilities.Root) + @sizeOf(*rise.UserScheduler); + const aligned_size = lib.alignForward(usize, total_size, lib.arch.valid_page_sizes[0]); + const padding_byte_count = aligned_size - total_size; + + comptime { + if (padding_byte_count == 0 and @hasField(UserScheduler, "padding")) { + @compileError("remove padding because it is not necessary"); + } + } +}; + +const print_stack_trace = false; +var panic_count: usize = 0; + +inline fn panicPrologue(comptime format: []const u8, arguments: anytype) !void { + panic_count += 1; + privileged.arch.disableInterrupts(); + if (panic_count == 1) panic_lock.acquire(); + + try writer.writeAll(lib.Color.get(.bold)); + try writer.writeAll(lib.Color.get(.red)); + try writer.writeAll("[CPU DRIVER] [PANIC] "); + try writer.writeAll(lib.Color.get(.reset)); + try writer.print(format, arguments); + try writer.writeByte('\n'); +} + +inline fn panicEpilogue() noreturn { + if (panic_count == 1) panic_lock.release(); + + shutdown(.failure); +} + +// inline fn printStackTrace(maybe_stack_trace: ?*lib.StackTrace) !void { +// if (maybe_stack_trace) |stack_trace| { +// var debug_info = try getDebugInformation(); +// try writer.writeAll("Stack trace:\n"); +// var frame_index: usize = 0; +// var frames_left: usize = @min(stack_trace.index, stack_trace.instruction_addresses.len); +// +// while (frames_left != 0) : ({ +// frames_left -= 1; +// frame_index = (frame_index + 1) % stack_trace.instruction_addresses.len; +// }) { +// const return_address = stack_trace.instruction_addresses[frame_index]; +// try writer.print("[{}] ", .{frame_index}); +// try printSourceAtAddress(&debug_info, return_address); +// } +// } else { +// try writer.writeAll("Stack trace not available\n"); +// } +// } + +// inline fn printStackTraceFromStackIterator(return_address: usize, frame_address: usize) !void { +// var debug_info = try getDebugInformation(); +// var stack_iterator = lib.StackIterator.init(return_address, frame_address); +// var frame_index: usize = 0; +// try writer.writeAll("Stack trace:\n"); +// +// try printSourceAtAddress(&debug_info, return_address); +// while (stack_iterator.next()) |address| : (frame_index += 1) { +// try writer.print("[{}] ", .{frame_index}); +// try printSourceAtAddress(&debug_info, address); +// } +// } + +// fn printSourceAtAddress(debug_info: *lib.ModuleDebugInfo, address: usize) !void { +// if (debug_info.findCompileUnit(address)) |compile_unit| { +// const symbol = .{ +// .symbol_name = debug_info.getSymbolName(address) orelse "???", +// .compile_unit_name = compile_unit.die.getAttrString(debug_info, lib.dwarf.AT.name, debug_info.debug_str, compile_unit.*) catch "???", +// .line_info = debug_info.getLineNumberInfo(heap_allocator.toZig(), compile_unit.*, address) catch null, +// }; +// try writer.print("0x{x}: {s}!{s} {s}:{}:{}\n", .{ address, symbol.symbol_name, symbol.compile_unit_name, symbol.line_info.?.file_name, symbol.line_info.?.line, symbol.line_info.?.column }); +// } else |err| { +// return err; +// } +// } + +pub fn panicWithStackTrace(stack_trace: ?*lib.StackTrace, comptime format: []const u8, arguments: anytype) noreturn { + _ = stack_trace; + panicPrologue(format, arguments) catch {}; + // if (print_stack_trace) printStackTrace(stack_trace) catch {}; + panicEpilogue(); +} + +pub fn panicFromInstructionPointerAndFramePointer(return_address: usize, frame_address: usize, comptime format: []const u8, arguments: anytype) noreturn { + _ = frame_address; + _ = return_address; + panicPrologue(format, arguments) catch {}; + //if (print_stack_trace) printStackTraceFromStackIterator(return_address, frame_address) catch {}; + panicEpilogue(); +} + +pub fn panic(comptime format: []const u8, arguments: anytype) noreturn { + @call(.always_inline, panicFromInstructionPointerAndFramePointer, .{ @returnAddress(), @frameAddress(), format, arguments }); +} + +pub var syscall_count: usize = 0; + +pub inline fn shutdown(exit_code: lib.QEMU.ExitCode) noreturn { + log.debug("Printing stats...", .{}); + log.debug("Syscall count: {}", .{syscall_count}); + + privileged.shutdown(exit_code); +} + +pub const PageAllocator = extern struct { + head: ?*Entry, + list_allocator: ListAllocator, + total_allocated_size: u32 = 0, + + fn getPageAllocatorInterface(pa: *PageAllocator) PageAllocatorInterface { + return .{ + .allocate = callbackAllocate, + .context = pa, + .context_type = .cpu, + }; + } + + fn callbackAllocate(context: ?*anyopaque, size: u64, alignment: u64, options: PageAllocatorInterface.AllocateOptions) Allocator.Allocate.Error!PhysicalMemoryRegion { + _ = options; + const pa = @as(?*PageAllocator, @ptrCast(@alignCast(context))) orelse return Allocator.Allocate.Error.OutOfMemory; + const result = try pa.allocate(size, alignment); + return result; + } + + pub fn allocate(pa: *PageAllocator, size: u64, alignment: u64) Allocator.Allocate.Error!PhysicalMemoryRegion { + if (pa.head == null) { + @panic("head null"); + } + + const allocation = blk: { + var ptr = pa.head; + while (ptr) |entry| : (ptr = entry.next) { + if (lib.isAligned(entry.region.address.value(), alignment) and entry.region.size > size) { + const result = PhysicalMemoryRegion{ + .address = entry.region.address, + .size = size, + }; + entry.region.address = entry.region.address.offset(size); + entry.region.size -= size; + + pa.total_allocated_size += @as(u32, @intCast(size)); + // log.debug("Allocated 0x{x}", .{size}); + + break :blk result; + } + } + + ptr = pa.head; + + while (ptr) |entry| : (ptr = entry.next) { + const aligned_address = lib.alignForward(entry.region.address.value(), alignment); + const top = entry.region.top().value(); + if (aligned_address < top and top - aligned_address > size) { + // log.debug("Found region which we should be splitting: (0x{x}, 0x{x})", .{ entry.region.address.value(), entry.region.size }); + // log.debug("User asked for 0x{x} bytes with alignment 0x{x}", .{ size, alignment }); + // Split the addresses to obtain the desired result + const first_region_size = aligned_address - entry.region.address.value(); + const first_region_address = entry.region.address; + const first_region_next = entry.next; + + const second_region_address = aligned_address + size; + const second_region_size = top - aligned_address + size; + + const result = PhysicalMemoryRegion{ + .address = PhysicalAddress.new(aligned_address), + .size = size, + }; + + // log.debug("\nFirst region: (Address: 0x{x}. Size: 0x{x}).\nRegion in the middle (allocated): (Address: 0x{x}. Size: 0x{x}).\nSecond region: (Address: 0x{x}. Size: 0x{x})", .{ first_region_address, first_region_size, result.address.value(), result.size, second_region_address, second_region_size }); + + const new_entry = pa.list_allocator.get(); + entry.* = .{ + .region = .{ + .address = first_region_address, + .size = first_region_size, + }, + .next = new_entry, + }; + + new_entry.* = .{ + .region = .{ + .address = PhysicalAddress.new(second_region_address), + .size = second_region_size, + }, + .next = first_region_next, + }; + // log.debug("First entry: (Address: 0x{x}. Size: 0x{x})", .{ entry.region.address.value(), entry.region.size }); + // log.debug("Second entry: (Address: 0x{x}. Size: 0x{x})", .{ new_entry.region.address.value(), new_entry.region.size }); + + // pa.total_allocated_size += @intCast(u32, size); + // log.debug("Allocated 0x{x}", .{size}); + + break :blk result; + } + } + + log.err("Allocate error. Size: 0x{x}. Alignment: 0x{x}. Total allocated size: 0x{x}", .{ size, alignment, pa.total_allocated_size }); + return Allocator.Allocate.Error.OutOfMemory; + }; + + //log.debug("Physical allocation: 0x{x}, 0x{x}", .{ allocation.address.value(), allocation.size }); + + @memset(allocation.toHigherHalfVirtualAddress().access(u8), 0); + + return allocation; + } + + pub inline fn fromBSP(bootloader_information: *bootloader.Information) InitializationError!PageAllocator { + const memory_map_entries = bootloader_information.getMemoryMapEntries(); + const page_counters = bootloader_information.getPageCounters(); + + var total_size: usize = 0; + const page_shifter = lib.arch.page_shifter(lib.arch.valid_page_sizes[0]); + + for (memory_map_entries, page_counters) |entry, page_counter| { + if (entry.type != .usable or !lib.isAligned(entry.region.size, lib.arch.valid_page_sizes[0]) or entry.region.address.value() < lib.mb) { + continue; + } + + total_size += entry.region.size - (page_counter << page_shifter); + } + + const cpu_count = bootloader_information.smp.cpu_count; + const total_memory_to_take = total_size / cpu_count; + + // Look for a 4K page to host the memory map + const backing_4k_page = for (memory_map_entries, page_counters) |entry, *page_counter| { + const occupied_size = page_counter.* << page_shifter; + const entry_size_left = entry.region.size - occupied_size; + if (entry_size_left != 0) { + if (entry.type != .usable or !lib.isAligned(entry.region.size, lib.arch.valid_page_sizes[0]) or entry.region.address.value() < lib.mb) continue; + + assert(lib.isAligned(entry_size_left, lib.arch.valid_page_sizes[0])); + page_counter.* += 1; + break entry.region.address.offset(occupied_size); + } + } else return InitializationError.bootstrap_region_not_found; + + var memory_taken: usize = 0; + var backing_4k_page_memory_allocated: usize = 0; + + var last_entry: ?*Entry = null; + var first_entry: ?*Entry = null; + + for (memory_map_entries, page_counters) |entry, *page_counter| { + if (entry.type != .usable or !lib.isAligned(entry.region.size, lib.arch.valid_page_sizes[0]) or entry.region.address.value() < lib.mb) continue; + + const occupied_size = page_counter.* << page_shifter; + + if (occupied_size < entry.region.size) { + const entry_size_left = entry.region.size - occupied_size; + + var memory_taken_from_region: usize = 0; + while (memory_taken + memory_taken_from_region < total_memory_to_take) { + if (memory_taken_from_region == entry_size_left) break; + + const size_to_take = @min(2 * lib.mb, entry_size_left); + memory_taken_from_region += size_to_take; + } + + memory_taken += memory_taken_from_region; + + page_counter.* += @as(u32, @intCast(memory_taken_from_region >> page_shifter)); + const region_descriptor = .{ + .address = entry.region.offset(occupied_size).address, + .size = memory_taken_from_region, + }; + + if (backing_4k_page_memory_allocated >= lib.arch.valid_page_sizes[0]) return InitializationError.memory_exceeded; + const entry_address = backing_4k_page.offset(backing_4k_page_memory_allocated); + const new_entry = entry_address.toHigherHalfVirtualAddress().access(*Entry); + backing_4k_page_memory_allocated += @sizeOf(Entry); + + new_entry.* = .{ + .region = .{ + .address = region_descriptor.address, + .size = region_descriptor.size, + }, + .next = null, + }; + + if (last_entry) |e| { + e.next = new_entry; + } else { + first_entry = new_entry; + } + + last_entry = new_entry; + + if (memory_taken >= total_memory_to_take) break; + } + } + + const result = .{ + .head = first_entry, + .list_allocator = .{ + .u = .{ + .primitive = .{ + .backing_4k_page = backing_4k_page, + .allocated = backing_4k_page_memory_allocated, + }, + }, + .primitive = true, + }, + }; + + return result; + } + + const ListAllocator = extern struct { + u: extern union { + primitive: extern struct { + backing_4k_page: PhysicalAddress, + allocated: u64, + }, + normal: extern struct { + foo: u64, + }, + }, + primitive: bool, + + pub fn get(list_allocator: *ListAllocator) *Entry { + switch (list_allocator.primitive) { + true => { + if (list_allocator.u.primitive.allocated < 0x1000) { + const result = list_allocator.u.primitive.backing_4k_page.offset(list_allocator.u.primitive.allocated).toHigherHalfVirtualAddress().access(*Entry); + list_allocator.u.primitive.backing_4k_page = list_allocator.u.primitive.backing_4k_page.offset(@sizeOf(Entry)); + return result; + } else { + @panic("reached limit"); + } + }, + false => { + @panic("not primitive allocator not implemented"); + }, + } + } + }; + + pub const Entry = extern struct { + region: PhysicalMemoryRegion, + next: ?*Entry, + }; + + const InitializationError = error{ + bootstrap_region_not_found, + memory_exceeded, + }; +}; + +// fn getDebugInformation() !lib.ModuleDebugInfo { +// const debug_info = lib.getDebugInformation(heap_allocator.toZig(), file) catch |err| { +// try writer.print("Failed to get debug information: {}", .{err}); +// return err; +// }; +// +// return debug_info; +// } + +pub const writer = privileged.E9Writer{ .context = {} }; diff --git a/src/cpu/arch.zig b/src/cpu/arch.zig new file mode 100644 index 0000000..2f1010c --- /dev/null +++ b/src/cpu/arch.zig @@ -0,0 +1,13 @@ +const lib = @import("lib"); + +pub const current = switch (lib.cpu.arch) { + .x86_64 => x86_64, + else => @compileError("Architecture not supported"), +}; + +pub const x86_64 = @import("arch/x86_64.zig"); +pub usingnamespace current; + +pub const entryPoint = current.entryPoint; +pub const virtualAddressSpaceallocatePages = current.virtualAddressSpaceallocatePages; +pub const root_page_table_type = current.root_page_table_entry; diff --git a/src/cpu/arch/x86/64/init.zig b/src/cpu/arch/x86/64/init.zig new file mode 100644 index 0000000..aa37a3d --- /dev/null +++ b/src/cpu/arch/x86/64/init.zig @@ -0,0 +1,1492 @@ +const bootloader = @import("bootloader"); +const cpu = @import("cpu"); +const lib = @import("lib"); +const privileged = @import("privileged"); +const rise = @import("rise"); + +const Allocator = lib.Allocator; +const assert = lib.assert; +const ELF = lib.ELF(64); +const log = lib.log.scoped(.INIT); +const PhysicalAddress = lib.PhysicalAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const VirtualAddress = lib.VirtualAddress; +const VirtualMemoryRegion = lib.VirtualMemoryRegion; + +const panic = cpu.panic; +const x86_64 = cpu.arch.current; + +const paging = privileged.arch.paging; + +const APIC = privileged.arch.x86_64.APIC; +const cr0 = privileged.arch.x86_64.registers.cr0; +const cr3 = privileged.arch.x86_64.registers.cr3; +const cr4 = privileged.arch.x86_64.registers.cr4; +const XCR0 = privileged.arch.x86_64.registers.XCR0; +const IA32_APIC_BASE = privileged.arch.x86_64.registers.IA32_APIC_BASE; +const IA32_EFER = privileged.arch.x86_64.registers.IA32_EFER; +const IA32_FS_BASE = privileged.arch.x86_64.registers.IA32_FS_BASE; +const IA32_FSTAR = privileged.arch.x86_64.registers.IA32_FSTAR; +const IA32_FMASK = privileged.arch.x86_64.registers.IA32_FMASK; +const IA32_LSTAR = privileged.arch.x86_64.registers.IA32_LSTAR; +const IA32_STAR = privileged.arch.x86_64.registers.IA32_STAR; + +const user_scheduler_memory_start_virtual_address = VirtualAddress.new(0x200_000); +const user_scheduler_virtual_address = user_scheduler_memory_start_virtual_address; + +pub fn entryPoint() callconv(.Naked) noreturn { + asm volatile ( + \\lea stack(%rip), %rsp + \\add %[stack_len], %rsp + \\pushq $0 + \\mov %rsp, %rbp + \\jmp *%[main] + : + : [stack_len] "i" (cpu.stack.len), + [main] "{rax}" (&main), + : "rsp", "rbp" + ); + + unreachable; +} + +const InitializationError = error{ + feature_requested_and_not_available, + no_files, + cpu_file_not_found, + init_file_not_found, +}; + +noinline fn main(bootloader_information: *bootloader.Information) callconv(.C) noreturn { + log.info("Initializing...\n\n\t[BUILD MODE] {s}\n\t[BOOTLOADER] {s}\n\t[BOOT PROTOCOL] {s}\n", .{ @tagName(lib.build_mode), @tagName(bootloader_information.bootloader), @tagName(bootloader_information.protocol) }); + archInitialize(bootloader_information) catch |err| { + cpu.panicWithStackTrace(@errorReturnTrace(), "Failed to initialize CPU: {}", .{err}); + }; +} + +fn archInitialize(bootloader_information: *bootloader.Information) !noreturn { + // bootloader_information.draw_context.clearScreen(0xffff7f50); + // Do an integrity check so that the bootloader information is in perfect state and there is no weird memory behavior. + // This is mainly due to the transition from a 32-bit bootloader to a 64-bit CPU driver in the x86-64 architecture. + try bootloader_information.checkIntegrity(); + // Informing the bootloader information struct that we have reached the CPU driver and any bootloader + // functionality is not available anymore + bootloader_information.stage = .cpu; + // Check that the bootloader has loaded some files as the CPU driver needs them to go forward + cpu.bundle = bootloader_information.getSlice(.bundle); + if (cpu.bundle.len == 0) return InitializationError.no_files; + cpu.bundle_files = bootloader_information.getSlice(.file_list); + if (cpu.bundle_files.len == 0) return InitializationError.no_files; + + const cpuid = lib.arch.x86_64.cpuid; + if (x86_64.pcid) { + if (cpuid(1).ecx & (1 << 17) == 0) return InitializationError.feature_requested_and_not_available; + } + + if (x86_64.invariant_tsc) { + if (cpuid(0x80000007).edx & (1 << 8) == 0) return InitializationError.feature_requested_and_not_available; + } + + // Initialize GDT + const gdt_descriptor = x86_64.GDT.Descriptor{ + .limit = @sizeOf(x86_64.GDT) - 1, + .address = @intFromPtr(&gdt), + }; + + asm volatile ( + \\lgdt %[gdt] + \\mov %[ds], %rax + \\movq %rax, %ds + \\movq %rax, %es + \\movq %rax, %fs + \\movq %rax, %gs + \\movq %rax, %ss + \\pushq %[cs] + \\lea 1f(%rip), %rax + \\pushq %rax + \\lretq + \\1: + : + : [gdt] "*p" (&gdt_descriptor), + [ds] "i" (x86_64.data_64), + [cs] "i" (x86_64.code_64), + : "memory" + ); + + const tss_address = @intFromPtr(&tss); + gdt.tss_descriptor = .{ + .limit_low = @as(u16, @truncate(@sizeOf(x86_64.TSS))), + .base_low = @as(u16, @truncate(tss_address)), + .base_mid_low = @as(u8, @truncate(tss_address >> 16)), + .access = .{ + .type = .tss_available, + .dpl = 0, + .present = true, + }, + .attributes = .{ + .limit = @as(u4, @truncate(@sizeOf(x86_64.TSS) >> 16)), + .available_for_system_software = false, + .granularity = false, + }, + .base_mid_high = @as(u8, @truncate(tss_address >> 24)), + .base_high = @as(u32, @truncate(tss_address >> 32)), + }; + + tss.rsp[0] = @intFromPtr(&interrupt_stack) + interrupt_stack.len; + asm volatile ( + \\ltr %[tss_selector] + : + : [tss_selector] "r" (@as(u16, x86_64.tss_selector)), + : "memory" + ); + + // Initialize IDT + + for (&idt.descriptors, interrupt_handlers, 0..) |*descriptor, interrupt_handler, i| { + const interrupt_address = @intFromPtr(interrupt_handler); + descriptor.* = .{ + .offset_low = @as(u16, @truncate(interrupt_address)), + .segment_selector = x86_64.code_64, + .flags = .{ + .ist = 0, + .type = if (i < 32) .trap_gate else .interrupt_gate, // TODO: I think this is not correct + .dpl = 0, + .present = true, + }, + .offset_mid = @as(u16, @truncate(interrupt_address >> 16)), + .offset_high = @as(u32, @truncate(interrupt_address >> 32)), + }; + } + + const idt_descriptor = x86_64.IDT.Descriptor{ + .limit = @sizeOf(x86_64.IDT) - 1, + .address = @intFromPtr(&idt), + }; + + asm volatile ( + \\lidt %[idt_descriptor] + : + : [idt_descriptor] "*p" (&idt_descriptor), + : "memory" + ); + + // Mask PIC + privileged.arch.io.write(u8, 0xa1, 0xff); + privileged.arch.io.write(u8, 0x21, 0xff); + + asm volatile ("sti" ::: "memory"); + + const star = IA32_STAR{ + .kernel_cs = x86_64.code_64, + .user_cs_anchor = x86_64.data_64, + }; + + comptime { + assert(x86_64.data_64 == star.kernel_cs + 8); + assert(star.user_cs_anchor == x86_64.user_data_64 - 8); + assert(star.user_cs_anchor == x86_64.user_code_64 - 16); + } + + star.write(); + + IA32_LSTAR.write(@intFromPtr(&cpu.arch.x86_64.syscall.entryPoint)); + const syscall_mask = privileged.arch.x86_64.registers.syscall_mask; + IA32_FMASK.write(syscall_mask); + + // Enable syscall extensions + var efer = IA32_EFER.read(); + efer.SCE = true; + efer.write(); + + // TODO: AVX + + const avx_xsave_cpuid = cpuid(1); + const avx_support = avx_xsave_cpuid.ecx & (1 << 28) != 0; + const xsave_support = avx_xsave_cpuid.ecx & (1 << 26) != 0; + const avx2_support = cpuid(7).ebx & (1 << 5) != 0; + log.debug("AVX: {}. AVX2: {}. XSAVE: {}. Can't enable them yet", .{ avx_support, avx2_support, xsave_support }); + + comptime { + assert(lib.arch.valid_page_sizes[0] == 0x1000); + } + + var my_cr4 = cr4.read(); + my_cr4.OSFXSR = true; + my_cr4.OSXMMEXCPT = true; + //my_cr4.OSXSAVE = true; + my_cr4.page_global_enable = true; + my_cr4.performance_monitoring_counter_enable = true; + my_cr4.write(); + + var my_cr0 = cr0.read(); + my_cr0.monitor_coprocessor = true; + my_cr0.emulation = false; + my_cr0.numeric_error = true; + my_cr0.task_switched = false; + my_cr0.write(); + + // The bootloader already mapped APIC, so it's not necessary to map it here + var ia32_apic_base = IA32_APIC_BASE.read(); + cpu.bsp = ia32_apic_base.bsp; + ia32_apic_base.global_enable = true; + + const spurious_vector: u8 = 0xFF; + APIC.write(.spurious, @as(u32, 0x100) | spurious_vector); + + const tpr = APIC.TaskPriorityRegister{}; + tpr.write(); + + const lvt_timer = APIC.LVTTimer{}; + lvt_timer.write(); + + ia32_apic_base.write(); + + x86_64.ticks_per_ms = APIC.calibrateTimer(); + + cpu.core_id = APIC.read(.id); + + asm volatile ( + \\fninit + // TODO: figure out why this crashes with KVM + //\\ldmxcsr %[mxcsr] + :: //[mxcsr] "m" (@as(u32, 0x1f80)), + : "memory"); + + // Write user TLS base address + IA32_FS_BASE.write(user_scheduler_virtual_address.value()); + + // TODO: configure PAT + + try initialize(bootloader_information); +} + +fn initialize(bootloader_information: *bootloader.Information) !noreturn { + const memory_map_entries = bootloader_information.getMemoryMapEntries(); + const page_counters = bootloader_information.getPageCounters(); + + var free_size: usize = 0; + var free_region_count: usize = 0; + + for (memory_map_entries, page_counters) |mmap_entry, page_counter| { + if (mmap_entry.type == .usable) { + const free_region = mmap_entry.getFreeRegion(page_counter); + free_size += free_region.size; + free_region_count += @intFromBool(free_region.size > 0); + } + } + + const total_to_allocate = @sizeOf(cpu.Driver) + @sizeOf(cpu.capabilities.Root) + lib.arch.valid_page_sizes[0]; + + const total_physical: struct { + region: PhysicalMemoryRegion, + free_size: u64, + index: usize, + } = for (memory_map_entries, page_counters, 0..) |mmap_entry, page_counter, index| { + if (mmap_entry.type == .usable) { + const free_region = mmap_entry.getFreeRegion(page_counter); + if (free_region.size >= total_to_allocate) { + break .{ + .region = PhysicalMemoryRegion.new(.{ + .address = free_region.address, + .size = total_to_allocate, + }), + .free_size = free_region.size - total_to_allocate, + .index = index, + }; + } + } + } else @panic("Total physical region not found"); + + var offset: usize = 0; + + cpu.driver = total_physical.region.offset(offset).address.toHigherHalfVirtualAddress().access(*align(lib.arch.valid_page_sizes[0]) cpu.Driver); + offset += @sizeOf(cpu.Driver); + + const root_capability = total_physical.region.offset(offset).address.toHigherHalfVirtualAddress().access(*cpu.capabilities.Root); + offset += @sizeOf(cpu.capabilities.Root); + + var heap_offset: usize = 0; + const heap_region = total_physical.region.offset(offset); + assert(heap_region.size == lib.arch.valid_page_sizes[0]); + const host_free_ram = heap_region.offset(heap_offset).address.toHigherHalfVirtualAddress().access(*cpu.capabilities.RAM.Region); + host_free_ram.* = .{ + .region = PhysicalMemoryRegion.new(.{ + .address = total_physical.region.offset(total_to_allocate).address, + .size = total_physical.free_size, + }), + }; + heap_offset += @sizeOf(cpu.capabilities.RAM.Region); + const privileged_cpu_memory = heap_region.offset(heap_offset).address.toHigherHalfVirtualAddress().access(*cpu.capabilities.RAM.Region); + privileged_cpu_memory.* = .{ + .region = total_physical.region, + }; + + heap_offset += @sizeOf(cpu.capabilities.RAM); + + var previous_free_ram = host_free_ram; + for (memory_map_entries, page_counters, 0..) |memory_map_entry, page_counter, index| { + if (index == total_physical.index) continue; + + if (memory_map_entry.type == .usable) { + const region = memory_map_entry.getFreeRegion(page_counter); + if (region.size > 0) { + const new_free_ram = heap_region.offset(heap_offset).address.toHigherHalfVirtualAddress().access(*cpu.capabilities.RAM.Region); + heap_offset += @sizeOf(cpu.capabilities.RAM.Region); + new_free_ram.* = .{ + .region = region, + }; + previous_free_ram.next = new_free_ram; + previous_free_ram = new_free_ram; + } + } + } + + root_capability.* = .{ + .static = .{ + .cpu = true, + .boot = true, + .process = true, + }, + .dynamic = .{ + .io = .{ + .debug = true, + }, + .ram = .{ + .lists = blk: { + var lists = [1]?*cpu.capabilities.RAM.Region{null} ** lib.arch.reverse_valid_page_sizes.len; + var free_ram_iterator: ?*cpu.capabilities.RAM.Region = host_free_ram; + while (free_ram_iterator) |free_ram| { + comptime assert(lib.arch.reverse_valid_page_sizes.len == 3); + const next = free_ram.next; + + if (free_ram.region.size >= lib.arch.reverse_valid_page_sizes[0]) { + const previous_first = lists[0]; + lists[0] = free_ram; + free_ram.next = previous_first; + } else if (free_ram.region.size >= lib.arch.reverse_valid_page_sizes[1]) { + const previous_first = lists[1]; + lists[1] = free_ram; + free_ram.next = previous_first; + } else if (free_ram.region.size >= lib.arch.reverse_valid_page_sizes[2]) { + const previous_first = lists[2]; + lists[2] = free_ram; + free_ram.next = previous_first; + } else unreachable; + + free_ram_iterator = next; + } + + break :blk lists; + }, + }, + .cpu_memory = .{ + .flags = .{ + .allocate = true, + }, + }, + .page_table = .{}, + }, + .scheduler = .{ + .memory = undefined, + }, + .heap = cpu.capabilities.Root.Heap.new(heap_region, heap_offset), + }; + + cpu.driver.* = .{ + .valid = true, + .init_root_capability = .{ + .value = root_capability, + }, + }; + + switch (cpu.bsp) { + true => { + const init_module_descriptor = try bootloader_information.getFileDescriptor("init"); + try spawnInitBSP(init_module_descriptor.content, bootloader_information.cpu_page_tables); + }, + false => @panic("Implement APP"), + } +} + +export var interrupt_stack: [0x1000]u8 align(lib.arch.stack_alignment) = undefined; +export var gdt = x86_64.GDT{}; +export var tss = x86_64.TSS{}; +export var idt = x86_64.IDT{}; +export var user_stack: u64 = 0; + +comptime { + assert(rise.arch.user_code_selector == x86_64.user_code_selector); + assert(rise.arch.user_data_selector == x86_64.user_data_selector); +} + +pub fn InterruptHandler(comptime interrupt_number: u64, comptime has_error_code: bool) fn () callconv(.Naked) noreturn { + return struct { + fn handler() callconv(.Naked) noreturn { + asm volatile ( + \\endbr64 + ::: "memory"); + + if (x86_64.smap) { + // TODO: Investigate why this is Exception #6 + asm volatile ( + \\clac + ::: "memory"); + } + + asm volatile ( + \\cld + ::: "memory"); + + if (!has_error_code) { + asm volatile ("pushq $0" ::: "memory"); + } + + asm volatile ( + \\push %rdi + \\push %rsi + \\push %rdx + \\push %rcx + \\push %rax + \\push %r8 + \\push %r9 + \\push %r10 + \\push %r11 + \\push %rbx + \\push %rbp + \\push %r12 + \\push %r13 + \\push %r14 + \\push %r15 + \\mov %rsp, %rdi + \\mov %[interrupt_number], %rsi + \\call interruptHandler + \\pop %r15 + \\pop %r14 + \\pop %r13 + \\pop %r12 + \\pop %rbp + \\pop %rbx + \\pop %r11 + \\pop %r10 + \\pop %r9 + \\pop %r8 + \\pop %rax + \\pop %rcx + \\pop %rdx + \\pop %rsi + \\pop %rdi + : + : [interrupt_number] "i" (interrupt_number), + : "memory" + ); + + if (!has_error_code) { + asm volatile ( + \\add $0x8, %rsp + ::: "memory"); + } + + asm volatile ( + \\iretq + \\int3 + ::: "memory"); + + unreachable; + } + }.handler; +} + +const Interrupt = enum(u5) { + DE = 0x00, + DB = 0x01, + NMI = 0x02, + BP = 0x03, + OF = 0x04, + BR = 0x05, + UD = 0x06, + NM = 0x07, + DF = 0x08, + CSO = 0x09, // Not used anymore + TS = 0x0A, + NP = 0x0B, + SS = 0x0C, + GP = 0x0D, + PF = 0x0E, + MF = 0x10, + AC = 0x11, + MC = 0x12, + XM = 0x13, + VE = 0x14, + CP = 0x15, + _, +}; +const interrupt_handlers = [256]*const fn () callconv(.Naked) noreturn{ + InterruptHandler(@intFromEnum(Interrupt.DE), false), + InterruptHandler(@intFromEnum(Interrupt.DB), false), + InterruptHandler(@intFromEnum(Interrupt.NMI), false), + InterruptHandler(@intFromEnum(Interrupt.BP), false), + InterruptHandler(@intFromEnum(Interrupt.OF), false), + InterruptHandler(@intFromEnum(Interrupt.BR), false), + InterruptHandler(@intFromEnum(Interrupt.UD), false), + InterruptHandler(@intFromEnum(Interrupt.NM), false), + InterruptHandler(@intFromEnum(Interrupt.DF), true), + InterruptHandler(@intFromEnum(Interrupt.CSO), false), + InterruptHandler(@intFromEnum(Interrupt.TS), true), + InterruptHandler(@intFromEnum(Interrupt.NP), true), + InterruptHandler(@intFromEnum(Interrupt.SS), true), + InterruptHandler(@intFromEnum(Interrupt.GP), true), + InterruptHandler(@intFromEnum(Interrupt.PF), true), + InterruptHandler(0x0f, false), + InterruptHandler(@intFromEnum(Interrupt.MF), false), + InterruptHandler(@intFromEnum(Interrupt.AC), true), + InterruptHandler(@intFromEnum(Interrupt.MC), false), + InterruptHandler(@intFromEnum(Interrupt.XM), false), + InterruptHandler(@intFromEnum(Interrupt.VE), false), + InterruptHandler(@intFromEnum(Interrupt.CP), true), + InterruptHandler(0x16, false), + InterruptHandler(0x17, false), + InterruptHandler(0x18, false), + InterruptHandler(0x19, false), + InterruptHandler(0x1a, false), + InterruptHandler(0x1b, false), + InterruptHandler(0x1c, false), + InterruptHandler(0x1d, false), + InterruptHandler(0x1e, false), + InterruptHandler(0x1f, false), + InterruptHandler(0x20, false), + InterruptHandler(0x21, false), + InterruptHandler(0x22, false), + InterruptHandler(0x23, false), + InterruptHandler(0x24, false), + InterruptHandler(0x25, false), + InterruptHandler(0x26, false), + InterruptHandler(0x27, false), + InterruptHandler(0x28, false), + InterruptHandler(0x29, false), + InterruptHandler(0x2a, false), + InterruptHandler(0x2b, false), + InterruptHandler(0x2c, false), + InterruptHandler(0x2d, false), + InterruptHandler(0x2e, false), + InterruptHandler(0x2f, false), + InterruptHandler(0x30, false), + InterruptHandler(0x31, false), + InterruptHandler(0x32, false), + InterruptHandler(0x33, false), + InterruptHandler(0x34, false), + InterruptHandler(0x35, false), + InterruptHandler(0x36, false), + InterruptHandler(0x37, false), + InterruptHandler(0x38, false), + InterruptHandler(0x39, false), + InterruptHandler(0x3a, false), + InterruptHandler(0x3b, false), + InterruptHandler(0x3c, false), + InterruptHandler(0x3d, false), + InterruptHandler(0x3e, false), + InterruptHandler(0x3f, false), + InterruptHandler(0x40, false), + InterruptHandler(0x41, false), + InterruptHandler(0x42, false), + InterruptHandler(0x43, false), + InterruptHandler(0x44, false), + InterruptHandler(0x45, false), + InterruptHandler(0x46, false), + InterruptHandler(0x47, false), + InterruptHandler(0x48, false), + InterruptHandler(0x49, false), + InterruptHandler(0x4a, false), + InterruptHandler(0x4b, false), + InterruptHandler(0x4c, false), + InterruptHandler(0x4d, false), + InterruptHandler(0x4e, false), + InterruptHandler(0x4f, false), + InterruptHandler(0x50, false), + InterruptHandler(0x51, false), + InterruptHandler(0x52, false), + InterruptHandler(0x53, false), + InterruptHandler(0x54, false), + InterruptHandler(0x55, false), + InterruptHandler(0x56, false), + InterruptHandler(0x57, false), + InterruptHandler(0x58, false), + InterruptHandler(0x59, false), + InterruptHandler(0x5a, false), + InterruptHandler(0x5b, false), + InterruptHandler(0x5c, false), + InterruptHandler(0x5d, false), + InterruptHandler(0x5e, false), + InterruptHandler(0x5f, false), + InterruptHandler(0x60, false), + InterruptHandler(0x61, false), + InterruptHandler(0x62, false), + InterruptHandler(0x63, false), + InterruptHandler(0x64, false), + InterruptHandler(0x65, false), + InterruptHandler(0x66, false), + InterruptHandler(0x67, false), + InterruptHandler(0x68, false), + InterruptHandler(0x69, false), + InterruptHandler(0x6a, false), + InterruptHandler(0x6b, false), + InterruptHandler(0x6c, false), + InterruptHandler(0x6d, false), + InterruptHandler(0x6e, false), + InterruptHandler(0x6f, false), + InterruptHandler(0x70, false), + InterruptHandler(0x71, false), + InterruptHandler(0x72, false), + InterruptHandler(0x73, false), + InterruptHandler(0x74, false), + InterruptHandler(0x75, false), + InterruptHandler(0x76, false), + InterruptHandler(0x77, false), + InterruptHandler(0x78, false), + InterruptHandler(0x79, false), + InterruptHandler(0x7a, false), + InterruptHandler(0x7b, false), + InterruptHandler(0x7c, false), + InterruptHandler(0x7d, false), + InterruptHandler(0x7e, false), + InterruptHandler(0x7f, false), + InterruptHandler(0x80, false), + InterruptHandler(0x81, false), + InterruptHandler(0x82, false), + InterruptHandler(0x83, false), + InterruptHandler(0x84, false), + InterruptHandler(0x85, false), + InterruptHandler(0x86, false), + InterruptHandler(0x87, false), + InterruptHandler(0x88, false), + InterruptHandler(0x89, false), + InterruptHandler(0x8a, false), + InterruptHandler(0x8b, false), + InterruptHandler(0x8c, false), + InterruptHandler(0x8d, false), + InterruptHandler(0x8e, false), + InterruptHandler(0x8f, false), + InterruptHandler(0x90, false), + InterruptHandler(0x91, false), + InterruptHandler(0x92, false), + InterruptHandler(0x93, false), + InterruptHandler(0x94, false), + InterruptHandler(0x95, false), + InterruptHandler(0x96, false), + InterruptHandler(0x97, false), + InterruptHandler(0x98, false), + InterruptHandler(0x99, false), + InterruptHandler(0x9a, false), + InterruptHandler(0x9b, false), + InterruptHandler(0x9c, false), + InterruptHandler(0x9d, false), + InterruptHandler(0x9e, false), + InterruptHandler(0x9f, false), + InterruptHandler(0xa0, false), + InterruptHandler(0xa1, false), + InterruptHandler(0xa2, false), + InterruptHandler(0xa3, false), + InterruptHandler(0xa4, false), + InterruptHandler(0xa5, false), + InterruptHandler(0xa6, false), + InterruptHandler(0xa7, false), + InterruptHandler(0xa8, false), + InterruptHandler(0xa9, false), + InterruptHandler(0xaa, false), + InterruptHandler(0xab, false), + InterruptHandler(0xac, false), + InterruptHandler(0xad, false), + InterruptHandler(0xae, false), + InterruptHandler(0xaf, false), + InterruptHandler(0xb0, false), + InterruptHandler(0xb1, false), + InterruptHandler(0xb2, false), + InterruptHandler(0xb3, false), + InterruptHandler(0xb4, false), + InterruptHandler(0xb5, false), + InterruptHandler(0xb6, false), + InterruptHandler(0xb7, false), + InterruptHandler(0xb8, false), + InterruptHandler(0xb9, false), + InterruptHandler(0xba, false), + InterruptHandler(0xbb, false), + InterruptHandler(0xbc, false), + InterruptHandler(0xbd, false), + InterruptHandler(0xbe, false), + InterruptHandler(0xbf, false), + InterruptHandler(0xc0, false), + InterruptHandler(0xc1, false), + InterruptHandler(0xc2, false), + InterruptHandler(0xc3, false), + InterruptHandler(0xc4, false), + InterruptHandler(0xc5, false), + InterruptHandler(0xc6, false), + InterruptHandler(0xc7, false), + InterruptHandler(0xc8, false), + InterruptHandler(0xc9, false), + InterruptHandler(0xca, false), + InterruptHandler(0xcb, false), + InterruptHandler(0xcc, false), + InterruptHandler(0xcd, false), + InterruptHandler(0xce, false), + InterruptHandler(0xcf, false), + InterruptHandler(0xd0, false), + InterruptHandler(0xd1, false), + InterruptHandler(0xd2, false), + InterruptHandler(0xd3, false), + InterruptHandler(0xd4, false), + InterruptHandler(0xd5, false), + InterruptHandler(0xd6, false), + InterruptHandler(0xd7, false), + InterruptHandler(0xd8, false), + InterruptHandler(0xd9, false), + InterruptHandler(0xda, false), + InterruptHandler(0xdb, false), + InterruptHandler(0xdc, false), + InterruptHandler(0xdd, false), + InterruptHandler(0xde, false), + InterruptHandler(0xdf, false), + InterruptHandler(0xe0, false), + InterruptHandler(0xe1, false), + InterruptHandler(0xe2, false), + InterruptHandler(0xe3, false), + InterruptHandler(0xe4, false), + InterruptHandler(0xe5, false), + InterruptHandler(0xe6, false), + InterruptHandler(0xe7, false), + InterruptHandler(0xe8, false), + InterruptHandler(0xe9, false), + InterruptHandler(0xea, false), + InterruptHandler(0xeb, false), + InterruptHandler(0xec, false), + InterruptHandler(0xed, false), + InterruptHandler(0xee, false), + InterruptHandler(0xef, false), + InterruptHandler(0xf0, false), + InterruptHandler(0xf1, false), + InterruptHandler(0xf2, false), + InterruptHandler(0xf3, false), + InterruptHandler(0xf4, false), + InterruptHandler(0xf5, false), + InterruptHandler(0xf6, false), + InterruptHandler(0xf7, false), + InterruptHandler(0xf8, false), + InterruptHandler(0xf9, false), + InterruptHandler(0xfa, false), + InterruptHandler(0xfb, false), + InterruptHandler(0xfc, false), + InterruptHandler(0xfd, false), + InterruptHandler(0xfe, false), + InterruptHandler(0xff, false), +}; + +const BSPEarlyAllocator = extern struct { + base: PhysicalAddress, + size: usize, + offset: usize, + allocator: Allocator = .{ + .callbacks = .{ + .allocate = callbackAllocate, + }, + }, + heap_first: ?*BSPHeapEntry = null, + + const BSPHeapEntry = extern struct { + virtual_memory_region: VirtualMemoryRegion, + offset: usize = 0, + next: ?*BSPHeapEntry = null, + + // pub fn create(heap: *BSPHeapEntry, comptime T: type) !*T { + // _ = heap; + // @panic("TODO: create"); + // } + + pub fn allocateBytes(heap: *BSPHeapEntry, size: u64, alignment: u64) ![]u8 { + assert(alignment < lib.arch.valid_page_sizes[0]); + assert(heap.virtual_memory_region.size > size); + if (!lib.isAligned(heap.virtual_memory_region.address.value(), alignment)) { + const misalignment = lib.alignForward(usize, heap.virtual_memory_region.address.value(), alignment) - heap.virtual_memory_region.address.value(); + _ = heap.virtual_memory_region.takeSlice(misalignment); + } + + return heap.virtual_memory_region.takeByteSlice(size); + } + }; + + pub fn createPageAligned(allocator: *BSPEarlyAllocator, comptime T: type) AllocatorError!*align(lib.arch.valid_page_sizes[0]) T { + return @as(*align(lib.arch.valid_page_sizes[0]) T, @ptrCast(try allocator.allocateBytes(@sizeOf(T), lib.arch.valid_page_sizes[0]))); + } + + pub fn allocateBytes(allocator: *BSPEarlyAllocator, size: u64, alignment: u64) AllocatorError![]align(lib.arch.valid_page_sizes[0]) u8 { + if (!lib.isAligned(size, lib.arch.valid_page_sizes[0])) return AllocatorError.bad_alignment; + if (allocator.offset + size > allocator.size) return AllocatorError.out_of_memory; + + // TODO: don't trash memory + if (!lib.isAligned(allocator.base.offset(allocator.offset).value(), alignment)) { + const aligned = lib.alignForward(usize, allocator.base.offset(allocator.offset).value(), alignment); + allocator.offset += aligned - allocator.base.offset(allocator.offset).value(); + } + + const physical_address = allocator.base.offset(allocator.offset); + allocator.offset += size; + const slice = physical_address.toHigherHalfVirtualAddress().access([*]align(lib.arch.valid_page_sizes[0]) u8)[0..size]; + @memset(slice, 0); + + return slice; + } + + pub fn callbackAllocate(allocator: *Allocator, size: u64, alignment: u64) Allocator.Allocate.Error!Allocator.Allocate.Result { + const early_allocator = @fieldParentPtr(BSPEarlyAllocator, "allocator", allocator); + if (alignment == lib.arch.valid_page_sizes[0] or size % lib.arch.valid_page_sizes[0] == 0) { + const result = early_allocator.allocateBytes(size, alignment) catch return Allocator.Allocate.Error.OutOfMemory; + return .{ + .address = @intFromPtr(result.ptr), + .size = result.len, + }; + } else if (alignment > lib.arch.valid_page_sizes[0]) { + @panic("WTF"); + } else { + assert(size < lib.arch.valid_page_sizes[0]); + const heap_entry_allocation = early_allocator.allocateBytes(lib.arch.valid_page_sizes[0], lib.arch.valid_page_sizes[0]) catch return Allocator.Allocate.Error.OutOfMemory; + const heap_entry_region = VirtualMemoryRegion.fromByteSlice(.{ + .slice = heap_entry_allocation, + }); + const heap_entry = try early_allocator.addHeapRegion(heap_entry_region); + const result = try heap_entry.allocateBytes(size, alignment); + return .{ + .address = @intFromPtr(result.ptr), + .size = result.len, + }; + } + } + + inline fn addHeapRegion(early_allocator: *BSPEarlyAllocator, region: VirtualMemoryRegion) !*BSPHeapEntry { + const heap_entry = region.address.access(*BSPHeapEntry); + const offset = @sizeOf(BSPHeapEntry); + heap_entry.* = .{ + .offset = offset, + .virtual_memory_region = region.offset(offset), + .next = early_allocator.heap_first, + }; + + early_allocator.heap_first = heap_entry; + + return heap_entry; + } + const AllocatorError = error{ + out_of_memory, + bad_alignment, + }; +}; + +const half_page_table_entry_count = @divExact(paging.page_table_entry_count, 2); + +fn spawnInitBSP(init_file: []const u8, cpu_page_tables: paging.CPUPageTables) !noreturn { + const spawn_init = try spawnInitCommon(cpu_page_tables); + const init_scheduler = spawn_init.scheduler; + const page_table_regions = spawn_init.page_table_regions; + + // TODO: make this the right one + const address_space = page_table_regions.getAddressSpace(); + const init_elf = try ELF.Parser.init(init_file); + const entry_point = init_elf.getEntryPoint(); + const program_headers = init_elf.getProgramHeaders(); + const scheduler_common = init_scheduler.common; + + for (program_headers) |program_header| { + if (program_header.type == .load) { + const aligned_size = lib.alignForward(usize, program_header.size_in_memory, lib.arch.valid_page_sizes[0]); + const segment_virtual_address = VirtualAddress.new(program_header.virtual_address); + const indexed_virtual_address = @as(paging.IndexedVirtualAddress, @bitCast(program_header.virtual_address)); + _ = indexed_virtual_address; + const segment_flags = .{ + .execute_disable = !program_header.flags.executable, + .write = program_header.flags.writable, + .user = true, + }; + + const segment_physical_region = try cpu.driver.getRootCapability().allocatePages(aligned_size); + try page_table_regions.map(segment_virtual_address, segment_physical_region.address, segment_physical_region.size, segment_flags); + + const src = init_file[program_header.offset..][0..program_header.size_in_file]; + const dst = segment_physical_region.toHigherHalfVirtualAddress().access(u8)[0..program_header.size_in_file]; + @memcpy(dst, src); + } + } + + // Once all page tables are set up, copy lower half of the address space to the cpu page table + const cpu_pml4 = page_table_regions.getCpuPML4(); + const user_pml4 = page_table_regions.getUserPML4(); + @memcpy(cpu_pml4[0..half_page_table_entry_count], user_pml4[0..half_page_table_entry_count]); + + cpu.user_scheduler = init_scheduler; + address_space.cr3.write(); + + scheduler_common.self = scheduler_common; + + const scheduler_common_arch = scheduler_common.architectureSpecific(); + + // Set arguments + + // First argument + scheduler_common_arch.disabled_save_area.registers.rdi = user_scheduler_virtual_address.value(); + // Second argument + const is_init = true; + scheduler_common_arch.disabled_save_area.registers.rsi = @intFromBool(is_init); + + scheduler_common_arch.disabled_save_area.registers.rip = entry_point; // Set entry point + scheduler_common_arch.disabled_save_area.registers.rsp = user_scheduler_virtual_address.offset(@offsetOf(rise.UserScheduler, "setup_stack")).value() + scheduler_common_arch.generic.setup_stack.len; + scheduler_common.setup_stack_lock.value = true; + scheduler_common_arch.disabled_save_area.registers.rflags = .{ .IF = true }; // Set RFLAGS + + scheduler_common_arch.disabled_save_area.fpu.fcw = 0x037f; // Set FPU + scheduler_common_arch.disabled_save_area.fpu.mxcsr = 0x1f80; + + scheduler_common_arch.disabled_save_area.contextSwitch(); +} + +const UserMemory = extern struct { + root: PhysicalMemoryRegion, + pdpt: PhysicalMemoryRegion, + pdt: PhysicalMemoryRegion, + pt: PhysicalMemoryRegion, +}; + +const PageTableRegions = extern struct { + regions: [region_count]PhysicalMemoryRegion, + total: PhysicalMemoryRegion, + base_virtual_address: VirtualAddress, + + fn mapQuick(page_table_regions: PageTableRegions, virtual_address: VirtualAddress, physical_address: PhysicalAddress, size: usize, flags: paging.MemoryFlags) void { + const ptes = page_table_regions.getPageTables(.{ .index = .pt }); + // log.debug("PTE base: 0x{x}", .{@ptrToInt(ptes.ptr)}); + assert(lib.isAligned(size, lib.arch.valid_page_sizes[0])); + const indexed = @as(paging.IndexedVirtualAddress, @bitCast(virtual_address.value())); + const base_indexed = @as(paging.IndexedVirtualAddress, @bitCast(page_table_regions.base_virtual_address.value())); + const physical_base = physical_address.value(); + var physical_iterator = physical_base; + const physical_top = physical_base + size; + const pd_offset_index = indexed.PD - base_indexed.PD; + // log.debug("PD index: {}. PD offset index: {}", .{ indexed.PD, pd_offset_index }); + var index = @as(usize, pd_offset_index) * paging.page_table_entry_count + indexed.PT; + // log.debug("Virtual address: 0x{x}. Size: 0x{x}. Index: {}. PD: {}. PT: {}", .{ virtual_address.value(), size, index, indexed.PD, indexed.PT }); + + while (physical_iterator < physical_top) : ({ + physical_iterator += lib.arch.valid_page_sizes[0]; + index += 1; + }) { + ptes[index] = paging.getPageEntry(paging.PTE, physical_iterator, flags); + } + } + + fn map(page_table_regions: PageTableRegions, virtual_address: VirtualAddress, physical_address: PhysicalAddress, size: usize, flags: paging.MemoryFlags) !void { + // log.debug("Mapping 0x{x} -> 0x{x} for 0x{x} bytes", .{ virtual_address.value(), physical_address.value(), size }); + assert(page_table_regions.regions[@intFromEnum(Index.pml4)].size == 2 * lib.arch.valid_page_sizes[0]); + assert(page_table_regions.regions[@intFromEnum(Index.pdp)].size == lib.arch.valid_page_sizes[0]); + assert(page_table_regions.regions[@intFromEnum(Index.pd)].size == lib.arch.valid_page_sizes[0]); + + page_table_regions.mapQuick(virtual_address, physical_address, size, flags); + + const address_space = page_table_regions.getAddressSpace(); + const virtual_address_top = virtual_address.offset(size).value(); + var index: usize = 0; + + while (virtual_address.offset(index * lib.arch.valid_page_sizes[0]).value() < virtual_address_top) : (index += 1) { + const offset = index * lib.arch.valid_page_sizes[0]; + const expected_pa = physical_address.offset(offset); + const va = virtual_address.offset(offset); + + const translated_physical_address = address_space.translateAddress(va, flags) catch |err| { + panic("Mapping of 0x{x} failed: {}", .{ va.value(), err }); + }; + + if (translated_physical_address.value() != expected_pa.value()) { + @panic("Mapping failed"); + } + } + } + + const region_count = lib.enumCount(Index); + const Index = enum(u2) { + pml4, + pdp, + pd, + pt, + }; + + const sizes = blk: { + const shifter = lib.arch.page_shifter(lib.arch.valid_page_sizes[0]); + var result: [region_count]comptime_int = undefined; + + for (&result, entry_count_array) |*size, entry_count| { + size.* = @divExact(entry_count, paging.page_table_entry_count) << shifter; + } + + break :blk result; + }; + + const total_size = blk: { + var result: comptime_int = 0; + + for (sizes) |size| { + result += size; + } + + break :blk result; + }; + + const entry_count_array = blk: { + var result: [region_count]comptime_int = undefined; + + result[@intFromEnum(Index.pml4)] = 2 * paging.page_table_entry_count; + result[@intFromEnum(Index.pdp)] = init_vas_pdpe_count; + result[@intFromEnum(Index.pd)] = init_vas_pde_count; + result[@intFromEnum(Index.pt)] = init_vas_pte_count; + + break :blk result; + }; + + const EntryType = blk: { + var result: [region_count]type = undefined; + result[@intFromEnum(Index.pml4)] = paging.PML4TE; + result[@intFromEnum(Index.pdp)] = paging.PDPTE; + result[@intFromEnum(Index.pd)] = paging.PDTE; + result[@intFromEnum(Index.pt)] = paging.PTE; + break :blk result; + }; + + const init_vas_size = 128 * lib.mb; + const init_vas_page_count = @divExact(init_vas_size, lib.arch.valid_page_sizes[0]); + + const init_vas_pte_count = init_vas_page_count; + const init_vas_pde_count = lib.alignForward(usize, @divExact(init_vas_pte_count, paging.page_table_entry_count), paging.page_table_entry_count); + const init_vas_pdpe_count = lib.alignForward(usize, @divExact(init_vas_pde_count, paging.page_table_entry_count), paging.page_table_entry_count); + + const AccessOptions = packed struct { + index: Index, + user: bool = true, + }; + + pub inline fn getPhysicalRegion(regions: PageTableRegions, comptime options: AccessOptions) PhysicalMemoryRegion { + const index = @intFromEnum(options.index); + const result = regions.regions[index].offset(switch (index) { + 0 => switch (options.user) { + true => paging.page_table_size, + false => 0, + }, + else => 0, + }); + + return switch (index) { + 0 => PhysicalMemoryRegion.new(.{ .address = result.address, .size = paging.page_table_size }), + else => result, + }; + } + + pub inline fn getPageTables(regions: PageTableRegions, comptime options: AccessOptions) []EntryType[@intFromEnum(options.index)] { + return regions.getPhysicalRegion(options).toHigherHalfVirtualAddress().access(EntryType[@intFromEnum(options.index)]); + } + + pub inline fn getAddressSpace(regions: PageTableRegions) paging.Specific { + const address_space = paging.Specific{ .cr3 = cr3.fromAddress(regions.getPhysicalRegion(.{ .index = .pml4, .user = true }).address) }; + return address_space; + } + + pub inline fn getPrivilegedAddressSpace(regions: PageTableRegions) paging.Specific { + const address_space = paging.Specific{ .cr3 = cr3.fromAddress(regions.getPhysicalRegion(.{ .index = .pml4, .user = false }).address) }; + return address_space; + } + + pub inline fn getCpuPML4(regions: PageTableRegions) *paging.PML4Table { + return regions.getPageTables(.{ .index = .pml4, .user = false })[0..paging.page_table_entry_count]; + } + + pub inline fn getUserPML4(regions: PageTableRegions) *paging.PML4Table { + return regions.getPageTables(.{ .index = .pml4, .user = true })[0..paging.page_table_entry_count]; + } +}; + +const SpawnInitCommonResult = extern struct { + page_table_regions: PageTableRegions, + scheduler: *cpu.UserScheduler, +}; + +const scheduler_memory_size = 1 << 19; +const dispatch_count = x86_64.IDT.entry_count; +var once: bool = false; + +fn spawnInitCommon(cpu_page_tables: paging.CPUPageTables) !SpawnInitCommonResult { + assert(!once); + once = true; + // TODO: delete in the future + assert(cpu.bsp); + cpu.driver.valid = true; + + const allocation: extern struct { + page_table_regions: PageTableRegions, + cpu_page_table_physical_region: PhysicalMemoryRegion, + } = blk: { + const page_table_regions_total_size = PageTableRegions.total_size; + const cpu_page_table_size = (paging.Level.count - 1) * paging.page_table_size; + const allocation_size = page_table_regions_total_size + cpu_page_table_size; + const allocation_alignment = 2 * paging.page_table_alignment; + const total_region = try cpu.driver.getRootCapability().allocatePageCustomAlignment(allocation_size, allocation_alignment); + //log.debug("Total region: (0x{x}, 0x{x})", .{ total_region.address.value(), total_region.top().value() }); + var region_slicer = total_region; + var page_table_regions = PageTableRegions{ + .regions = undefined, + .total = total_region, + .base_virtual_address = user_scheduler_virtual_address, + }; + + inline for (&page_table_regions.regions, 0..) |*region, index| { + region.* = region_slicer.takeSlice(PageTableRegions.sizes[index]); + } + + assert(lib.isAligned(page_table_regions.regions[0].address.value(), 2 * paging.page_table_alignment)); + + assert(region_slicer.size == cpu_page_table_size); + + const cpu_page_table_physical_region = region_slicer; + + break :blk .{ + .page_table_regions = page_table_regions, + .cpu_page_table_physical_region = cpu_page_table_physical_region, + }; + }; + + const page_table_regions = allocation.page_table_regions; + const cpu_page_table_physical_region = allocation.cpu_page_table_physical_region; + + const indexed_start = @as(paging.IndexedVirtualAddress, @bitCast(user_scheduler_virtual_address.value())); + const indexed_end = @as(paging.IndexedVirtualAddress, @bitCast(user_scheduler_virtual_address.offset(PageTableRegions.init_vas_size).value())); + // log.debug("Indexed start: {}", .{indexed_start}); + // log.debug("Indexed end: {}", .{indexed_end}); + page_table_regions.getPageTables(.{ + .index = .pml4, + .user = true, + })[indexed_start.PML4] = .{ + .present = true, + .write = true, + .user = true, + .address = paging.packAddress(paging.PML4TE, page_table_regions.getPhysicalRegion(.{ .index = .pdp }).address.value()), + }; + + page_table_regions.getPageTables(.{ .index = .pdp })[indexed_start.PDP] = .{ + .present = true, + .write = true, + .user = true, + .address = paging.packAddress(paging.PDPTE, page_table_regions.getPhysicalRegion(.{ .index = .pd }).address.value()), + }; + + const pdes = page_table_regions.getPageTables(.{ .index = .pd }); + // log.debug("PDE count: {}", .{pdes.len}); + //log.debug("PTE base: 0x{x}. PTE count: {}", .{ page_table_regions.get(.{ .index = .pt }).address.value(), page_table_regions.getPageTables(.{ .index = .pt }).len }); + + for (pdes[indexed_start.PD .. indexed_start.PD + indexed_end.PD], 0..) |*pde, pde_offset| { + const pte_index = paging.page_table_entry_count * pde_offset; + const pte_offset = pte_index * paging.page_table_entry_size; + const pte_address = page_table_regions.getPhysicalRegion(.{ .index = .pt }).offset(pte_offset).address.value(); + // log.debug("Linking PDE[{}] 0x{x} with PTE base address: 0x{x} (pte index: {}. pte offset: 0x{x})", .{ pde_offset, @ptrToInt(pde), pte_address, pte_index, pte_offset }); + pde.* = paging.PDTE{ + .present = true, + .write = true, + .user = true, + .address = paging.packAddress(paging.PDTE, pte_address), + }; + } + + const scheduler_memory_physical_region = try cpu.driver.getRootCapability().allocatePages(scheduler_memory_size); + const scheduler_memory_map_flags = .{ + .present = true, + .write = true, + .user = true, + .execute_disable = true, + }; + + try page_table_regions.map(user_scheduler_memory_start_virtual_address, scheduler_memory_physical_region.address, scheduler_memory_physical_region.size, scheduler_memory_map_flags); + + const root_page_tables = [2]PhysicalMemoryRegion{ + page_table_regions.getPhysicalRegion(.{ .index = .pml4, .user = false }), + page_table_regions.getPhysicalRegion(.{ .index = .pml4, .user = true }), + }; + // log.debug("Root page tables: {any}", .{root_page_tables}); + assert(root_page_tables[0].size == lib.arch.valid_page_sizes[0]); + + // Map CPU driver into the CPU page table + var cpu_page_table_physical_region_iterator = cpu_page_table_physical_region; + // log.debug("CPU page table physical region: 0x{x} - 0x{x}", .{ cpu_page_table_physical_region.address.value(), cpu_page_table_physical_region.top().value() }); + + const cpu_pte_count = paging.page_table_entry_count - paging.CPUPageTables.left_ptables; + const cpu_ptes = cpu_page_tables.p_table.toHigherHalfVirtualAddress().access(*paging.PTable)[0..cpu_pte_count]; + const user_mapped_cpu_pte_offset = (paging.Level.count - 2) * paging.page_table_size; + // log.debug("[OFFSET] 0x{x}", .{user_mapped_cpu_pte_offset}); + const user_mapped_cpu_ptes = cpu_page_table_physical_region.offset(user_mapped_cpu_pte_offset).toHigherHalfVirtualAddress().access(paging.PTE)[0..cpu_pte_count]; + @memcpy(user_mapped_cpu_ptes, cpu_ptes); + + const user_root_page_table_region = root_page_tables[1]; + const RootPageTableEntryType = paging.EntryTypeMap(lib.arch.valid_page_sizes[1])[@intFromEnum(x86_64.root_page_table_entry)]; + user_root_page_table_region.toHigherHalfVirtualAddress().access(paging.PML4TE)[paging.CPUPageTables.pml4_index] = paging.PML4TE{ + .present = true, + .write = true, + .execute_disable = false, + .address = paging.packAddress(RootPageTableEntryType, cpu_page_table_physical_region.offset(0).address.value()), + }; + + const current_address_space = paging.Specific{ .cr3 = cr3.read() }; + const src_half = (try current_address_space.getPML4TableUnchecked())[half_page_table_entry_count..][0..half_page_table_entry_count]; + @memcpy(root_page_tables[0].toHigherHalfVirtualAddress().access(paging.PML4TE)[half_page_table_entry_count..][0..half_page_table_entry_count], src_half); + + const pdp = cpu_page_table_physical_region_iterator.takeSlice(paging.page_table_size); + const pd = cpu_page_table_physical_region_iterator.takeSlice(paging.page_table_size); + const pt = cpu_page_table_physical_region_iterator.takeSlice(paging.page_table_size); + assert(cpu_page_table_physical_region_iterator.size == 0); + + const pdp_table = pdp.toHigherHalfVirtualAddress().access(paging.PDPTE); + // log.debug("pdp index: {}. pdp table: 0x{x}", .{ paging.CPUPageTables.pdp_index, @ptrToInt(pdp_table.ptr) }); + pdp_table[paging.CPUPageTables.pdp_index] = paging.PDPTE{ + .present = true, + .write = true, + .execute_disable = false, + .address = paging.packAddress(paging.PDPTE, pd.address.value()), + }; + + const pd_table = pd.toHigherHalfVirtualAddress().access(paging.PDTE); + pd_table[paging.CPUPageTables.pd_index] = paging.PDTE{ + .present = true, + .write = true, + .execute_disable = false, + .address = paging.packAddress(paging.PDTE, pt.address.value()), + }; + + const supporting_page_table_size = PageTableRegions.total_size; + _ = supporting_page_table_size; + const indexed_base = @as(paging.IndexedVirtualAddress, @bitCast(page_table_regions.total.address.toHigherHalfVirtualAddress().value())); + const indexed_top = @as(paging.IndexedVirtualAddress, @bitCast(page_table_regions.total.top().toHigherHalfVirtualAddress().value())); + const diff = @as(u64, @bitCast(indexed_top)) - @as(u64, @bitCast(indexed_base)); + // log.debug("Mapping 0x{x} - 0x{x} to higher half", .{ page_table_regions.total.address.value(), page_table_regions.total.top().value() }); + // log.debug("supporting_page_table_size: {}", .{supporting_page_table_size}); + // log.debug("\nBASE: {}\n\nTOP: {}\n\n", .{ indexed_base, indexed_top }); + + assert(indexed_base.PML4 == indexed_top.PML4); + assert(indexed_base.PDP == indexed_top.PDP); + const ptable_count = indexed_top.PD - indexed_base.PD + 1; + + const cpu_indexed_base = @as(paging.IndexedVirtualAddress, @bitCast(cpu_page_table_physical_region.toHigherHalfVirtualAddress().address.value())); + const cpu_indexed_top = @as(paging.IndexedVirtualAddress, @bitCast(cpu_page_table_physical_region.toHigherHalfVirtualAddress().top().value())); + const cpu_diff = @as(u64, @bitCast(cpu_indexed_top)) - @as(u64, @bitCast(cpu_indexed_base)); + // log.debug("\nCPU BASE: {}\n\nCPU TOP: {}\n\n", .{ cpu_indexed_base, cpu_indexed_top }); + assert(cpu_indexed_base.PML4 == cpu_indexed_top.PML4); + assert(cpu_indexed_base.PDP == cpu_indexed_top.PDP); + assert(cpu_indexed_base.PDP == indexed_base.PDP); + assert(cpu_indexed_base.PD == cpu_indexed_top.PD); + assert(cpu_indexed_base.PT < cpu_indexed_top.PT); + assert(cpu_indexed_base.PML4 == indexed_base.PML4); + assert(cpu_indexed_base.PDP == indexed_base.PDP); + const cpu_ptable_count = cpu_indexed_top.PD - cpu_indexed_base.PD + 1; + assert(cpu_ptable_count <= ptable_count); + + const support_pdp_table_count = 1; + const support_pd_table_count = 1; + const min = @min(@as(u64, @bitCast(indexed_base)), @as(u64, @bitCast(cpu_indexed_base))); + const max = @max(@as(u64, @bitCast(indexed_top)), @as(u64, @bitCast(cpu_indexed_top))); + const min_indexed = @as(paging.IndexedVirtualAddress, @bitCast(min)); + const general_diff = max - min; + const pte_count = @divExact(general_diff, lib.arch.valid_page_sizes[0]); + const support_p_table_count = 1 + pte_count / paging.page_table_entry_count + @intFromBool(@as(usize, paging.page_table_entry_count) - min_indexed.PT < pte_count); + // log.debug("Support p table count: {}", .{support_p_table_count}); + // log.debug("indexed base: 0x{x}. top: 0x{x}", .{ @bitCast(u64, indexed_base), @bitCast(u64, indexed_top) }); + // log.debug("cpu indexed base: 0x{x}. top: 0x{x}", .{ @bitCast(u64, cpu_indexed_base), @bitCast(u64, cpu_indexed_top) }); + + const support_page_table_count = @as(usize, support_pdp_table_count + support_pd_table_count + support_p_table_count); + const support_page_table_physical_region = try cpu.driver.getRootCapability().allocatePages(support_page_table_count * paging.page_table_size); + // log.debug("Support page tables: 0x{x} - 0x{x}", .{ support_page_table_physical_region.address.value(), support_page_table_physical_region.top().value() }); + // log.debug("PD table count: {}. P table count: {}", .{ support_pd_table_count, support_p_table_count }); + + const support_pdp_offset = 0; + const support_pd_offset = support_pdp_table_count * paging.page_table_size; + const support_pt_offset = support_pd_offset + support_pd_table_count * paging.page_table_size; + + const support_pml4 = page_table_regions.getPageTables(.{ .user = true, .index = .pml4 }); + const support_pdp_region = support_page_table_physical_region.offset(support_pdp_offset); + const support_pd_region = support_page_table_physical_region.offset(support_pd_offset); + const support_pt_region = support_page_table_physical_region.offset(support_pt_offset); + + assert(!support_pml4[indexed_base.PML4].present); + assert(support_pdp_table_count == 1); + + support_pml4[indexed_base.PML4] = paging.PML4TE{ + .present = true, + .write = true, + .address = paging.packAddress(paging.PML4TE, support_pdp_region.address.value()), + }; + + const support_pdp = support_pdp_region.toHigherHalfVirtualAddress().access(paging.PDPTE); + assert(!support_pdp[indexed_base.PDP].present); + assert(support_pd_table_count == 1); + + support_pdp[indexed_base.PDP] = paging.PDPTE{ + .present = true, + .write = true, + .address = paging.packAddress(paging.PDPTE, support_pd_region.address.value()), + }; + + const support_pd = support_pd_region.toHigherHalfVirtualAddress().access(paging.PDTE); + assert(!support_pd[indexed_base.PD].present); + assert(indexed_base.PD <= cpu_indexed_base.PD); + + for (0..support_p_table_count) |i| { + const pd_index = indexed_base.PD + i; + const p_table_physical_region = support_pt_region.offset(i * paging.page_table_size); + support_pd[pd_index] = paging.PDTE{ + .present = true, + .write = true, + .address = paging.packAddress(paging.PDTE, p_table_physical_region.address.value()), + }; + } + + const support_ptes = support_pt_region.toHigherHalfVirtualAddress().access(paging.PTE); + for (0..@divExact(diff, lib.arch.valid_page_sizes[0])) |page_index| { + support_ptes[indexed_base.PT + page_index] = paging.getPageEntry(paging.PTE, page_table_regions.total.offset(page_index * lib.arch.valid_page_sizes[0]).address.value(), .{ + .present = true, + .write = true, + }); + } + + for (0..@divExact(cpu_diff, lib.arch.valid_page_sizes[0])) |page_index| { + support_ptes[cpu_indexed_base.PT + page_index] = paging.getPageEntry(paging.PTE, cpu_page_table_physical_region.offset(page_index * lib.arch.valid_page_sizes[0]).address.value(), .{ + .present = true, + .write = true, + }); + } + + { + const privileged_stack_physical_region = try cpu.driver.getRootCapability().allocatePages(x86_64.capability_address_space_stack_size); + const indexed_privileged_stack = @as(paging.IndexedVirtualAddress, @bitCast(x86_64.capability_address_space_stack_address.value())); + const stack_last_page = x86_64.capability_address_space_stack_address.offset(x86_64.capability_address_space_stack_size - lib.arch.valid_page_sizes[0]); + const indexed_privileged_stack_last_page = @as(paging.IndexedVirtualAddress, @bitCast(stack_last_page.value())); + assert(indexed_privileged_stack.PD == indexed_privileged_stack_last_page.PD); + assert(indexed_privileged_stack.PT < indexed_privileged_stack_last_page.PT); + + const pml4te = &page_table_regions.getPageTables(.{ .index = .pml4, .user = false })[indexed_privileged_stack.PML4]; + assert(pml4te.present); + + const pdpte = &(try paging.accessPageTable(PhysicalAddress.new(paging.unpackAddress(pml4te)), *paging.PDPTable))[indexed_privileged_stack.PDP]; + assert(!pdpte.present); + const pd_table_physical_region = try cpu.driver.getRootCapability().allocatePages(paging.page_table_size); + pdpte.* = paging.PDPTE{ + .present = true, + .write = true, + .address = paging.packAddress(paging.PDTE, pd_table_physical_region.address.value()), + }; + + const pdte = &(try paging.accessPageTable(PhysicalAddress.new(paging.unpackAddress(pdpte)), *paging.PDTable))[indexed_privileged_stack.PD]; + assert(!pdte.present); + const p_table_physical_region = try cpu.driver.getRootCapability().allocatePages(paging.page_table_size); + pdte.* = paging.PDTE{ + .present = true, + .write = true, + .address = paging.packAddress(paging.PDTE, p_table_physical_region.address.value()), + }; + + const p_table = try paging.accessPageTable(PhysicalAddress.new(paging.unpackAddress(pdte)), *paging.PTable); + for (p_table[indexed_privileged_stack.PT .. @as(usize, indexed_privileged_stack_last_page.PT) + 1], 0..) |*pte, index| { + const physical_address = privileged_stack_physical_region.offset(index * paging.page_table_size).address; + pte.* = paging.getPageEntry(paging.PTE, physical_address.value(), .{ + .present = true, + .write = true, + }); + } + } + + const init_cpu_scheduler_physical_region = try cpu.driver.getRootCapability().allocatePages(@sizeOf(cpu.UserScheduler)); + const init_cpu_scheduler_virtual_region = init_cpu_scheduler_physical_region.toHigherHalfVirtualAddress(); + const init_cpu_scheduler = init_cpu_scheduler_virtual_region.address.access(*cpu.UserScheduler); + // log.debug("Init scheduler: 0x{x}", .{init_cpu_scheduler_virtual_region.address.value()}); + const cpu_scheduler_indexed = @as(paging.IndexedVirtualAddress, @bitCast(init_cpu_scheduler_virtual_region.address.value())); + // log.debug("CPU scheduler indexed: {}", .{cpu_scheduler_indexed}); + + assert(cpu_scheduler_indexed.PML4 == cpu_indexed_base.PML4); + + const scheduler_pml4te = &page_table_regions.getPageTables(.{ .index = .pml4, .user = true })[cpu_scheduler_indexed.PML4]; + assert(scheduler_pml4te.present); + + const scheduler_pdpte = &(try paging.accessPageTable(PhysicalAddress.new(paging.unpackAddress(scheduler_pml4te)), *paging.PDPTable))[cpu_scheduler_indexed.PDP]; + + // Sanity checks + + const scheduler_pdte = blk: { + const pdp_is_inside = cpu_scheduler_indexed.PDP >= cpu_indexed_base.PDP and cpu_scheduler_indexed.PDP <= cpu_indexed_top.PDP; + // log.debug("PDP inside: {}", .{pdp_is_inside}); + assert(scheduler_pdpte.present == pdp_is_inside); + + if (!scheduler_pdpte.present) { + const pdte_allocation = try cpu.driver.getRootCapability().allocatePages(paging.page_table_size); + scheduler_pdpte.* = .{ + .present = true, + .write = true, + .address = paging.packAddress(@TypeOf(scheduler_pdpte.*), pdte_allocation.address.value()), + }; + } + + break :blk &(try paging.accessPageTable(PhysicalAddress.new(paging.unpackAddress(scheduler_pdpte)), *paging.PDTable))[cpu_scheduler_indexed.PD]; + }; + + const scheduler_pte = blk: { + const is_inside_cpu_page_table_limits = cpu_scheduler_indexed.PD >= cpu_indexed_base.PD and cpu_scheduler_indexed.PD <= cpu_indexed_top.PD; + assert(is_inside_cpu_page_table_limits == scheduler_pdte.present); + if (!scheduler_pdte.present) { + const pte_allocation = try cpu.driver.getRootCapability().allocatePages(paging.page_table_size); + scheduler_pdte.* = .{ + .present = true, + .write = true, + .address = paging.packAddress(@TypeOf(scheduler_pdte.*), pte_allocation.address.value()), + }; + } + + break :blk &(try paging.accessPageTable(PhysicalAddress.new(paging.unpackAddress(scheduler_pdte)), *paging.PTable))[cpu_scheduler_indexed.PT]; + }; + + scheduler_pte.* = paging.getPageEntry(paging.PTE, init_cpu_scheduler_physical_region.address.value(), .{ + .present = true, + .write = true, + }); + + init_cpu_scheduler.* = cpu.UserScheduler{ + .common = user_scheduler_virtual_address.access(*rise.UserScheduler), + .capability_root_node = cpu.capabilities.Root{ + .static = .{ + .cpu = true, + .boot = true, + .process = true, + }, + .dynamic = .{ + .io = .{ + .debug = true, + }, + .ram = cpu.driver.getRootCapability().dynamic.ram, + .cpu_memory = .{ + .flags = .{ + .allocate = true, + }, + }, + .page_table = .{}, + }, + .scheduler = .{ + .handle = init_cpu_scheduler, + .memory = scheduler_memory_physical_region, + }, + }, + }; + + const higher_half_scheduler_common = scheduler_memory_physical_region.address.toHigherHalfVirtualAddress().access(*rise.UserScheduler); + // log.debug("Higher half: 0x{x}", .{@ptrToInt(higher_half_scheduler_common)}); + higher_half_scheduler_common.disabled = true; + higher_half_scheduler_common.core_id = cpu.core_id; + + // log.debug("cpu scheduler: 0x{x}", .{@ptrToInt(init_cpu_scheduler)}); + + return SpawnInitCommonResult{ + .page_table_regions = page_table_regions, + .scheduler = init_cpu_scheduler, + }; +} diff --git a/src/cpu/arch/x86/64/linker_script.ld b/src/cpu/arch/x86/64/linker_script.ld new file mode 100644 index 0000000..69c7cdc --- /dev/null +++ b/src/cpu/arch/x86/64/linker_script.ld @@ -0,0 +1,39 @@ +PHDRS { + none PT_NULL FLAGS(0); + text PT_LOAD FLAGS((1 << 2) | (1 << 0) /* Readable | Executable */); + rodata PT_LOAD FLAGS((1 << 2) /* Readable */); + data PT_LOAD FLAGS((1 << 2) | (1 << 1) /* Readable | Writeable */); +} + +SECTIONS { + . = 0xFFFFFFFF80000000; + PROVIDE(cpu_driver_start = .); + + + PROVIDE(text_section_start = .); + .text . : { + *(.text*) + }:text + + . = ALIGN(4K); + PROVIDE(text_section_end = .); + + PROVIDE(rodata_section_start = .); + .rodata . : { + *(.rodata*) + }:rodata + + . = ALIGN(4K); + PROVIDE(rodata_section_end = .); + + PROVIDE(data_section_start = .); + .data . : { + *(.data*) + *(.bss*) + *(.got*) + }:data + + . = ALIGN(4K); + PROVIDE(data_section_end = .); + PROVIDE(cpu_driver_end = .); +} diff --git a/src/cpu/arch/x86/64/syscall.zig b/src/cpu/arch/x86/64/syscall.zig new file mode 100644 index 0000000..354281b --- /dev/null +++ b/src/cpu/arch/x86/64/syscall.zig @@ -0,0 +1,281 @@ +const cpu = @import("cpu"); +const lib = @import("lib"); +const log = lib.log; +const privileged = @import("privileged"); +const rise = @import("rise"); + +const assert = lib.assert; + +const cr3 = privileged.arch.x86_64.registers.cr3; + +const cr3_user_page_table_mask = 1 << @bitOffsetOf(cr3, "address"); +const cr3_user_page_table_and_pcid_mask = cr3_user_page_table_mask | pcid_mask; +const pcid_bit = 11; +const pcid_mask = 1 << pcid_bit; + +/// SYSCALL documentation +/// ABI: +/// - RAX: System call options (number for Linux) +/// - RCX: Return address +/// - R11: Saved rflags +/// - RDI: argument 0 +/// - RSI: argument 1 +/// - RDX: argument 2 +/// - R10: argument 3 +/// - R8: argument 4 +/// - R9: argument 5 +fn riseSyscall(comptime Syscall: type, raw_arguments: rise.syscall.Arguments) Syscall.ErrorSet.Error!Syscall.Result { + cpu.syscall_count += 1; + comptime assert(Syscall == rise.capabilities.Syscall(Syscall.capability, Syscall.command)); + const capability: rise.capabilities.Type = Syscall.capability; + const command: rise.capabilities.Command(capability) = Syscall.command; + const arguments = try Syscall.toArguments(raw_arguments); + + return if (cpu.user_scheduler.capability_root_node.hasPermissions(capability, command)) switch (capability) { + .io => switch (command) { + .copy, .mint, .retype, .delete, .revoke, .create => unreachable, + .log => blk: { + const message = arguments; + cpu.writer.writeAll(message) catch unreachable; + comptime assert(Syscall.Result == usize); + break :blk message.len; + }, + }, + .cpu => switch (command) { + .copy, .mint, .retype, .delete, .revoke, .create => unreachable, + .get_core_id => cpu.core_id, + .shutdown => cpu.shutdown(.success), + .get_command_buffer => { + const command_buffer = arguments; + _ = command_buffer; + @panic("TODO: get_command_buffer"); + }, + }, + .cpu_memory => switch (command) { + .allocate => blk: { + comptime assert(@TypeOf(arguments) == usize); + const size = arguments; + const physical_region = try cpu.user_scheduler.capability_root_node.allocatePages(size); + try cpu.user_scheduler.capability_root_node.allocateCPUMemory(physical_region, .{ .privileged = false }); + break :blk physical_region.address; + }, + else => @panic(@tagName(command)), + }, + .ram => unreachable, + .boot => switch (command) { + .get_bundle_size => cpu.bundle.len, + .get_bundle_file_list_size => cpu.bundle_files.len, + else => @panic(@tagName(command)), + }, + .process => switch (command) { + .exit => switch (arguments) { + true => cpu.shutdown(.success), + false => cpu.panic("User process panicked", .{}), + }, + else => @panic(@tagName(command)), + }, + .page_table => @panic("TODO: page_table"), + } else error.forbidden; +} + +export fn syscall(registers: *const Registers) callconv(.C) rise.syscall.Result { + const options = @as(rise.syscall.Options, @bitCast(registers.syscall_number)); + const arguments = rise.syscall.Arguments{ registers.rdi, registers.rsi, registers.rdx, registers.r10, registers.r8, registers.r9 }; + + return switch (options.general.convention) { + .rise => switch (options.rise.type) { + inline else => |capability| switch (@as(rise.capabilities.Command(capability), @enumFromInt(options.rise.command))) { + inline else => |command| blk: { + const Syscall = rise.capabilities.Syscall(capability, command); + const result: Syscall.Result = riseSyscall(Syscall, arguments) catch |err| break :blk Syscall.errorToRaw(err); + break :blk Syscall.resultToRaw(result); + }, + }, + }, + .linux => @panic("linux syscall"), + }; +} + +/// SYSCALL documentation +/// ABI: +/// - RAX: System call number +/// - RCX: Return address +/// - R11: Saved rflags +/// - RDI: argument 0 +/// - RSI: argument 1 +/// - RDX: argument 2 +/// - R10: argument 3 +/// - R8: argument 4 +/// - R9: argument 5 +pub fn entryPoint() callconv(.Naked) void { + asm volatile ( + \\endbr64 + \\swapgs + \\movq %rsp, user_stack(%rip) + ); + + if (cpu.arch.x86_64.kpti) { + asm volatile ( + \\mov %cr3, %rsp + ::: "memory"); + + if (cpu.arch.pcid) { + @compileError("pcid support not yet implemented"); + } + + asm volatile ( + \\andq %[mask], %rsp + \\mov %rsp, %cr3 + : + : [mask] "i" (~@as(u64, cr3_user_page_table_and_pcid_mask)), + : "memory" + ); + } + + // Safe stack + asm volatile ("movabsq %[capability_address_space_stack_top], %rsp" + : + : [capability_address_space_stack_top] "i" (cpu.arch.x86_64.capability_address_space_stack_top), + : "memory", "rsp" + ); + + asm volatile ( + \\pushq %[user_ds] + \\pushq (user_stack) + \\pushq %r11 + \\pushq %[user_cs] + \\pushq %rcx + \\pushq %rax + : + : [user_ds] "i" (cpu.arch.x86_64.user_data_selector), + [user_cs] "i" (cpu.arch.x86_64.user_code_selector), + : "memory" + ); + + // Push and clear registers + asm volatile ( + // Push + \\pushq %rdi + \\pushq %rsi + \\pushq %rdx + \\pushq %rcx + \\pushq %rax + \\pushq %r8 + \\pushq %r9 + \\pushq %r10 + \\pushq %r11 + \\pushq %rbx + \\pushq %rbp + \\pushq %r12 + \\pushq %r13 + \\pushq %r14 + \\pushq %r15 + // Clear + \\xorl %esi, %esi + \\xorl %edx, %edx + \\xorl %ecx, %ecx + \\xorl %r8d, %r8d + \\xorl %r9d, %r9d + \\xorl %r10d, %r10d + \\xorl %r11d, %r11d + \\xorl %ebx, %ebx + \\xorl %ebp, %ebp + \\xorl %r12d, %r12d + \\xorl %r13d, %r13d + \\xorl %r14d, %r14d + \\xorl %r15d, %r15d + ::: "memory"); + + // Pass arguments + asm volatile ( + \\mov %rsp, %rdi + \\mov %rax, %rsi + ::: "memory"); + + // TODO: more security stuff + asm volatile ( + \\call syscall + ::: "memory"); + + // TODO: more security stuff + + // Pop registers + asm volatile ( + \\popq %r15 + \\popq %r14 + \\popq %r13 + \\popq %r12 + \\popq %rbp + \\popq %rbx + \\popq %r11 + \\popq %r10 + \\popq %r9 + \\popq %r8 + \\popq %rcx + // RAX + \\popq %rcx + // RDX + \\popq %rsi + \\popq %rsi + \\popq %rdi + ::: "memory"); + + if (cpu.arch.x86_64.kpti) { + // Restore CR3 + asm volatile ( + \\mov %cr3, %rsp + ::: "memory"); + + if (cpu.arch.x86_64.pcid) { + @compileError("PCID not supported yet"); + } + + asm volatile ( + \\orq %[user_cr3_mask], %rsp + \\mov %rsp, %cr3 + : + : [user_cr3_mask] "i" (cr3_user_page_table_mask), + : "memory" + ); + } + + // Restore RSP + asm volatile ( + \\mov user_stack(%rip), %rsp + ::: "memory"); + + asm volatile ( + \\swapgs + \\sysretq + ::: "memory"); + + asm volatile ( + \\int3 + ::: "memory"); + + unreachable; +} + +pub const Registers = extern struct { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + rbp: u64, + rbx: u64, + r11: u64, + r10: u64, + r9: u64, + r8: u64, + rax: u64, + rcx: u64, + rdx: u64, + rsi: u64, + rdi: u64, + syscall_number: u64, + rip: u64, + cs: u64, + rflags: u64, + rsp: u64, + ss: u64, +}; diff --git a/src/cpu/arch/x86_64.zig b/src/cpu/arch/x86_64.zig new file mode 100644 index 0000000..90044dd --- /dev/null +++ b/src/cpu/arch/x86_64.zig @@ -0,0 +1,265 @@ +const lib = @import("lib"); +const Allocator = lib.Allocator; +const assert = lib.assert; +const ELF = lib.ELF(64); +const log = lib.log; +const Spinlock = lib.Spinlock; +const bootloader = @import("bootloader"); +const privileged = @import("privileged"); +const panic = cpu.panic; +const PageAllocator = cpu.PageAllocator; +const x86_64 = privileged.arch.x86_64; +const APIC = x86_64.APIC; +const paging = x86_64.paging; +const cr0 = x86_64.registers.cr0; +const cr3 = x86_64.registers.cr3; +const cr4 = x86_64.registers.cr4; +const PhysicalAddress = lib.PhysicalAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const VirtualAddress = lib.VirtualAddress; +const VirtualMemoryRegion = lib.VirtualMemoryRegion; + +const cpu = @import("cpu"); +const Heap = cpu.Heap; + +const init = @import("./x86/64/init.zig"); +pub const syscall = @import("./x86/64/syscall.zig"); +pub const entryPoint = init.entryPoint; + +const rise = @import("rise"); + +var writer_lock: Spinlock = .released; + +pub const Registers = extern struct { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + rbp: u64, + rbx: u64, + r11: u64, + r10: u64, + r9: u64, + r8: u64, + rax: u64, + rcx: u64, + rdx: u64, + rsi: u64, + rdi: u64, + syscall_number_or_error_code: u64, + rip: u64, + cs: u64, + rflags: lib.arch.x86_64.registers.RFLAGS, + rsp: u64, + ss: u64, +}; + +const interrupt_kind: u32 = 0; + +export fn interruptHandler(regs: *const InterruptRegisters, interrupt_number: u8) void { + switch (interrupt_number) { + local_timer_vector => { + APIC.write(.eoi, 0); + nextTimer(10); + }, + else => cpu.panicFromInstructionPointerAndFramePointer(regs.rip, regs.rbp, "Exception: 0x{x}", .{interrupt_number}), + } +} + +const InterruptRegisters = extern struct { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + rbp: u64, + rbx: u64, + r11: u64, + r10: u64, + r9: u64, + r8: u64, + rax: u64, + rcx: u64, + rdx: u64, + rsi: u64, + rdi: u64, + error_code: u64, + rip: u64, + cs: u64, + rflags: u64, + rsp: u64, + ss: u64, +}; + +const local_timer_vector = 0xef; +pub export var ticks_per_ms: privileged.arch.x86_64.TicksPerMS = undefined; +pub inline fn nextTimer(ms: u32) void { + APIC.write(.lvt_timer, local_timer_vector | (1 << 17)); + APIC.write(.timer_initcnt, ticks_per_ms.lapic * ms); +} +pub const kpti = true; +pub const pcid = false; +pub const smap = false; +pub const invariant_tsc = false; +pub const capability_address_space_size = 1 * lib.gb; +pub const capability_address_space_start = capability_address_space_stack_top - capability_address_space_size; +pub const capability_address_space_stack_top = 0xffff_ffff_8000_0000; +pub const capability_address_space_stack_size = privileged.default_stack_size; +pub const capability_address_space_stack_alignment = lib.arch.valid_page_sizes[0]; +pub const capability_address_space_stack_address = VirtualAddress.new(capability_address_space_stack_top - capability_address_space_stack_size); +pub const code_64 = @offsetOf(GDT, "code_64"); +pub const data_64 = @offsetOf(GDT, "data_64"); +pub const user_code_64 = @offsetOf(GDT, "user_code_64"); +pub const user_data_64 = @offsetOf(GDT, "user_data_64"); +pub const tss_selector = @offsetOf(GDT, "tss_descriptor"); +pub const user_code_selector = user_code_64 | user_dpl; +pub const user_data_selector = user_data_64 | user_dpl; +pub const user_dpl = 3; + +pub const GDT = extern struct { + null: Entry = GDT.Entry.null_entry, // 0x00 + code_16: Entry = GDT.Entry.code_16, // 0x08 + data_16: Entry = GDT.Entry.data_16, // 0x10 + code_32: Entry = GDT.Entry.code_32, // 0x18 + data_32: Entry = GDT.Entry.data_32, // 0x20 + code_64: u64 = 0x00A09A0000000000, // 0x28 + data_64: u64 = 0x0000920000000000, // 0x30 + user_data_64: u64 = @as(u64, 0x0000920000000000) | (3 << 45), //GDT.Entry.user_data_64, // 0x38 + user_code_64: u64 = @as(u64, 0x00A09A0000000000) | (3 << 45), //GDT.Entry.user_code_64, // 0x40 + tss_descriptor: TSS.Descriptor = undefined, // 0x48 + + const Entry = privileged.arch.x86_64.GDT.Entry; + + pub const Descriptor = privileged.arch.x86_64.GDT.Descriptor; + + comptime { + const entry_count = 9; + const target_size = entry_count * @sizeOf(Entry) + @sizeOf(TSS.Descriptor); + + assert(@sizeOf(GDT) == target_size); + assert(@offsetOf(GDT, "code_64") == 0x28); + assert(@offsetOf(GDT, "data_64") == 0x30); + assert(@offsetOf(GDT, "user_data_64") == 0x38); + assert(@offsetOf(GDT, "user_code_64") == 0x40); + assert(@offsetOf(GDT, "tss_descriptor") == entry_count * @sizeOf(Entry)); + } + + pub fn getDescriptor(global_descriptor_table: *const GDT) GDT.Descriptor { + return .{ + .limit = @sizeOf(GDT) - 1, + .address = @intFromPtr(global_descriptor_table), + }; + } +}; + +pub const SystemSegmentDescriptor = extern struct { + const Type = enum(u4) { + ldt = 0b0010, + tss_available = 0b1001, + tss_busy = 0b1011, + call_gate = 0b1100, + interrupt_gate = 0b1110, + trap_gate = 0b1111, + }; +}; + +pub const TSS = extern struct { + reserved0: u32 = 0, + rsp: [3]u64 align(4) = [3]u64{ 0, 0, 0 }, + reserved1: u64 align(4) = 0, + IST: [7]u64 align(4) = [7]u64{ 0, 0, 0, 0, 0, 0, 0 }, + reserved3: u64 align(4) = 0, + reserved4: u16 = 0, + IO_map_base_address: u16 = 104, + + comptime { + assert(@sizeOf(TSS) == 104); + } + + pub const Descriptor = extern struct { + limit_low: u16, + base_low: u16, + base_mid_low: u8, + access: Access, + attributes: Attributes, + base_mid_high: u8, + base_high: u32, + reserved: u32 = 0, + + pub const Access = packed struct(u8) { + type: SystemSegmentDescriptor.Type, + reserved: u1 = 0, + dpl: u2, + present: bool, + }; + + pub const Attributes = packed struct(u8) { + limit: u4, + available_for_system_software: bool, + reserved: u2 = 0, + granularity: bool, + }; + + comptime { + assert(@sizeOf(TSS.Descriptor) == 0x10); + } + }; + + pub fn getDescriptor(tss_struct: *const TSS, offset: u64) Descriptor { + const address = @intFromPtr(tss_struct) + offset; + return Descriptor{ + .low = .{ + .limit_low = @as(u16, @truncate(@sizeOf(TSS) - 1)), + .base_low = @as(u16, @truncate(address)), + .base_low_mid = @as(u8, @truncate(address >> 16)), + .type = 0b1001, + .descriptor_privilege_level = 0, + .present = 1, + .limit_high = 0, + .available_for_system_software = 0, + .granularity = 0, + .base_mid = @as(u8, @truncate(address >> 24)), + }, + .base_high = @as(u32, @truncate(address >> 32)), + }; + } +}; + +pub const IDT = extern struct { + descriptors: [entry_count]GateDescriptor = undefined, + pub const Descriptor = privileged.arch.x86_64.SegmentDescriptor; + pub const GateDescriptor = extern struct { + offset_low: u16, + segment_selector: u16, + flags: packed struct(u16) { + ist: u3, + reserved: u5 = 0, + type: SystemSegmentDescriptor.Type, + reserved1: u1 = 0, + dpl: u2, + present: bool, + }, + offset_mid: u16, + offset_high: u32, + reserved: u32 = 0, + + comptime { + assert(@sizeOf(@This()) == 0x10); + } + }; + pub const entry_count = 256; +}; + +pub inline fn writerStart() void { + writer_lock.acquire(); +} + +pub inline fn writerEnd() void { + writer_lock.release(); +} + +pub const PageTableEntry = paging.Level; +pub const root_page_table_entry = @as(cpu.arch.PageTableEntry, @enumFromInt(0)); + +pub const IOMap = extern struct { + debug: bool, +}; diff --git a/src/cpu/capabilities.zig b/src/cpu/capabilities.zig new file mode 100644 index 0000000..ae8ea8f --- /dev/null +++ b/src/cpu/capabilities.zig @@ -0,0 +1,419 @@ +const lib = @import("lib"); +const assert = lib.assert; +const Allocator = lib.Allocator; +const enumCount = lib.enumCount; +const log = lib.log.scoped(.capabilities); + +const privileged = @import("privileged"); +const PhysicalAddress = lib.PhysicalAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const rise = @import("rise"); +const cpu = @import("cpu"); + +pub const RootDescriptor = extern struct { + value: *Root, +}; + +pub const Static = enum { + cpu, + boot, + process, + + pub const count = lib.enumCount(@This()); + + pub const Bitmap = @Type(.{ + .Struct = blk: { + const full_bit_size = @max(@as(comptime_int, 1 << 3), @as(u8, @sizeOf(Static)) << 3); + break :blk .{ + .layout = .Packed, + .backing_integer = lib.IntType(.unsigned, full_bit_size), + .fields = fields: { + var fields: []const lib.Type.StructField = &.{}; + inline for (lib.enumFields(Static)) |static_field| { + fields = fields ++ [1]lib.Type.StructField{.{ + .name = static_field.name, + .type = bool, + .default_value = null, + .is_comptime = false, + .alignment = 0, + }}; + } + + assert(Static.count > 0); + assert(@sizeOf(Static) > 0 or Static.count == 1); + + const padding_type = lib.IntType(.unsigned, full_bit_size - Static.count); + + fields = fields ++ [1]lib.Type.StructField{.{ + .name = "reserved", + .type = padding_type, + .default_value = &@as(padding_type, 0), + .is_comptime = false, + .alignment = 0, + }}; + break :fields fields; + }, + .decls = &.{}, + .is_tuple = false, + }; + }, + }); +}; + +pub const Dynamic = enum { + io, + ram, // Barrelfish equivalent: RAM (no PhysAddr) + cpu_memory, // Barrelfish equivalent: Frame + page_table, // Barrelfish equivalent: VNode + // irq_table, + // device_memory, + // scheduler, + + pub const Map = extern struct { + io: IO, + ram: RAM, + cpu_memory: CPUMemory, + page_table: PageTables, + + comptime { + inline for (lib.fields(Dynamic.Map), lib.fields(Dynamic)) |struct_field, enum_field| { + assert(lib.equal(u8, enum_field.name, struct_field.name)); + } + } + }; +}; + +pub const RAM = extern struct { + lists: [lib.arch.reverse_valid_page_sizes.len]?*Region = .{null} ** lib.arch.valid_page_sizes.len, + + const AllocateError = error{ + OutOfMemory, + }; + + inline fn getListIndex(size: usize) usize { + inline for (lib.arch.reverse_valid_page_sizes, 0..) |reverse_page_size, reverse_index| { + if (size >= reverse_page_size) return reverse_index; + } + + unreachable; + } + + pub const Region = extern struct { + region: PhysicalMemoryRegion, + next: ?*@This() = null, + + const UnalignedAllocationResult = extern struct { + wasted: PhysicalMemoryRegion, + allocated: PhysicalMemoryRegion, + }; + + inline fn allocateUnaligned(free_ram: *Region, size: usize, alignment: usize) ?UnalignedAllocationResult { + const aligned_region_address = lib.alignForward(usize, free_ram.region.address.value(), alignment); + const wasted_space = aligned_region_address - free_ram.region.address.value(); + if (free_ram.region.size >= wasted_space + size) { + const wasted_region = free_ram.region.takeSlice(wasted_space); + const allocated_region = free_ram.region.takeSlice(size); + return UnalignedAllocationResult{ + .wasted = wasted_region, + .allocated = allocated_region, + }; + } + + return null; + } + }; +}; + +pub const CPUMemory = extern struct { + privileged: RAM = .{}, + user: RAM = .{}, + flags: Flags, + + const Flags = packed struct(u64) { + allocate: bool, + reserved: u63 = 0, + }; +}; + +pub const PageTables = extern struct { + foo: u32 = 0, +}; + +pub const IO = extern struct { + debug: bool, +}; + +pub const Scheduler = extern struct { + handle: ?*cpu.UserScheduler = null, + memory: PhysicalMemoryRegion, +}; + +comptime { + assert(enumCount(Dynamic) + enumCount(Static) == enumCount(rise.capabilities.Type)); +} + +pub const Root = extern struct { + static: Static.Bitmap, + dynamic: Dynamic.Map, + scheduler: Scheduler, + heap: Heap = .{}, + padding: [padding_byte_count]u8 = .{0} ** padding_byte_count, + + const max_alignment = @max(@alignOf(Static.Bitmap), @alignOf(Dynamic.Map), @alignOf(Scheduler), @alignOf(Heap)); + const total_size = lib.alignForward(usize, @sizeOf(Static.Bitmap) + @sizeOf(Dynamic.Map) + @sizeOf(Scheduler) + @sizeOf(Heap), max_alignment); + const page_aligned_size = lib.alignForward(usize, total_size, lib.arch.valid_page_sizes[0]); + const padding_byte_count = page_aligned_size - total_size; + + comptime { + assert(@sizeOf(Root) % lib.arch.valid_page_sizes[0] == 0); + } + + pub fn copy(root: *Root, other: *Root) void { + other.static = root.static; + // TODO: + other.dynamic = root.dynamic; + } + + pub inline fn hasPermissions(root: *const Root, comptime capability_type: rise.capabilities.Type, command: rise.capabilities.Command(capability_type)) bool { + return switch (capability_type) { + // static capabilities + inline .cpu, + .boot, + .process, + => |static_capability| @field(root.static, @tagName(static_capability)), + // dynamic capabilities + .io => switch (command) { + .copy, .mint, .retype, .delete, .revoke, .create => unreachable, + .log => root.dynamic.io.debug, + }, + .cpu_memory => root.dynamic.cpu_memory.flags.allocate, + .ram => unreachable, + .page_table => unreachable, + }; + } + + pub const AllocateError = error{ + OutOfMemory, + }; + + // Fast path + pub fn allocatePages(root: *Root, size: usize) AllocateError!PhysicalMemoryRegion { + assert(size != 0); + assert(lib.isAligned(size, lib.arch.valid_page_sizes[0])); + var index = RAM.getListIndex(size); + + const result = blk: { + while (true) : (index -= 1) { + const list = root.dynamic.ram.lists[index]; + var iterator = list; + + while (iterator) |free_ram| : (iterator = free_ram.next) { + if (free_ram.region.size >= size) { + if (free_ram.region.size >= size) { + const result = free_ram.region.takeSlice(size); + break :blk result; + } else { + @panic("TODO: cnsume all reigon"); + } + } + } + + if (index == 0) break; + } + + return error.OutOfMemory; + }; + + @memset(result.toHigherHalfVirtualAddress().access(u8), 0); + + return result; + } + + // Slow uncommon path. Use cases: + // 1. CR3 switch. This is assumed to be privileged, so this function assumes privileged use of the memory + pub fn allocatePageCustomAlignment(root: *Root, size: usize, alignment: usize) AllocateError!PhysicalMemoryRegion { + assert(alignment > lib.arch.valid_page_sizes[0] and alignment < lib.arch.valid_page_sizes[1]); + + comptime assert(lib.arch.valid_page_sizes.len == 3); + var index = RAM.getListIndex(size); + + while (true) : (index -= 1) { + if (root.dynamic.ram.lists[index]) |smallest_region_list| { + var iterator: ?*cpu.capabilities.RAM.Region = smallest_region_list; + while (iterator) |free_ram| : (iterator = free_ram.next) { + if (lib.isAligned(free_ram.region.address.value(), alignment)) { + if (free_ram.region.size >= size) { + const allocated_region = free_ram.region.takeSlice(size); + return allocated_region; + } + } else if (free_ram.allocateUnaligned(size, alignment)) |unaligned_allocation| { + try root.addRegion(&root.dynamic.ram, unaligned_allocation.wasted); + return unaligned_allocation.allocated; + } + } + } + + if (index == 0) break; + } + + return AllocateError.OutOfMemory; + } + + fn allocateSingle(root: *Root, comptime T: type) AllocateError!*T { + var iterator = root.heap.first; + while (iterator) |heap_region| : (iterator = heap_region.next) { + if (heap_region.alignmentFits(@alignOf(T))) { + if (heap_region.sizeFits(@sizeOf(T))) { + const allocated_region = heap_region.takeRegion(@sizeOf(T)); + const result = &allocated_region.toHigherHalfVirtualAddress().access(T)[0]; + return result; + } + } else { + @panic("ELSE"); + } + } + + const physical_region = try root.allocatePages(lib.arch.valid_page_sizes[0]); + const heap_region = physical_region.toHigherHalfVirtualAddress().address.access(*Heap.Region); + const first = root.heap.first; + heap_region.* = .{ + .descriptor = physical_region.offset(@sizeOf(Heap.Region)), + .allocated_size = @sizeOf(Heap.Region), + .next = first, + }; + + root.heap.first = heap_region; + + return try root.allocateSingle(T); + } + + fn allocateMany(root: *Root, comptime T: type, count: usize) AllocateError![]T { + _ = count; + _ = root; + + @panic("TODO many"); + } + + fn addRegion(root: *Root, ram: *RAM, physical_region: PhysicalMemoryRegion) !void { + const index = RAM.getListIndex(physical_region.size); + const new_region = try root.allocateSingle(RAM.Region); + new_region.* = RAM.Region{ + .region = physical_region, + .next = root.dynamic.ram.lists[index], + }; + + ram.lists[index] = new_region; + } + + pub const AllocateCPUMemoryOptions = packed struct { + privileged: bool, + }; + + pub fn allocateCPUMemory(root: *Root, physical_region: PhysicalMemoryRegion, options: AllocateCPUMemoryOptions) !void { + const ram_region = switch (options.privileged) { + true => &root.dynamic.cpu_memory.privileged, + false => &root.dynamic.cpu_memory.user, + }; + + try root.addRegion(ram_region, physical_region); + } + + pub const Heap = extern struct { + first: ?*Region = null, + + const AllocateError = error{ + OutOfMemory, + }; + + pub fn new(physical_region: PhysicalMemoryRegion, previous_allocated_size: usize) Heap { + const allocated_size = previous_allocated_size + @sizeOf(Region); + assert(physical_region.size > allocated_size); + const region = physical_region.offset(previous_allocated_size).address.toHigherHalfVirtualAddress().access(*Region); + region.* = .{ + .descriptor = physical_region, + .allocated_size = allocated_size, + }; + return Heap{ + .first = region, + }; + } + + fn create(heap: *Heap, comptime T: type) Heap.AllocateError!*T { + const result = try heap.allocate(T, 1); + return &result[0]; + } + + fn allocate(heap: *Heap, comptime T: type, count: usize) Heap.AllocateError![]T { + var iterator = heap.first; + while (iterator) |heap_region| { + const allocation = heap_region.allocate(T, count) catch continue; + return allocation; + } + @panic("TODO: allocate"); + } + + const Region = extern struct { + descriptor: PhysicalMemoryRegion, + allocated_size: usize, + next: ?*Region = null, + + inline fn getFreeRegion(region: Region) PhysicalMemoryRegion { + const free_region = region.descriptor.offset(region.allocated_size); + assert(free_region.size > 0); + return free_region; + } + + const AllocateError = error{ + OutOfMemory, + }; + + fn takeRegion(region: *Region, size: usize) PhysicalMemoryRegion { + var free_region = region.getFreeRegion(); + assert(free_region.size >= size); + const allocated_region = free_region.takeSlice(size); + region.allocated_size += size; + return allocated_region; + } + + fn allocate(region: *Region, comptime T: type, count: usize) Region.AllocateError![]T { + const free_region = region.getFreeRegion(); + _ = free_region; + _ = count; + @panic("TODO: region allocate"); + } + + fn create(region: *Region, comptime T: type) Region.AllocateError!*T { + const result = try region.allocate(T, 1); + return &result[0]; + } + + inline fn canAllocateDirectly(region: Region, size: usize, alignment: usize) bool { + const alignment_fits = region.alignmentFits(alignment); + const size_fits = region.sizeFits(size); + return alignment_fits and size_fits; + } + + inline fn canAllocateSplitting(region: Region, size: usize, alignment: usize) bool { + const free_region = region.getFreeRegion(); + const aligned_region_address = lib.alignForward(usize, free_region.address.value(), alignment); + const wasted_space = aligned_region_address - free_region.address.value(); + log.warn("Wasted space: {} bytes", .{wasted_space}); + _ = size; + @panic("TODO: canAllocateSplitting"); + } + + inline fn sizeFits(region: Region, size: usize) bool { + return region.descriptor.size - region.allocated_size >= size; + } + + inline fn alignmentFits(region: Region, alignment: usize) bool { + const result = lib.isAligned(region.getFreeRegion().address.value(), alignment); + return result; + } + }; + }; +}; + +pub const RootPageTableEntry = extern struct { + address: PhysicalAddress, +}; diff --git a/src/cpu/main.zig b/src/cpu/main.zig new file mode 100644 index 0000000..7d7d9f2 --- /dev/null +++ b/src/cpu/main.zig @@ -0,0 +1,37 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log; + +const privileged = @import("privileged"); +const stopCPU = privileged.arch.stopCPU; + +const cpu = @import("cpu"); + +var lock: lib.Spinlock = .released; + +pub const std_options = struct { + pub fn logFn(comptime level: lib.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype) void { + lock.acquire(); + cpu.writer.writeAll("[CPU DRIVER] ") catch unreachable; + cpu.writer.writeByte('[') catch unreachable; + cpu.writer.writeAll(@tagName(scope)) catch unreachable; + cpu.writer.writeAll("] ") catch unreachable; + cpu.writer.writeByte('[') catch unreachable; + cpu.writer.writeAll(@tagName(level)) catch unreachable; + cpu.writer.writeAll("] ") catch unreachable; + lib.format(cpu.writer, format, args) catch unreachable; + cpu.writer.writeByte('\n') catch unreachable; + + lock.release(); + } + + pub const log_level = lib.log.Level.debug; +}; + +pub fn panic(message: []const u8, _: ?*lib.StackTrace, _: ?usize) noreturn { + @call(.always_inline, cpu.panic, .{ "{s}", .{message} }); +} + +comptime { + @export(cpu.arch.entryPoint, .{ .name = "_start", .linkage = .Strong }); +} diff --git a/src/cpu/test.zig b/src/cpu/test.zig new file mode 100644 index 0000000..c079be4 --- /dev/null +++ b/src/cpu/test.zig @@ -0,0 +1,21 @@ +const lib = @import("lib"); +const log = lib.log.scoped(.TEST); +const privileged = @import("privileged"); +const writer = privileged.writer; + +test "Hello kernel" { + lib.testing.log_level = .debug; + log.debug("Hello kernel test", .{}); +} + +pub const std_options = struct { + pub fn logFn(comptime level: lib.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype) void { + _ = level; + writer.writeAll("[CPU DRIVER] ") catch unreachable; + writer.writeByte('[') catch unreachable; + writer.writeAll(@tagName(scope)) catch unreachable; + writer.writeAll("] ") catch unreachable; + lib.format(writer, format, args) catch unreachable; + writer.writeByte('\n') catch unreachable; + } +}; diff --git a/src/cpu/test_runner.zig b/src/cpu/test_runner.zig new file mode 100644 index 0000000..b80a220 --- /dev/null +++ b/src/cpu/test_runner.zig @@ -0,0 +1,38 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.TEST); +const privileged = @import("privileged"); +const QEMU = lib.QEMU; + +const cpu = @import("cpu"); + +const RunAllTestResult = error{ + failure, +}; + +pub fn runAllTests() RunAllTestResult!void { + comptime assert(lib.is_test); + const test_functions = @import("builtin").test_functions; + var failed_test_count: usize = 0; + for (test_functions) |test_function| { + test_function.func() catch |err| { + log.err("Test failed: {}", .{err}); + failed_test_count += 1; + }; + } + + const test_count = test_functions.len; + assert(QEMU.isa_debug_exit.io_size == @sizeOf(u32)); + const exit_code = switch (failed_test_count) { + 0 => blk: { + log.info("All {} tests passed.", .{test_count}); + break :blk .success; + }, + else => blk: { + log.info("Run {} tests. Failed {}.", .{ test_count, failed_test_count }); + break :blk .failure; + }, + }; + + cpu.shutdown(exit_code); +} diff --git a/src/host.zig b/src/host.zig new file mode 100644 index 0000000..5ca1104 --- /dev/null +++ b/src/host.zig @@ -0,0 +1,74 @@ +const lib = @import("lib"); + +comptime { + if (lib.os == .freestanding) @compileError("Host file included in non-host target"); +} + +const std = @import("std"); +pub const ChildProcess = std.ChildProcess; + +pub const posix = std.os; +pub const sync = std.os.sync; + +pub const fs = std.fs; +pub const cwd = fs.cwd; +pub const Dir = fs.Dir; +pub const basename = fs.path.basename; +pub const dirname = fs.path.dirname; + +const io = std.io; +pub const getStdOut = std.io.getStdOut; + +const heap = std.heap; +pub const ArenaAllocator = heap.ArenaAllocator; +pub const page_allocator = heap.page_allocator; + +pub const ArrayList = std.ArrayList; +pub const ArrayListAligned = std.ArrayListAligned; + +pub const time = std.time; + +// Build imports +pub const build = std.build; + +pub fn allocateZeroMemory(bytes: u64) ![]align(0x1000) u8 { + switch (lib.os) { + .windows => { + const windows = std.os.windows; + return @as([*]align(0x1000) u8, @ptrCast(@alignCast(try windows.VirtualAlloc(null, bytes, windows.MEM_RESERVE | windows.MEM_COMMIT, windows.PAGE_READWRITE))))[0..bytes]; + }, + // Assume all systems are POSIX + else => { + const mmap = std.os.mmap; + const PROT = std.os.PROT; + const MAP = std.os.MAP; + return try mmap(null, bytes, PROT.READ | PROT.WRITE, MAP.PRIVATE | MAP.ANONYMOUS, -1, 0); + }, + .freestanding => @compileError("Not implemented yet"), + } +} + +pub const ExecutionError = error{failed}; +pub fn spawnProcess(arguments: []const []const u8, allocator: lib.ZigAllocator) !void { + var process = ChildProcess.init(arguments, allocator); + process.stdout_behavior = .Ignore; + const execution_result = try process.spawnAndWait(); + + switch (execution_result) { + .Exited => |exit_code| { + switch (exit_code) { + 0 => {}, + else => return ExecutionError.failed, + } + }, + .Signal => |signal_code| { + _ = signal_code; + unreachable; + }, + .Stopped, .Unknown => unreachable, + } +} + +pub const panic = std.debug.panic; + +pub const allocateArguments = std.process.argsAlloc; diff --git a/src/host/disk_image_builder.zig b/src/host/disk_image_builder.zig new file mode 100644 index 0000000..6813aea --- /dev/null +++ b/src/host/disk_image_builder.zig @@ -0,0 +1,250 @@ +const lib = @import("lib"); +const assert = lib.assert; +const FAT32 = lib.Filesystem.FAT32; +const PartitionTable = lib.PartitionTable; +const GPT = PartitionTable.GPT; +const MBR = PartitionTable.MBR; +const host = @import("host"); + +pub const ImageDescription = struct { + partition_name: []const u8, + partition_start_lba: u64, + disk_sector_count: u64, + disk_sector_size: u64, + partition_filesystem: lib.FilesystemType, +}; + +pub extern fn deploy(device_path: [*:0]const u8, limine_hdd_ptr: [*]const u8, limine_hdd_len: usize) callconv(.C) c_int; + +const Disk = lib.Disk; +pub const DiskImage = extern struct { + disk: Disk, + buffer_ptr: [*]u8, + + pub fn write(disk: *Disk, bytes: []const u8, sector_offset: u64, commit_memory_to_disk: bool) Disk.WriteError!void { + const need_write = !(disk.type == .memory and !commit_memory_to_disk); + if (need_write) { + const disk_image = @fieldParentPtr(DiskImage, "disk", disk); + assert(disk_image.disk.disk_size > 0); + //assert(disk.disk.partition_count == 1); + assert(bytes.len > 0); + //assert(disk.disk.disk_size == disk.buffer.items.len); + const byte_offset = sector_offset * disk_image.disk.sector_size; + + if (byte_offset + bytes.len > disk_image.disk.disk_size) return Disk.WriteError.disk_size_overflow; + + @memcpy(disk_image.getBuffer()[byte_offset .. byte_offset + bytes.len], bytes); + } + } + + pub fn read(disk: *Disk, sector_count: u64, sector_offset: u64, provided_buffer: ?[]const u8) Disk.ReadError!Disk.ReadResult { + assert(provided_buffer == null); + const disk_image = @fieldParentPtr(DiskImage, "disk", disk); + assert(disk_image.disk.disk_size > 0); + assert(sector_count > 0); + //assert(disk.disk.disk_size == disk.buffer.items.len); + const byte_count = sector_count * disk_image.disk.sector_size; + const byte_offset = sector_offset * disk_image.disk.sector_size; + if (byte_offset + byte_count > disk.disk_size) { + return Disk.ReadError.read_error; + } + return .{ + .buffer = disk_image.getBuffer()[byte_offset .. byte_offset + byte_count].ptr, + .sector_count = sector_count, + }; + } + + pub fn readCache(disk: *Disk, sector_count: u64, sector_offset: u64) Disk.ReadError!Disk.ReadResult { + _ = sector_count; + _ = sector_offset; + _ = disk; + return error.read_error; + } + + pub fn fromZero(sector_count: usize, sector_size: u16) !DiskImage { + const disk_bytes = try host.allocateZeroMemory(sector_count * sector_size); + var disk_image = DiskImage{ + .disk = .{ + .type = .memory, + .callbacks = .{ + .read = DiskImage.read, + .write = DiskImage.write, + .readCache = DiskImage.readCache, + }, + .disk_size = disk_bytes.len, + .sector_size = sector_size, + .cache_size = 0, + }, + .buffer_ptr = disk_bytes.ptr, + }; + + return disk_image; + } + + pub fn createFAT(disk_image: *DiskImage, comptime image: ImageDescription, original_gpt_cache: ?GPT.Partition.Cache) !GPT.Partition.Cache { + const gpt_cache = try GPT.create(&disk_image.disk, if (original_gpt_cache) |o_gpt_cache| o_gpt_cache.gpt.header else null); + const partition_name_u16 = lib.unicode.utf8ToUtf16LeStringLiteral(image.partition_name); + const gpt_partition_cache = try gpt_cache.addPartition(image.partition_filesystem, partition_name_u16, image.partition_start_lba, gpt_cache.header.last_usable_lba, if (original_gpt_cache) |o_gpt_cache| o_gpt_cache.partition else null); + + return gpt_partition_cache; + } + + pub fn fromFile(file_path: []const u8, sector_size: u16, allocator: lib.ZigAllocator) !DiskImage { + const disk_memory = try host.cwd().readFileAlloc(allocator, file_path, lib.maxInt(usize)); + + var disk_image = DiskImage{ + .disk = .{ + .type = .memory, + .callbacks = .{ + .read = DiskImage.read, + .write = DiskImage.write, + .readCache = DiskImage.readCache, + }, + .disk_size = disk_memory.len, + .sector_size = sector_size, + .cache_size = 0, + }, + .buffer_ptr = disk_memory.ptr, + }; + + return disk_image; + } + + const File = struct { + handle: lib.File, + size: usize, + }; + + pub inline fn getBuffer(disk_image: DiskImage) []u8 { + return disk_image.buffer_ptr[0..disk_image.disk.disk_size]; + } +}; + +pub fn format(disk: *Disk, partition_range: Disk.PartitionRange, copy_mbr: ?*const MBR.Partition) !FAT32.Cache { + if (disk.type != .memory) @panic("disk is not memory"); + const fat_partition_mbr_lba = partition_range.first_lba; + const fat_partition_mbr = try disk.readTypedSectors(MBR.Partition, fat_partition_mbr_lba, null, .{}); + + const sectors_per_track = 32; + const total_sector_count_32 = @as(u32, @intCast(lib.alignBackward(u64, partition_range.last_lba - partition_range.first_lba, sectors_per_track))); + const fat_count = FAT32.count; + + var cluster_size: u8 = 1; + const max_cluster_size = 128; + var fat_data_sector_count: u32 = undefined; + var fat_length_32: u32 = undefined; + var cluster_count_32: u32 = undefined; + + while (true) { + assert(cluster_size > 0); + fat_data_sector_count = total_sector_count_32 - lib.alignForward(u32, FAT32.default_reserved_sector_count, cluster_size); + cluster_count_32 = (fat_data_sector_count * disk.sector_size + fat_count * 8) / (cluster_size * disk.sector_size + fat_count * 4); + fat_length_32 = lib.alignForward(u32, cdiv((cluster_count_32 + 2) * 4, disk.sector_size), cluster_size); + cluster_count_32 = (fat_data_sector_count - fat_count * fat_length_32) / cluster_size; + const max_cluster_size_32 = @min(fat_length_32 * disk.sector_size / 4, FAT32.getMaxCluster(.fat32)); + if (cluster_count_32 > max_cluster_size_32) { + cluster_count_32 = 0; + } + if (cluster_count_32 != 0 and cluster_count_32 < FAT32.getMinCluster(.fat32)) { + cluster_count_32 = 0; + } + + if (cluster_count_32 != 0) break; + + cluster_size <<= 1; + + const keep_going = cluster_size != 0 and cluster_size <= max_cluster_size; + if (!keep_going) break; + @panic("unexpected fat32 bug"); + } + + var root_directory_entries: u64 = 0; + _ = root_directory_entries; + + const reserved_sector_count = lib.alignForward(u16, FAT32.default_reserved_sector_count, cluster_size); + + fat_partition_mbr.* = MBR.Partition{ + .bpb = .{ + .dos3_31 = .{ + .dos2_0 = .{ + .jmp_code = .{ 0xeb, 0x58, 0x90 }, + .oem_identifier = "mkfs.fat".*, + .sector_size = disk.sector_size, + .cluster_sector_count = cluster_size, + .reserved_sector_count = reserved_sector_count, + .fat_count = fat_count, + .root_entry_count = 0, + .total_sector_count_16 = 0, + .media_descriptor = 0xf8, + .fat_sector_count_16 = 0, + }, + .physical_sectors_per_track = sectors_per_track, + .disk_head_count = 8, + .hidden_sector_count = @as(u32, @intCast(partition_range.first_lba)), + .total_sector_count_32 = total_sector_count_32, + }, + .fat_sector_count_32 = fat_length_32, + .drive_description = 0, + .version = .{ 0, 0 }, + .root_directory_cluster_offset = FAT32.starting_cluster, + .fs_info_sector = FAT32.default_fs_info_sector, + .backup_boot_record_sector = FAT32.default_backup_boot_record_sector, + .drive_number = 0x80, + .extended_boot_signature = 0x29, + .serial_number = if (copy_mbr) |copy_partition_mbr| copy_partition_mbr.bpb.serial_number else @truncate(@as(u64, @intCast(host.time.microTimestamp()))), + .volume_label = "NO NAME ".*, + .filesystem_type = "FAT32 ".*, + }, + .code = [_]u8{ + 0xe, 0x1f, 0xbe, 0x77, 0x7c, 0xac, 0x22, 0xc0, 0x74, 0xb, 0x56, 0xb4, 0xe, 0xbb, 0x7, 0x0, 0xcd, 0x10, 0x5e, 0xeb, 0xf0, 0x32, 0xe4, 0xcd, 0x16, 0xcd, 0x19, 0xeb, 0xfe, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x2e, 0x20, 0x20, 0x50, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6f, 0x6f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x66, 0x6c, 0x6f, 0x70, 0x70, 0x79, 0x20, 0x61, 0x6e, 0x64, 0xd, 0xa, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x72, 0x79, 0x20, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x20, 0x2e, 0x2e, 0x2e, 0x20, 0xd, 0xa, + } ++ [1]u8{0} ** 227, + // This should be zero + .partitions = lib.zeroes([4]MBR.LegacyPartition), + }; + + try disk.writeTypedSectors(MBR.Partition, fat_partition_mbr, fat_partition_mbr_lba, false); + + const backup_boot_record_sector = partition_range.first_lba + fat_partition_mbr.bpb.backup_boot_record_sector; + const backup_boot_record = try disk.readTypedSectors(MBR.Partition, backup_boot_record_sector, null, .{}); + backup_boot_record.* = fat_partition_mbr.*; + try disk.writeTypedSectors(MBR.Partition, backup_boot_record, backup_boot_record_sector, false); + + const fs_info_lba = partition_range.first_lba + fat_partition_mbr.bpb.fs_info_sector; + const fs_info = try disk.readTypedSectors(FAT32.FSInfo, fs_info_lba, null, .{}); + fs_info.* = .{ + .lead_signature = 0x41615252, + .signature = 0x61417272, + .free_cluster_count = cluster_count_32, + .last_allocated_cluster = 0, + .trail_signature = 0xaa550000, + }; + try disk.writeTypedSectors(FAT32.FSInfo, fs_info, fs_info_lba, false); + + const cache = FAT32.Cache{ + .disk = disk, + .partition_range = partition_range, + .mbr = fat_partition_mbr, + .fs_info = fs_info, + .allocator = null, + }; + + // TODO: write this properly + + try cache.registerCluster(0, FAT32.Entry.reserved_and_should_not_be_used_eof, null); + try cache.registerCluster(1, FAT32.Entry.allocated_and_eof, null); + try cache.registerCluster(2, FAT32.Entry.reserved_and_should_not_be_used_eof, null); + + cache.fs_info.last_allocated_cluster = 2; + cache.fs_info.free_cluster_count = cluster_count_32 - 1; + + const backup_fs_info_lba = backup_boot_record_sector + backup_boot_record.bpb.fs_info_sector; + const backup_fs_info = try disk.readTypedSectors(FAT32.FSInfo, backup_fs_info_lba, null, .{}); + backup_fs_info.* = fs_info.*; + try disk.writeTypedSectors(FAT32.FSInfo, backup_fs_info, backup_fs_info_lba, false); + + return cache; +} + +fn cdiv(a: u32, b: u32) u32 { + return (a + b - 1) / b; +} diff --git a/src/host/disk_image_builder/boot_disk.zig b/src/host/disk_image_builder/boot_disk.zig new file mode 100644 index 0000000..55823b1 --- /dev/null +++ b/src/host/disk_image_builder/boot_disk.zig @@ -0,0 +1,405 @@ +const bootloader = @import("bootloader"); +const bios = @import("bios"); +const uefi = @import("uefi"); +const host = @import("host"); +const lib = @import("lib"); +const assert = lib.assert; +const MBR = lib.PartitionTable.MBR; + +pub const BootDisk = extern struct { + bpb: MBR.BIOSParameterBlock.DOS7_1_79, + code: [code_byte_count]u8, + gdt: GDT, + gdt_descriptor: GDT.Descriptor, + dap: MBR.DAP align(2), + partitions: [4]MBR.LegacyPartition align(2), + signature: [2]u8 = [_]u8{ 0x55, 0xaa }, + + const code_byte_count = 0x10d; + + const GDT = bootloader.arch.x86_64.GDT; + + const hlt = [_]u8{0xf4}; + const clc = [_]u8{0xf8}; + const cli = [_]u8{0xfa}; + const sti = [_]u8{0xfb}; + const cld = [_]u8{0xfc}; + + const xor = 0x31; + const xor_si_si_16 = [_]u8{ xor, 0xf6 }; + const push_ds = [_]u8{0x1e}; + const mov_ds_si = [_]u8{ 0x8e, 0xde }; + const mov_es_si = [_]u8{ 0x8e, 0xc6 }; + const mov_ss_si = [_]u8{ 0x8e, 0xd6 }; + const mov_sp_stack_top = [_]u8{0xbc} ++ lib.asBytes(&bios.stack_top).*; + const mov_bx_0xaa55 = [_]u8{ 0xbb, 0xaa, 0x55 }; + const cmp_bx_0xaa55 = [_]u8{ 0x81, 0xfb, 0x55, 0xaa }; + + const jc = 0x72; + const jne = 0x75; + + const mov_eax_cr0 = [_]u8{ 0x0f, 0x20, 0xc0 }; + const mov_cr0_eax = [_]u8{ 0x0f, 0x22, 0xc0 }; + + const code_32 = @offsetOf(GDT, "code_32"); + const data_32 = @offsetOf(GDT, "data_32"); + + const reload_data_segments_32 = [_]u8{ + 0xb8, data_32, 0x00, 0x00, 0x00, // mov eax, 0x10 + 0x8e, 0xd8, // mov ds, ax + 0x8e, 0xc0, // mov es, ax + 0x8e, 0xe0, // mov fs, ax + 0x8e, 0xe8, // mov gs, ax + 0x8e, 0xd0, // mov ss, ax + }; + const xor_eax_eax = [_]u8{ xor, 0xc8 }; + const xor_ebx_ebx = [_]u8{ xor, 0xdb }; + const nop = [_]u8{0x90}; + const rep_movsb = [_]u8{ 0xf3, 0xa4 }; + + fn or_ax(imm8: u8) [4]u8 { + return .{ 0x66, 0x83, 0xc8, imm8 }; + } + + fn int(interrupt_number: u8) [2]u8 { + return .{ 0xcd, interrupt_number }; + } + + fn mov_cx(imm16: u16) [3]u8 { + const imm_bytes = lib.asBytes(&imm16); + return .{ 0xb9, imm_bytes[0], imm_bytes[1] }; + } + + fn mov_di(imm16: u16) [3]u8 { + const imm_bytes = lib.asBytes(&imm16); + return .{ 0xbf, imm_bytes[0], imm_bytes[1] }; + } + + fn mov_si(imm16: u16) [3]u8 { + const imm_bytes = lib.asBytes(&imm16); + return .{ 0xbe, imm_bytes[0], imm_bytes[1] }; + } + + fn mov_ah(imm8: u8) [2]u8 { + return .{ 0xb4, imm8 }; + } + + pub fn fill(mbr: *BootDisk, allocator: lib.ZigAllocator, dap: MBR.DAP) !void { + // Hardcoded jmp to end of FAT32 BPB + const jmp_to_end_of_bpb = .{ 0xeb, @sizeOf(MBR.BIOSParameterBlock.DOS7_1_79) - 2 }; + mbr.bpb.dos3_31.dos2_0.jmp_code = jmp_to_end_of_bpb ++ nop; + mbr.dap = dap; + mbr.gdt = .{}; + mbr.gdt_descriptor = .{ + .limit = @sizeOf(GDT) - 1, + .address = bios.mbr_offset + @offsetOf(BootDisk, "gdt"), + }; + var assembler = Assembler{ + .boot_disk = mbr, + .patches = host.ArrayList(Patch).init(allocator), + .labels = host.ArrayList(Label.Offset).init(allocator), + }; + defer assembler.patch(); + + // 16-bit + assembler.addInstruction(&cli); + assembler.addInstruction(&xor_si_si_16); + assembler.addInstruction(&mov_ds_si); + assembler.addInstruction(&mov_es_si); + assembler.addInstruction(&mov_ss_si); + assembler.addInstruction(&mov_sp_stack_top); + assembler.addInstruction(&mov_si(0x7c00)); + assembler.addInstruction(&mov_di(bios.mbr_offset)); + assembler.addInstruction(&mov_cx(lib.default_sector_size)); + assembler.addInstruction(&cld); + assembler.addInstruction(&rep_movsb); + try assembler.far_jmp_16(0x0, .reload_cs_16); + + try assembler.add_instruction_with_label(&sti, .reload_cs_16); + assembler.addInstruction(&mov_ah(0x41)); + assembler.addInstruction(&mov_bx_0xaa55); + assembler.addInstruction(&int(0x13)); + try assembler.jcc(jc, .error16); + assembler.addInstruction(&cmp_bx_0xaa55); + try assembler.jcc(jne, .error16); + try assembler.add_instruction_with_label(&mov_ah(0x42), .read_sectors); + try assembler.mov_si(.dap); + assembler.addInstruction(&clc); + assembler.addInstruction(&int(0x13)); + + try assembler.jcc(jc, .error16); + // Save real mode + try assembler.lgdt_16(.gdt_descriptor); + assembler.addInstruction(&cli); + assembler.addInstruction(&mov_eax_cr0); + assembler.addInstruction(&or_ax(1)); + assembler.addInstruction(&mov_cr0_eax); + try assembler.far_jmp_16(code_32, .protected_mode); + + try assembler.add_instruction_with_label(&cli, .error16); + assembler.addInstruction(&hlt); + + // 32-bit + try assembler.add_instruction_with_label(&reload_data_segments_32, .protected_mode); + assembler.addInstruction(&xor_eax_eax); + assembler.addInstruction(&xor_ebx_ebx); + + assembler.addInstruction(&[_]u8{0xbe} ++ lib.asBytes(&@as(u32, 0x600))); + assembler.addInstruction(&[_]u8{0xbf} ++ lib.asBytes(&@as(u32, 0x10000))); + const aligned_file_size = @as(u32, dap.sector_count * lib.default_sector_size); + assembler.addInstruction(&[_]u8{0xb9} ++ lib.asBytes(&aligned_file_size)); + assembler.addInstruction(&cld); + assembler.addInstruction(&[_]u8{ 0xf3, 0xa4 }); + + // mov ebp, 0x10000 + assembler.addInstruction(&[_]u8{0xbd} ++ lib.asBytes(&@as(u32, 0x10000))); + + //b0: 66 8b 5d 2a mov bx,WORD PTR [rbp+0x2a] // BX: Program header size + assembler.addInstruction(&.{ 0x66, 0x8b, 0x5d, 0x2a }); + //b4: 66 8b 45 2c mov ax,WORD PTR [rbp+0x2c] // AX: Program header count + assembler.addInstruction(&.{ 0x66, 0x8b, 0x45, 0x2c }); + //b8: 8b 55 1c mov edx,DWORD PTR [rbp+0x1c] // EDX: Program header offset + assembler.addInstruction(&.{ 0x8b, 0x55, 0x1c }); + //bb: 01 ea add edx,ebp // EDX: program header base address + assembler.addInstruction(&.{ 0x01, 0xea }); + //bd: 83 3a 01 cmp DWORD PTR [rdx],0x1 // [EDX]: Program header type. Compare if it is PT_LOAD + try assembler.add_instruction_with_label(&.{ 0x83, 0x3a, 0x01 }, .elf_loader_loop); + //c0: 75 0d jne 0xcf // Continue if not PT_LOAD + try assembler.jcc(jne, .elf_loader_loop_continue); + //c2: 89 ee mov esi,ebp // ESI: ELF base address + assembler.addInstruction(&.{ 0x89, 0xee }); + //c4: 03 72 04 add esi,DWORD PTR [rdx+0x4] // ESI: program segment address, source of the memcpy + + assembler.addInstruction(&.{ 0x03, 0x72, 0x04 }); + //c7: 8b 7a 0c mov edi,DWORD PTR [rdx+0xc] // EDI: program segment physical address, destination of the memcpy + assembler.addInstruction(&.{ 0x8b, 0x7a, 0x0c }); + //ca: 8b 4a 10 mov ecx,DWORD PTR [rdx+0x10] // ECX: program header file size, bytes to memcpy + assembler.addInstruction(&.{ 0x8b, 0x4a, 0x10 }); + //cd: f3 a4 rep movs BYTE PTR es:[rdi],BYTE PTR ds:[rsi] + assembler.addInstruction(&.{ 0xf3, 0xa4 }); + //cf: 01 da add edx,ebx + try assembler.add_instruction_with_label(&.{ 0x01, 0xda }, .elf_loader_loop_continue); + //d1: 48 dec eax + assembler.addInstruction(&.{0x48}); + // jnz loop + const jnz = jne; + try assembler.jcc(jnz, .elf_loader_loop); + //d5: 8b 5d 18 mov ebx,DWORD PTR [rbp+0x18] + assembler.addInstruction(&.{ 0x8b, 0x5d, 0x18 }); + + // EXPERIMENT: stack to a higher address + assembler.addInstruction(.{@as(u8, 0xbd)} ++ lib.asBytes(&bios.loader_stack_top)); + + //d8: ff e3 jmp rbx + assembler.addInstruction(&.{ 0xff, 0xe3 }); + // log.debug("MBR code length: 0x{x}/0x{x}", .{ assembler.code_index, assembler.boot_disk.code.len }); + } + + const Label = enum { + reload_cs_16, + error16, + read_sectors, + dap, + dap_pointer, + gdt_descriptor, + protected_mode, + elf_loader_loop, + elf_loader_loop_continue, + + const Offset = struct { + label: Label, + offset: u8, + }; + }; + + const Patch = struct { + label: Label, + label_size: u8, + label_offset: u8, + // For relative labels, instruction len to compute RIP-relative address + // For absolute labels, offset in which to introduce a 8-bit absolute offset + label_type: enum { + relative, + absolute, + }, + label_section: enum { + code, + data, + }, + instruction_starting_offset: u8, + instruction_len: u8, + }; + + pub const Assembler = struct { + boot_disk: *BootDisk, + code_index: u8 = 0, + patches: host.ArrayList(Patch), + labels: host.ArrayList(Label.Offset), + + pub inline fn addInstruction(assembler: *Assembler, instruction_bytes: []const u8) void { + assert(assembler.code_index + instruction_bytes.len <= assembler.boot_disk.code.len); + // lib.print("[0x{x:0>4}] ", .{bios.mbr_offset + @offsetOf(BootDisk, "code") + assembler.code_index}); + // for (instruction_bytes) |byte| { + // lib.print("{x:0>2} ", .{byte}); + // } + // lib.print("\n", .{}); + @memcpy(assembler.boot_disk.code[assembler.code_index .. assembler.code_index + instruction_bytes.len], instruction_bytes); + assembler.code_index += @as(u8, @intCast(instruction_bytes.len)); + } + + pub fn add_instruction_with_label(assembler: *Assembler, instruction_bytes: []const u8, label: Label) !void { + try assembler.labels.append(.{ .label = label, .offset = assembler.code_index }); + assembler.addInstruction(instruction_bytes); + } + + pub fn far_jmp_16(assembler: *Assembler, segment: u16, label: Label) !void { + const segment_bytes = lib.asBytes(&segment); + const offset_bytes = lib.asBytes(&bios.mbr_offset); + const instruction_bytes = [_]u8{ 0xea, offset_bytes[0], offset_bytes[1], segment_bytes[0], segment_bytes[1] }; + try assembler.patches.append(.{ + .label = label, + .label_size = @sizeOf(u16), + .label_offset = 1, + .label_type = .absolute, + .label_section = .code, + .instruction_starting_offset = assembler.code_index, + .instruction_len = instruction_bytes.len, + }); + assembler.addInstruction(&instruction_bytes); + } + + pub fn jcc(assembler: *Assembler, jmp_opcode: u8, label: Label) !void { + const instruction_bytes = [_]u8{ jmp_opcode, 0x00 }; + try assembler.patches.append(.{ + .label = label, + .label_size = @sizeOf(u8), + .label_offset = 1, + .label_type = .relative, + .label_section = .code, + .instruction_starting_offset = assembler.code_index, + .instruction_len = instruction_bytes.len, + }); + assembler.addInstruction(&instruction_bytes); + } + + pub fn mov_si(assembler: *Assembler, label: Label) !void { + const instruction_bytes = [_]u8{ 0xbe, 0x00, 0x00 }; + try assembler.patches.append(.{ + .label = label, + .label_size = @sizeOf(u16), + .label_offset = 1, + .label_type = .absolute, + .label_section = .data, + .instruction_starting_offset = assembler.code_index, + .instruction_len = instruction_bytes.len, + }); + assembler.addInstruction(&instruction_bytes); + } + + pub fn lgdt_16(assembler: *Assembler, label: Label) !void { + const instruction_bytes = [_]u8{ 0x0f, 0x01, 0x16, 0x00, 0x00 }; + try assembler.patches.append(.{ + .label = label, + .label_size = @sizeOf(u16), + .label_offset = 3, + .label_type = .absolute, + .label_section = .data, + .instruction_starting_offset = assembler.code_index, + .instruction_len = instruction_bytes.len, + }); + assembler.addInstruction(&instruction_bytes); + } + + pub fn mov_ebp_dword_ptr(assembler: *Assembler, label: Label) !void { + const instruction_bytes = [_]u8{ 0x8b, 0x2d, 0x00, 0x00, 0x00, 0x00 }; + try assembler.patches.append(.{ + .label = label, + .label_size = @sizeOf(u16), + .label_offset = 2, + .label_type = .absolute, + .label_section = .data, + .instruction_starting_offset = assembler.code_index, + .instruction_len = instruction_bytes.len, + }); + assembler.addInstruction(&instruction_bytes); + } + + pub fn patch(assembler: *Assembler) void { + var patched: usize = 0; + + next_patch: for (assembler.patches.items) |patch_descriptor| { + const index = patch_descriptor.instruction_starting_offset + patch_descriptor.label_offset; + // log.debug("Trying to patch instruction. Section: {s}. Label: {s}. Label size: {}. Label type: {s}", .{ @tagName(patch_descriptor.label_section), @tagName(patch_descriptor.label), patch_descriptor.label_size, @tagName(patch_descriptor.label_type) }); + switch (patch_descriptor.label_section) { + .code => for (assembler.labels.items) |label_descriptor| { + if (patch_descriptor.label == label_descriptor.label) { + switch (patch_descriptor.label_type) { + .absolute => { + assert(patch_descriptor.label_size == @sizeOf(u16)); + @as(*align(1) u16, @ptrCast(&assembler.boot_disk.code[index])).* = bios.mbr_offset + @offsetOf(BootDisk, "code") + label_descriptor.offset; + }, + .relative => { + assert(patch_descriptor.label_size == @sizeOf(u8)); + assert(patch_descriptor.label_section == .code); + const computed_after_instruction_offset = patch_descriptor.instruction_starting_offset + patch_descriptor.instruction_len; + const operand_a = @as(isize, @intCast(label_descriptor.offset)); + const operand_b = @as(isize, @intCast(computed_after_instruction_offset)); + const diff = @as(u8, @bitCast(@as(i8, @intCast(operand_a - operand_b)))); + @as(*align(1) u8, @ptrCast(&assembler.boot_disk.code[index])).* = diff; + }, + } + + // const instruction_start = bios.mbr_offset + @offsetOf(BootDisk, "code") + patch_descriptor.instruction_starting_offset; + // lib.print("[0x{x:0>4}] ", .{instruction_start}); + // const instruction_bytes = assembler.boot_disk.code[patch_descriptor.instruction_starting_offset .. patch_descriptor.instruction_starting_offset + patch_descriptor.instruction_len]; + // for (instruction_bytes) |byte| { + // lib.print("{x:0>2} ", .{byte}); + // } + // lib.print("\n", .{}); + patched += 1; + continue :next_patch; + } + }, + .data => { + // log.debug("Data: {s}", .{@tagName(patch_descriptor.label)}); + const dap_offset = @offsetOf(BootDisk, "dap"); + // log.debug("DAP offset: 0x{x}", .{dap_offset}); + switch (patch_descriptor.label_type) { + .absolute => { + assert(patch_descriptor.label_size == @sizeOf(u16)); + const ptr = bios.mbr_offset + @as(u16, switch (patch_descriptor.label) { + .dap => dap_offset, + .gdt_descriptor => @offsetOf(BootDisk, "gdt_descriptor"), + .dap_pointer => dap_offset + @offsetOf(MBR.DAP, "offset"), + else => @panic("unreachable tag"), + }); + // log.debug("Ptr patched: 0x{x}", .{ptr}); + @as(*align(1) u16, @ptrCast(&assembler.boot_disk.code[index])).* = ptr; + }, + .relative => @panic("unreachable relative"), + } + + // log.debug("Patched instruction:", .{}); + // const instruction_start = bios.mbr_offset + @offsetOf(BootDisk, "code") + patch_descriptor.instruction_starting_offset; + // lib.print("[0x{x:0>4}] ", .{instruction_start}); + // const instruction_bytes = assembler.boot_disk.code[patch_descriptor.instruction_starting_offset .. patch_descriptor.instruction_starting_offset + patch_descriptor.instruction_len]; + // for (instruction_bytes) |byte| { + // lib.print("{x:0>2} ", .{byte}); + // } + // lib.print("\n", .{}); + + patched += 1; + continue :next_patch; + }, + } + + // log.debug("Patch count: {}. Patched count: {}", .{ assembler.patches.items.len, patched }); + assert(patched == assembler.patches.items.len); + } + } + }; + + comptime { + assert(@sizeOf(@This()) == lib.default_sector_size); + } +}; diff --git a/src/host/disk_image_builder/main.zig b/src/host/disk_image_builder/main.zig new file mode 100644 index 0000000..4a9aca1 --- /dev/null +++ b/src/host/disk_image_builder/main.zig @@ -0,0 +1,302 @@ +const host = @import("host"); +const lib = @import("lib"); +const bios = @import("bios"); +const limine_installer = @import("limine_installer"); + +const assert = lib.assert; +const log = lib.log.scoped(.DiskImageBuilder); + +const Disk = lib.Disk; +const GPT = lib.PartitionTable.GPT; +const MBR = lib.PartitionTable.MBR; +const FAT32 = lib.Filesystem.FAT32; + +const max_file_length = lib.maxInt(usize); + +const Configuration = lib.Configuration; + +const disk_image_builder = @import("../disk_image_builder.zig"); +const ImageDescription = disk_image_builder.ImageDescription; +const DiskImage = disk_image_builder.DiskImage; +const format = disk_image_builder.format; + +const BootDisk = @import("boot_disk.zig").BootDisk; + +const dap_file_read = 0x600; +const file_copy_offset = 0x10000; + +const Error = error{ + configuration_wrong_argument, + configuration_not_found, + cpu_not_found, + bootloader_path_not_found, + user_programs_not_found, + image_configuration_path_not_found, + disk_image_path_not_found, + wrong_arguments, + not_implemented, +}; + +fn readFileAbsolute(allocator: *lib.Allocator.Wrapped, absolute_file_path: []const u8) ![]const u8 { + return try ((try host.fs.openFileAbsolute(absolute_file_path, .{})).readToEndAlloc(allocator.zigUnwrap(), max_file_length)); +} + +fn readFileAbsoluteToArrayList(array_list: *host.ArrayList(u8), absolute_file_path: []const u8) !void { + const file = try host.fs.openFileAbsolute(absolute_file_path, .{}); + try file.reader().readAllArrayList(array_list, lib.maxInt(usize)); +} + +fn addFileToBundle(file: host.fs.File, file_list: *host.ArrayList(u8), name: []const u8, file_contents: *host.ArrayList(u8)) !void { + try file_contents.appendNTimes(0, lib.alignForward(usize, file_contents.items.len, 0x10) - file_contents.items.len); + const offset = file_contents.items.len; + try file.reader().readAllArrayList(file_contents, lib.maxInt(usize)); + const stat = try file.stat(); + try file_list.writer().writeIntLittle(u32, @as(u32, @intCast(offset))); + try file_list.writer().writeIntLittle(u32, @as(u32, @intCast(stat.size))); + try file_list.writer().writeIntLittle(u8, @as(u8, @intCast(name.len))); + try file_list.appendSlice(name); +} + +pub fn main() anyerror!void { + var arena_allocator = host.ArenaAllocator.init(host.page_allocator); + defer arena_allocator.deinit(); + var wrapped_allocator = lib.Allocator.wrap(arena_allocator.allocator()); + + const arguments = (try host.allocateArguments(wrapped_allocator.zigUnwrap()))[1..]; + + const arguments_result: lib.ArgumentParser.DiskImageBuilder.Result = blk: { + var argument_parser = lib.ArgumentParser.DiskImageBuilder{}; + var argument_configuration: ?Configuration = null; + var argument_bootloader: ?[]const u8 = null; + var argument_cpu: ?[]const u8 = null; + var argument_user_programs: ?[]const []const u8 = null; + var argument_image_configuration_path: ?[]const u8 = null; + var argument_disk_image_path: ?[]const u8 = null; + var argument_index: usize = 0; + + while (argument_parser.next()) |argument_type| switch (argument_type) { + .disk_image_path => { + assert(@intFromEnum(argument_type) == 0); + argument_disk_image_path = arguments[argument_index]; + argument_index += 1; + }, + .configuration => { + argument_configuration = undefined; + const configuration = &argument_configuration.?; + inline for (lib.fields(Configuration)) |field| { + @field(configuration, field.name) = lib.stringToEnum(field.type, arguments[argument_index]) orelse return Error.configuration_wrong_argument; + argument_index += 1; + } + }, + .image_configuration_path => { + argument_image_configuration_path = arguments[argument_index]; + argument_index += 1; + }, + .bootloader => { + const argument = arguments[argument_index]; + argument_index += 1; + if (!lib.equal(u8, argument, "-")) { + argument_bootloader = argument; + } + }, + .cpu => { + argument_cpu = arguments[argument_index]; + argument_index += 1; + }, + .user_programs => { + argument_user_programs = arguments[argument_index..]; + argument_index += argument_user_programs.?.len; + }, + }; + + assert(argument_index == arguments.len); + break :blk .{ + .configuration = argument_configuration orelse return Error.configuration_not_found, + .disk_image_path = argument_disk_image_path orelse return Error.disk_image_path_not_found, + .image_configuration_path = argument_image_configuration_path orelse return Error.image_configuration_path_not_found, + .bootloader = argument_bootloader orelse return Error.bootloader_path_not_found, + .cpu = argument_cpu orelse return Error.cpu_not_found, + .user_programs = argument_user_programs orelse return Error.user_programs_not_found, + }; + }; + + const configuration = arguments_result.configuration; + + // TODO: use a format with hex support + const image_config = try lib.ImageConfig.get(wrapped_allocator.zigUnwrap(), arguments_result.image_configuration_path); + var disk_image = try DiskImage.fromZero(image_config.sector_count, image_config.sector_size); + const disk = &disk_image.disk; + const gpt_cache = try GPT.create(disk, null); + var partition_name_buffer: [256]u16 = undefined; + const partition_name = blk: { + const partition_index = try lib.unicode.utf8ToUtf16Le(&partition_name_buffer, image_config.partition.name); + break :blk partition_name_buffer[0..partition_index]; + }; + + switch (image_config.partition.filesystem) { + .fat32 => { + const filesystem = .fat32; + const gpt_partition_cache = try gpt_cache.addPartition(filesystem, partition_name, image_config.partition.first_lba, gpt_cache.header.last_usable_lba, null); + const fat_partition_cache = try format(gpt_cache.disk, .{ + .first_lba = gpt_partition_cache.partition.first_lba, + .last_lba = gpt_partition_cache.partition.last_lba, + }, null); + + var bundle_file_list = host.ArrayList(u8).init(wrapped_allocator.zigUnwrap()); + var uncompressed = host.ArrayList(u8).init(wrapped_allocator.zigUnwrap()); + // Uncompressed bundle size + try bundle_file_list.writer().writeIntLittle(u32, 0); + // Compressed bundle size + try bundle_file_list.writer().writeIntLittle(u32, 0); + // (cpu + programs + font) Bundle file count + try bundle_file_list.writer().writeIntLittle(u32, @as(u32, @intCast(1 + arguments_result.user_programs.len + 1))); + + const cpu_path = arguments_result.cpu; + const cpu_file = try host.fs.openFileAbsolute(cpu_path, .{}); + const cpu_name = host.basename(cpu_path); + try addFileToBundle(cpu_file, &bundle_file_list, cpu_name, &uncompressed); + + for (arguments_result.user_programs) |user_program| { + const file = try host.fs.openFileAbsolute(user_program, .{}); + const name = host.basename(user_program); + try addFileToBundle(file, &bundle_file_list, name, &uncompressed); + } + + const font_file = try host.cwd().openFile("resources/zap-light16.psf", .{}); + try addFileToBundle(font_file, &bundle_file_list, "font", &uncompressed); + + var compressed = host.ArrayList(u8).init(wrapped_allocator.zigUnwrap()); + var compressor = try lib.deflate.compressor(wrapped_allocator.zigUnwrap(), compressed.writer(), .{ .level = .best_compression }); + try compressor.writer().writeAll(uncompressed.items); + try compressor.close(); + + // Wait until here because reallocations can happen in the ArrayList + const bundle_sizes = @as(*align(1) [2]u32, @ptrCast(&bundle_file_list.items[0])); + bundle_sizes[0] = @as(u32, @intCast(uncompressed.items.len)); + bundle_sizes[1] = @as(u32, @intCast(compressed.items.len)); + + // { + // var stream = lib.fixedBufferStream(compressed.items); + // var decompressor = try lib.deflate.decompressor(wrapped_allocator.zigUnwrap(), stream.reader(), null); + // var decompressed = host.ArrayList(u8).init(wrapped_allocator.zigUnwrap()); + // try decompressor.reader().readAllArrayList(&decompressed, lib.maxInt(usize)); + // log.debug("DECOMPRESSED AFTER:", .{}); + // if (decompressor.close()) |err| return err; + // + // for (decompressed.items[0..20], uncompressed.items[0..20]) |byte, before| { + // assert(byte == before); + // log.debug("Byte: 0x{x}", .{byte}); + // } + // } + + try fat_partition_cache.makeNewFile("/files", bundle_file_list.items, wrapped_allocator.unwrap(), null, 0); + try fat_partition_cache.makeNewFile("/bundle", compressed.items, wrapped_allocator.unwrap(), null, 0); + + const loader_file_path = arguments_result.bootloader; + const loader_file = try readFileAbsolute(&wrapped_allocator, loader_file_path); + + switch (configuration.bootloader) { + .limine => { + // log.debug("Installing Limine HDD", .{}); + try limine_installer.install(disk_image.getBuffer(), false, null); + // log.debug("Ended installing Limine HDD", .{}); + const limine_installable_path = "src/bootloader/limine/installables"; + const limine_installable_dir = try host.cwd().openDir(limine_installable_path, .{}); + const loader_fat_path = try lib.concat(wrapped_allocator.zigUnwrap(), u8, &.{ "/", host.basename(loader_file_path) }); + try fat_partition_cache.makeNewFile(loader_fat_path, loader_file, wrapped_allocator.unwrap(), null, 0); + + const limine_cfg = blk: { + var limine_cfg_generator = LimineCFG{ + .buffer = host.ArrayList(u8).init(wrapped_allocator.zigUnwrap()), + }; + try limine_cfg_generator.addField("TIMEOUT", "0"); + try limine_cfg_generator.addEntryName("Rise"); + try limine_cfg_generator.addField("PROTOCOL", "limine"); + try limine_cfg_generator.addField("DEFAULT_ENTRY", "0"); + try limine_cfg_generator.addField("KERNEL_PATH", try lib.concat(wrapped_allocator.zigUnwrap(), u8, &.{ "boot:///", loader_fat_path })); + + try limine_cfg_generator.addField("MODULE_PATH", "boot:////bundle"); + try limine_cfg_generator.addField("MODULE_PATH", "boot:////files"); + break :blk limine_cfg_generator.buffer.items; + }; + + try fat_partition_cache.makeNewFile("/limine.cfg", limine_cfg, wrapped_allocator.unwrap(), null, @as(u64, @intCast(host.time.milliTimestamp()))); + const limine_sys = try limine_installable_dir.readFileAlloc(wrapped_allocator.zigUnwrap(), "limine.sys", max_file_length); + try fat_partition_cache.makeNewFile("/limine.sys", limine_sys, wrapped_allocator.unwrap(), null, @as(u64, @intCast(host.time.milliTimestamp()))); + + switch (configuration.architecture) { + .x86_64 => { + try fat_partition_cache.makeNewDirectory("/EFI", wrapped_allocator.unwrap(), null, @as(u64, @intCast(host.time.milliTimestamp()))); + try fat_partition_cache.makeNewDirectory("/EFI/BOOT", wrapped_allocator.unwrap(), null, @as(u64, @intCast(host.time.milliTimestamp()))); + try fat_partition_cache.makeNewFile("/EFI/BOOT/BOOTX64.EFI", try limine_installable_dir.readFileAlloc(wrapped_allocator.zigUnwrap(), "BOOTX64.EFI", max_file_length), wrapped_allocator.unwrap(), null, @as(u64, @intCast(host.time.milliTimestamp()))); + }, + else => unreachable, + } + }, + .rise => switch (configuration.boot_protocol) { + .bios => { + const partition_first_usable_lba = gpt_partition_cache.gpt.header.first_usable_lba; + assert((fat_partition_cache.partition_range.first_lba - partition_first_usable_lba) * disk.sector_size > lib.alignForward(usize, loader_file.len, disk.sector_size)); + try disk.writeSlice(u8, loader_file, partition_first_usable_lba, true); + + // Build our own assembler + const boot_disk_mbr_lba = 0; + const boot_disk_mbr = try disk.readTypedSectors(BootDisk, boot_disk_mbr_lba, null, .{}); + // const dap_offset = @offsetOf(BootDisk, "dap"); + // _ = dap_offset; + // lib.log.debug("DAP offset: 0x{x}", .{dap_offset}); + const aligned_file_size = lib.alignForward(usize, loader_file.len, disk.sector_size); + const text_section_guess = lib.alignBackward(u32, @as(*align(1) const u32, @ptrCast(&loader_file[0x18])).*, 0x1000); + if (lib.maxInt(u32) - text_section_guess < aligned_file_size) @panic("unexpected size"); + const dap_top = bios.stack_top - bios.stack_size; + if (aligned_file_size > dap_top) host.panic("File size: 0x{x} bytes", .{aligned_file_size}); + // log.debug("DAP top: 0x{x}. Aligned file size: 0x{x}", .{ dap_top, aligned_file_size }); + const dap = MBR.DAP{ + .sector_count = @as(u16, @intCast(@divExact(aligned_file_size, disk.sector_size))), + .offset = dap_file_read, + .segment = 0x0, + .lba = partition_first_usable_lba, + }; + + if (dap_top - dap.offset < aligned_file_size) { + @panic("unable to fit file read from disk"); + } + + // if (dap.offset - bios.loader_start < aligned_file_size) { + // @panic("unable to fit loaded executable in memory"); + // } + + try boot_disk_mbr.fill(wrapped_allocator.zigUnwrap(), dap); + try disk.writeTypedSectors(BootDisk, boot_disk_mbr, boot_disk_mbr_lba, false); + }, + .uefi => { + try fat_partition_cache.makeNewDirectory("/EFI", wrapped_allocator.unwrap(), null, 0); + try fat_partition_cache.makeNewDirectory("/EFI/BOOT", wrapped_allocator.unwrap(), null, 0); + try fat_partition_cache.makeNewFile("/EFI/BOOT/BOOTX64.EFI", loader_file, wrapped_allocator.unwrap(), null, 0); + }, + }, + } + }, + else => @panic("Filesystem not supported"), + } + + const image_file = try host.fs.createFileAbsolute(arguments_result.disk_image_path, .{}); + try image_file.writeAll(disk_image.getBuffer()); +} + +const LimineCFG = struct { + buffer: host.ArrayList(u8), + + pub fn addField(limine_cfg: *LimineCFG, field_name: []const u8, field_value: []const u8) !void { + try limine_cfg.buffer.appendSlice(field_name); + try limine_cfg.buffer.append('='); + try limine_cfg.buffer.appendSlice(field_value); + try limine_cfg.buffer.append('\n'); + } + + pub fn addEntryName(limine_cfg: *LimineCFG, entry_name: []const u8) !void { + try limine_cfg.buffer.append(':'); + try limine_cfg.buffer.appendSlice(entry_name); + try limine_cfg.buffer.append('\n'); + } +}; diff --git a/src/host/disk_image_builder/test.zig b/src/host/disk_image_builder/test.zig new file mode 100644 index 0000000..fb88212 --- /dev/null +++ b/src/host/disk_image_builder/test.zig @@ -0,0 +1,224 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.DISK_IMAGE_BUILDER); +const FAT32 = lib.Filesystem.FAT32; +const GPT = lib.PartitionTable.GPT; +const host = @import("host"); +const limine_installer = @import("limine_installer"); + +const disk_image_builder = @import("../disk_image_builder.zig"); +const ImageDescription = disk_image_builder.ImageDescription; +const DiskImage = disk_image_builder.DiskImage; + +const LoopbackDevice = struct { + name: []const u8, + mount_dir: ?[]const u8 = null, + + fn start(loopback_device: *LoopbackDevice, allocator: lib.ZigAllocator, image_path: []const u8) !void { + try host.spawnProcess(&.{ "./tools/loopback_start.sh", image_path, loopback_device.name }, allocator); + } + + fn end(loopback_device: *LoopbackDevice, allocator: lib.ZigAllocator) !void { + loopback_device.mount_dir = null; + try host.spawnProcess(&.{ "./tools/loopback_end.sh", loopback_device.name }, allocator); + try host.cwd().deleteFile(loopback_device.name); + } + + fn mount(loopback_device: *LoopbackDevice, allocator: lib.ZigAllocator, mount_dir: []const u8) !MountedPartition { + try host.cwd().makePath(mount_dir); + try host.spawnProcess(&.{ "./tools/loopback_mount.sh", loopback_device.name, mount_dir }, allocator); + loopback_device.mount_dir = mount_dir; + + return MountedPartition{ + .loopback_device = loopback_device.*, + }; + } +}; + +const MountedPartition = struct { + loopback_device: LoopbackDevice, + + fn mkdir(partition: MountedPartition, allocator: lib.ZigAllocator, dir: []const u8) !void { + try host.spawnProcess(&.{ "sudo", "mkdir", "-p", try partition.join_with_root(allocator, dir) }, allocator); + } + + fn join_with_root(partition: MountedPartition, allocator: lib.ZigAllocator, path: []const u8) ![]const u8 { + const mount_dir = partition.get_mount_dir(); + const slices_to_join: []const []const u8 = if (path[0] == '/') &.{ mount_dir, path } else &.{ mount_dir, "/", path }; + const joint_path = try lib.concat(allocator, u8, slices_to_join); + return joint_path; + } + + pub fn get_mount_dir(partition: MountedPartition) []const u8 { + const mount_dir = partition.loopback_device.mount_dir orelse @panic("get_mount_dir"); + return mount_dir; + } + + fn copy_file(partition: MountedPartition, allocator: lib.ZigAllocator, file_path: []const u8, file_content: []const u8) !void { + // TODO: make this work for Windows? + const last_slash_index = lib.lastIndexOf(u8, file_path, "/") orelse @panic("fat32: copy file last slash"); + const file_name = host.basename(file_path); + assert(file_name.len > 0); + try host.cwd().writeFile(file_name, file_content); + const dir = file_path[0..if (last_slash_index == 0) 1 else last_slash_index]; + const destination_dir = try partition.join_with_root(allocator, dir); + const mkdir_process_args = &.{ "sudo", "mkdir", "-p", destination_dir }; + try host.spawnProcess(mkdir_process_args, allocator); + const copy_process_args = &.{ "sudo", "cp", "-v", file_name, destination_dir }; + try host.spawnProcess(copy_process_args, allocator); + try host.cwd().deleteFile(file_name); + } + + fn end(partition: *MountedPartition, allocator: lib.ZigAllocator) !void { + const mount_dir = partition.loopback_device.mount_dir orelse @panic("mount partition end"); + host.sync(); + try host.spawnProcess(&.{ "sudo", "umount", mount_dir }, allocator); + host.spawnProcess(&.{ "sudo", "rm", "-rf", mount_dir }, allocator) catch |err| { + switch (err) { + host.ExecutionError.failed => {}, + else => return err, + } + }; + } +}; + +const ShellImage = struct { + path: []const u8, + description: ImageDescription, + + fn createFAT(image: ShellImage, allocator: lib.ZigAllocator) !void { + const megabytes = @divExact(image.description.disk_sector_count * image.description.disk_sector_size, lib.mb); + try host.spawnProcess(&.{ "dd", "if=/dev/zero", "bs=1M", "count=0", try lib.allocPrint(allocator, "seek={d}", .{megabytes}), try lib.allocPrint(allocator, "of={s}", .{image.path}) }, allocator); + + try host.spawnProcess(&.{ "parted", "-s", image.path, "mklabel", "gpt" }, allocator); + try host.spawnProcess(&.{ "parted", "-s", image.path, "mkpart", image.description.partition_name, @tagName(image.description.partition_filesystem), try lib.allocPrint(allocator, "{d}s", .{image.description.partition_start_lba}), "100%" }, allocator); + try host.spawnProcess(&.{ "parted", "-s", image.path, "set", "1", "esp", "on" }, allocator); + } + + fn toDiskImage(image: ShellImage, allocator: lib.ZigAllocator) !DiskImage { + return try DiskImage.fromFile(image.path, @as(u16, @intCast(image.description.disk_sector_size)), allocator); + } + + fn delete(image: ShellImage) !void { + try host.cwd().deleteFile(image.path); + } +}; + +const File = struct { + path: []const u8, + content: []const u8, +}; + +const limine_directories = [_][]const u8{ + "/EFI", "/EFI/BOOT", +}; + +const limine_files = [_]File{ + .{ .path = "/limine.cfg", .content = @embedFile("../../bootloader/limine/installables/limine.cfg") }, + .{ .path = "/limine.sys", .content = @embedFile("../../bootloader/limine/installables/limine.sys") }, +}; + +test "Limine barebones" { + lib.testing.log_level = .debug; + + // + // Using an arena allocator because it doesn't care about memory leaks + var arena_allocator = host.ArenaAllocator.init(host.page_allocator); + defer arena_allocator.deinit(); + + var wrapped_allocator = lib.Allocator.wrap(arena_allocator.allocator()); + + const deploy_limine = true; + + switch (lib.os) { + .linux => { + const image = ImageDescription{ + .partition_start_lba = 0x800, + .disk_sector_count = 131072, + .disk_sector_size = lib.default_sector_size, + .partition_name = "ESP", + .partition_filesystem = .fat32, + }; + + const test_path = "zig-cache/test_original.hdd"; + const test_image = ShellImage{ + .path = test_path, + .description = image, + }; + test_image.delete() catch {}; + + try test_image.createFAT(wrapped_allocator.zigUnwrap()); + if (deploy_limine and disk_image_builder.deploy(test_path, &limine_installer.hdd, limine_installer.hdd.len) != 0) { + @panic("asjdkajsd"); + } + + var loopback_device = LoopbackDevice{ .name = "loopback_device" }; + try loopback_device.start(wrapped_allocator.zigUnwrap(), test_path); + + log.debug("Formatting", .{}); + try host.spawnProcess(&.{ "./tools/format_loopback_fat32.sh", loopback_device.name }, wrapped_allocator.zigUnwrap()); + + const mount_dir = "image_mount"; + + var partition = try loopback_device.mount(wrapped_allocator.zigUnwrap(), mount_dir); + + for (limine_directories) |directory| { + try partition.mkdir(wrapped_allocator.zigUnwrap(), directory); + } + + for (limine_files) |file| { + try partition.copy_file(wrapped_allocator.zigUnwrap(), file.path, file.content); + } + + try partition.end(wrapped_allocator.zigUnwrap()); + try loopback_device.end(wrapped_allocator.zigUnwrap()); + + var original_disk_image = try test_image.toDiskImage(wrapped_allocator.zigUnwrap()); + const original_gpt_cache = try GPT.Partition.Cache.fromPartitionIndex(&original_disk_image.disk, 0, wrapped_allocator.unwrap()); + const original_fat_cache = try FAT32.Cache.fromGPTPartitionCache(wrapped_allocator.unwrap(), original_gpt_cache); + + var disk_image = try DiskImage.fromZero(image.disk_sector_count, image.disk_sector_size); + const gpt_partition_cache = try disk_image.createFAT(image, original_gpt_cache); + + const original_buffer = original_disk_image.getBuffer(); + const my_buffer = disk_image.getBuffer(); + + if (deploy_limine) { + try limine_installer.install(my_buffer, false, null); + } + + const fat_partition_cache = try disk_image_builder.format(gpt_partition_cache.gpt.disk, .{ + .first_lba = gpt_partition_cache.partition.first_lba, + .last_lba = gpt_partition_cache.partition.last_lba, + }, original_fat_cache.mbr); + + for (limine_directories) |directory| { + log.debug("Creating directory: {s}", .{directory}); + try fat_partition_cache.makeNewDirectory(directory, null, original_fat_cache, @as(u64, @intCast(host.time.milliTimestamp()))); + } + + for (limine_files) |file| { + log.debug("Creating file: {s}", .{file.path}); + try fat_partition_cache.makeNewFile(file.path, file.content, wrapped_allocator.unwrap(), original_fat_cache, @as(u64, @intCast(host.time.milliTimestamp()))); + } + + var diff_count: u32 = 0; + for (my_buffer, 0..) |mb, i| { + const ob = original_buffer[i]; + const diff = ob != mb; + if (diff) { + log.debug("[0x{x}] Diff. Expected: 0x{x}. Actual: 0x{x}", .{ i, ob, mb }); + } + diff_count += @intFromBool(diff); + } + + if (diff_count > 0) { + log.err("Diff count: {}", .{diff_count}); + } + try lib.testing.expectEqualSlices(u8, original_buffer, my_buffer); + + try test_image.delete(); + }, + else => {}, + } +} diff --git a/src/host/runner/main.zig b/src/host/runner/main.zig new file mode 100644 index 0000000..1e16588 --- /dev/null +++ b/src/host/runner/main.zig @@ -0,0 +1,350 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.RUNNER); +const host = @import("host"); +const Configuration = lib.Configuration; + +const Error = error{ + wrong_argument_count, + disk_image_path_not_found, + cpu_driver_not_found, + loader_path_not_found, + qemu_options_not_found, + configuration_not_found, + configuration_wrong_argument, + ci_not_found, + debug_user_not_found, + debug_loader_not_found, + init_not_found, + image_configuration_path_not_found, + qemu_error, + not_implemented, + architecture_not_supported, + execution_environment_not_supported, +}; + +pub fn main() anyerror!void { + const max_file_length = lib.maxInt(usize); + var arena_allocator = host.ArenaAllocator.init(host.page_allocator); + defer arena_allocator.deinit(); + var wrapped_allocator = lib.Allocator.wrap(arena_allocator.allocator()); + + const arguments_result: lib.ArgumentParser.Runner.Result = blk: { + const arguments = (try host.allocateArguments(wrapped_allocator.zigUnwrap()))[1..]; + + var argument_parser = lib.ArgumentParser.Runner{}; + var argument_disk_image_path: ?[]const u8 = null; + var argument_cpu_driver_path: ?[]const u8 = null; + var argument_loader_path: ?[]const u8 = null; + var argument_qemu_options: ?lib.QEMUOptions = null; + var argument_configuration: ?Configuration = null; + var argument_image_configuration_path: ?[]const u8 = null; + var argument_ci: ?bool = null; + var argument_debug_user: ?bool = null; + var argument_debug_loader: ?bool = null; + var argument_init_path: ?[]const u8 = null; + var argument_index: usize = 0; + + while (argument_parser.next()) |argument_type| switch (argument_type) { + .disk_image_path => { + argument_disk_image_path = arguments[argument_index]; + argument_index += 1; + }, + .cpu_driver => { + argument_cpu_driver_path = arguments[argument_index]; + argument_index += 1; + }, + .loader_path => { + argument_loader_path = arguments[argument_index]; + argument_index += 1; + }, + .qemu_options => { + const boolean_argument_strings = [2][]const u8{ arguments[argument_index], arguments[argument_index + 1] }; + argument_index += 2; + + argument_qemu_options = undefined; + inline for (lib.fields(lib.QEMUOptions), 0..) |field, field_index| { + @field(argument_qemu_options.?, field.name) = if (lib.equal(u8, boolean_argument_strings[field_index], "true")) true else if (lib.equal(u8, boolean_argument_strings[field_index], "false")) false else return Error.qemu_options_not_found; + } + }, + .configuration => { + argument_configuration = undefined; + const configuration = &argument_configuration.?; + inline for (lib.fields(Configuration)) |field| { + @field(configuration, field.name) = lib.stringToEnum(field.type, arguments[argument_index]) orelse return Error.configuration_wrong_argument; + argument_index += 1; + } + }, + .image_configuration_path => { + argument_image_configuration_path = arguments[argument_index]; + argument_index += 1; + }, + .ci => { + argument_ci = if (lib.equal(u8, arguments[argument_index], "true")) true else if (lib.equal(u8, arguments[argument_index], "false")) false else return Error.ci_not_found; + argument_index += 1; + }, + .debug_user => { + argument_debug_user = if (lib.equal(u8, arguments[argument_index], "true")) true else if (lib.equal(u8, arguments[argument_index], "false")) false else return Error.debug_user_not_found; + argument_index += 1; + }, + .debug_loader => { + argument_debug_loader = if (lib.equal(u8, arguments[argument_index], "true")) true else if (lib.equal(u8, arguments[argument_index], "false")) false else return Error.debug_loader_not_found; + argument_index += 1; + }, + .init => { + argument_init_path = arguments[argument_index]; + argument_index += 1; + }, + }; + + if (argument_index != arguments.len) return Error.wrong_argument_count; + + break :blk .{ + .disk_image_path = argument_disk_image_path orelse return Error.disk_image_path_not_found, + .cpu_driver = argument_cpu_driver_path orelse return Error.cpu_driver_not_found, + .loader_path = argument_loader_path orelse return Error.loader_path_not_found, + .qemu_options = argument_qemu_options orelse return Error.qemu_options_not_found, + .configuration = argument_configuration orelse return Error.configuration_not_found, + .image_configuration_path = argument_image_configuration_path orelse return Error.image_configuration_path_not_found, + .ci = argument_ci orelse return Error.ci_not_found, + .debug_user = argument_debug_user orelse return Error.debug_user_not_found, + .debug_loader = argument_debug_loader orelse return Error.debug_loader_not_found, + .init = argument_init_path orelse return Error.init_not_found, + }; + }; + + switch (arguments_result.configuration.execution_environment) { + .qemu => { + const qemu_options = arguments_result.qemu_options; + + const config_file = try host.cwd().readFileAlloc(wrapped_allocator.zigUnwrap(), "config/qemu.json", max_file_length); + const parsed_arguments = try lib.json.parseFromSlice(Arguments, wrapped_allocator.zigUnwrap(), config_file, .{}); + const arguments = parsed_arguments.value; + + var argument_list = host.ArrayList([]const u8).init(wrapped_allocator.zigUnwrap()); + + try argument_list.append(try lib.concat(wrapped_allocator.zigUnwrap(), u8, &.{ "qemu-system-", @tagName(arguments_result.configuration.architecture) })); + + if (qemu_options.is_test and !qemu_options.is_debug) { + try argument_list.appendSlice(&.{ "-device", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "isa-debug-exit,iobase=0x{x:0>2},iosize=0x{x:0>2}", .{ lib.QEMU.isa_debug_exit.io_base, lib.QEMU.isa_debug_exit.io_size }) }); + } + + switch (arguments_result.configuration.boot_protocol) { + .uefi => try argument_list.appendSlice(&.{ "-bios", "tools/OVMF_CODE-pure-efi.fd" }), + else => {}, + } + + const image_config = try lib.ImageConfig.get(wrapped_allocator.zigUnwrap(), arguments_result.image_configuration_path); + _ = image_config; + const disk_image_path = arguments_result.disk_image_path; + try argument_list.appendSlice(&.{ "-drive", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "file={s},index=0,media=disk,format=raw", .{disk_image_path}) }); + + try argument_list.append("-no-reboot"); + + if (!qemu_options.is_test) { + try argument_list.append("-no-shutdown"); + } + + if (arguments_result.ci) { + try argument_list.appendSlice(&.{ "-display", "none" }); + } + + //if (arguments.vga) |vga| { + //try argument_list.append("-vga"); + //try argument_list.append(@tagName(vga)); + //} + + if (arguments.smp) |smp| { + try argument_list.append("-smp"); + const smp_string = try lib.allocPrint(wrapped_allocator.zigUnwrap(), "{}", .{smp}); + try argument_list.append(smp_string); + } + + if (arguments.debugcon) |debugcon| { + try argument_list.append("-debugcon"); + try argument_list.append(@tagName(debugcon)); + } + + if (arguments.memory) |memory| { + try argument_list.append("-m"); + if (arguments_result.ci) { + try argument_list.append("1G"); + } else { + const memory_argument = try lib.allocPrint(wrapped_allocator.zigUnwrap(), "{}{c}", .{ memory.amount, @as(u8, switch (memory.unit) { + .kilobyte => 'K', + .megabyte => 'M', + .gigabyte => 'G', + else => @panic("Unit too big"), + }) }); + try argument_list.append(memory_argument); + } + } + + if (lib.canVirtualizeWithQEMU(arguments_result.configuration.architecture, arguments_result.ci) and (arguments_result.configuration.execution_type == .accelerated or (arguments.virtualize orelse false))) { + try argument_list.appendSlice(&.{ + "-accel", + switch (lib.os) { + .windows => "whpx", + .linux => "kvm", + .macos => "hvf", + else => @compileError("OS not supported"), + }, + "-cpu", + "host", + }); + } else { + switch (arguments_result.configuration.architecture) { + .x86_64 => try argument_list.appendSlice(&.{ "-cpu", "max" }), + else => return Error.architecture_not_supported, + } + + if (arguments.trace) |tracees| { + for (tracees) |tracee| { + const tracee_slice = try lib.allocPrint(wrapped_allocator.zigUnwrap(), "-{s}*", .{tracee}); + try argument_list.append("-trace"); + try argument_list.append(tracee_slice); + } + } + + if (!arguments_result.ci) { + if (arguments.log) |log_configuration| { + var log_what = host.ArrayList(u8).init(wrapped_allocator.zigUnwrap()); + + if (log_configuration.guest_errors) try log_what.appendSlice("guest_errors,"); + if (log_configuration.interrupts) try log_what.appendSlice("int,"); + if (!arguments_result.ci and log_configuration.assembly) try log_what.appendSlice("in_asm,"); + + if (log_what.items.len > 0) { + // Delete the last comma + _ = log_what.pop(); + + try argument_list.append("-d"); + try argument_list.append(log_what.items); + + if (log_configuration.interrupts) { + try argument_list.appendSlice(&.{ "-machine", "smm=off" }); + } + } + + if (log_configuration.file) |log_file| { + try argument_list.append("-D"); + try argument_list.append(log_file); + } + } + } + } + + if (qemu_options.is_debug) { + try argument_list.append("-s"); + if (!(arguments_result.configuration.execution_type == .accelerated or (arguments.virtualize orelse false))) { + try argument_list.append("-S"); + } + + // GF2, when not found in the PATH, can give problems + const use_gf = switch (lib.os) { + .macos => false, + .linux => false, + else => false, + }; + + var command_line_gdb = host.ArrayList([]const u8).init(wrapped_allocator.zigUnwrap()); + if (use_gf) { + try command_line_gdb.append("gf2"); + } else { + try command_line_gdb.append("kitty"); + try command_line_gdb.append(switch (lib.os) { + .linux => "gdb", + .macos => "x86_64-elf-gdb", + else => "gdb", + }); + } + + try command_line_gdb.appendSlice(&.{ "-ex", switch (arguments_result.configuration.architecture) { + .x86_64 => "set disassembly-flavor intel\n", + else => return Error.architecture_not_supported, + } }); + + try command_line_gdb.appendSlice(&.{ "-ex", "target remote localhost:1234" }); + if (arguments_result.debug_user) { + assert(!arguments_result.debug_loader); + try command_line_gdb.appendSlice(&.{ "-ex", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "symbol-file {s}", .{arguments_result.init}) }); + } else if (arguments_result.debug_loader) { + assert(!arguments_result.debug_user); + try command_line_gdb.appendSlice(&.{ "-ex", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "symbol-file {s}", .{arguments_result.loader_path}) }); + } else { + try command_line_gdb.appendSlice(&.{ "-ex", try lib.allocPrint(wrapped_allocator.zigUnwrap(), "symbol-file {s}", .{arguments_result.cpu_driver}) }); + } + + const gdb_script_file = try host.cwd().openFile("config/gdb_script", .{}); + var gdb_script_reader = gdb_script_file.reader(); + while (try gdb_script_reader.readUntilDelimiterOrEofAlloc(wrapped_allocator.zigUnwrap(), '\n', max_file_length)) |gdb_script_line| { + try command_line_gdb.appendSlice(&.{ "-ex", gdb_script_line }); + } + + const debugger_process_arguments = switch (lib.os) { + .linux, .macos => command_line_gdb.items, + else => return Error.not_implemented, + }; + + var debugger_process = host.ChildProcess.init(debugger_process_arguments, wrapped_allocator.zigUnwrap()); + try debugger_process.spawn(); + } + + var process = host.ChildProcess.init(argument_list.items, wrapped_allocator.zigUnwrap()); + const result = try process.spawnAndWait(); + + if (result == .Exited) { + const exit_code = result.Exited; + if (exit_code & 1 != 0) { + const mask = lib.maxInt(@TypeOf(exit_code)) - 1; + const masked_exit_code = exit_code & mask; + + if (masked_exit_code != 0) { + const qemu_exit_code = @as(lib.QEMU.ExitCode, @enumFromInt(masked_exit_code >> 1)); + + switch (qemu_exit_code) { + .success => return log.info("Success!", .{}), + .failure => log.err("QEMU exited with failure code 0x{x}", .{exit_code}), + _ => log.err("Totally unexpected value", .{}), + } + } else log.err("QEMU exited with unexpected code: {}. Masked: {}", .{ exit_code, masked_exit_code }); + } else log.err("QEMU exited with unexpected code: {}", .{exit_code}); + } else { + log.err("QEMU was {s}", .{@tagName(result)}); + } + + return Error.qemu_error; + }, + } +} + +const Arguments = struct { + const VGA = enum { + std, + cirrus, + vmware, + qxl, + xenfb, + tcx, + cg3, + virtio, + none, + }; + memory: ?struct { + amount: u64, + unit: lib.SizeUnit, + }, + virtualize: ?bool, + vga: ?VGA, + smp: ?usize, + debugcon: ?enum { + stdio, + }, + log: ?struct { + file: ?[]const u8, + guest_errors: bool, + assembly: bool, + interrupts: bool, + }, + trace: ?[]const []const u8, +}; diff --git a/src/host/test.zig b/src/host/test.zig new file mode 100644 index 0000000..e39b05a --- /dev/null +++ b/src/host/test.zig @@ -0,0 +1,6 @@ +const lib = @import("lib"); +const host = @import("host"); +test "Host tests" { + _ = lib; + _ = host; +} diff --git a/src/lib.zig b/src/lib.zig new file mode 100644 index 0000000..7dbcc0a --- /dev/null +++ b/src/lib.zig @@ -0,0 +1,1080 @@ +const common = @import("common.zig"); +pub usingnamespace common; + +pub const arch = @import("lib/arch.zig"); +/// This is done so the allocator can respect allocating from different address spaces +pub const config = @import("lib/config.zig"); +pub const CRC32 = @import("lib/crc32.zig"); +const disk_file = @import("lib/disk.zig"); +pub const Disk = disk_file.Disk; +pub const Filesystem = @import("lib/filesystem.zig"); +pub const NLS = @import("lib/nls.zig"); +pub const PartitionTable = @import("lib/partition_table.zig"); +pub const PSF1 = @import("lib/psf1.zig"); +pub const Spinlock = arch.Spinlock; + +const extern_enum_array = @import("lib/extern_enum_array.zig"); +pub const EnumArray = extern_enum_array.EnumArray; + +pub fn memcpy(noalias destination: []u8, noalias source: []const u8) void { + @setRuntimeSafety(false); + // Using this as the Zig implementation is really slow (at least in x86 with soft_float enabled + // if (common.cpu.arch == .x86 or common.cpu.arch == .x86_64 and common.Target.x86.featureSetHas(common.cpu.features, .soft_float)) { + const bytes_left = switch (common.cpu.arch) { + .x86 => asm volatile ( + \\rep movsb + : [ret] "={ecx}" (-> usize), + : [dest] "{edi}" (destination.ptr), + [src] "{esi}" (source.ptr), + [len] "{ecx}" (source.len), + ), + .x86_64 => asm volatile ( + \\rep movsb + : [ret] "={rcx}" (-> usize), + : [dest] "{rdi}" (destination.ptr), + [src] "{rsi}" (source.ptr), + [len] "{rcx}" (source.len), + ), + else => @compileError("Unreachable"), + }; + + common.assert(bytes_left == 0); + // } else { + // @memcpy(destination, source); + // } +} + +// pub fn memset(comptime T: type, slice: []T, elem: T) void { +// @setRuntimeSafety(false); +// +// const bytes_left = switch (T) { +// u8 => switch (common.cpu.arch) { +// .x86 => asm volatile ( +// \\rep stosb +// : [ret] "={ecx}" (-> usize), +// : [slice] "{edi}" (slice.ptr), +// [len] "{ecx}" (slice.len), +// [element] "{al}" (elem), +// ), +// .x86_64 => asm volatile ( +// \\rep movsb +// : [ret] "={rcx}" (-> usize), +// : [slice] "{rdi}" (slice.ptr), +// [len] "{rcx}" (slice.len), +// [element] "{al}" (elem), +// ), +// else => @compileError("Unsupported OS"), +// }, +// else => @compileError("Type " ++ @typeName(T) ++ " not supported"), +// }; +// +// common.assert(bytes_left == 0); +// } + +pub fn EnumStruct(comptime Enum: type, comptime Value: type) type { + const EnumFields = common.enumFields(Enum); + const MyEnumStruct = @Type(.{ + .Struct = .{ + .layout = .Extern, + .fields = &blk: { + var arr = [1]common.Type.StructField{undefined} ** EnumFields.len; + inline for (EnumFields) |EnumValue| { + arr[EnumValue.value] = .{ + .name = EnumValue.name, + .type = Value, + .default_value = null, + .is_comptime = false, + .alignment = @alignOf(Value), + }; + } + break :blk arr; + }, + .decls = &.{}, + .is_tuple = false, + }, + }); + const MyEnumArray = EnumArray(Enum, Value); + const Union = extern union { + fields: Struct, + array: Array, + + pub const Struct = MyEnumStruct; + pub const Array = MyEnumArray; + }; + + common.assert(@sizeOf(Union.Struct) == @sizeOf(Union.Array)); + common.assert(@sizeOf(Union.Array) == @sizeOf(Union)); + + return Union; +} + +pub const DirectoryTokenizer = struct { + string: []const u8, + index: usize = 0, + given_count: usize = 0, + total_count: usize, + + pub fn init(string: []const u8) DirectoryTokenizer { + common.assert(string.len > 0); + var count: usize = 0; + + if (string[0] == '/') { + for (string) |ch| { + count += @intFromBool(ch == '/'); + } + } else unreachable; + + return .{ .string = string, .total_count = count + 1 }; + } + + pub fn next(tokenizer: *DirectoryTokenizer) ?[]const u8 { + if (tokenizer.index == 0) { + const is_root_dir = tokenizer.string[0] == '/'; + if (is_root_dir) { + tokenizer.index += 1; + tokenizer.given_count += 1; + return "/"; + } else unreachable; + } else { + const original_index = tokenizer.index; + if (original_index < tokenizer.string.len) { + for (tokenizer.string[original_index..]) |char| { + if (char == '/') { + const result = tokenizer.string[original_index..tokenizer.index]; + tokenizer.given_count += 1; + tokenizer.index += 1; + return result; + } + + tokenizer.index += 1; + } + + tokenizer.given_count += 1; + + return tokenizer.string[original_index..]; + } else { + common.assert(original_index == tokenizer.string.len); + common.assert(tokenizer.given_count == tokenizer.total_count); + return null; + } + } + } + + pub fn is_last(tokenizer: DirectoryTokenizer) bool { + return tokenizer.given_count == tokenizer.total_count; + } + + test "directory tokenizer" { + common.log.err("ajskdjsa", .{}); + if (common.os != .freestanding) { + const TestCase = struct { + path: []const u8, + expected_result: []const []const u8, + }; + + const test_cases = [_]TestCase{ + .{ .path = "/EFI", .expected_result = &.{ "/", "EFI" } }, + .{ .path = "/abc/def/a", .expected_result = &.{ "/", "abc", "def", "a" } }, + }; + + inline for (test_cases) |case| { + var dir_tokenizer = DirectoryTokenizer.init(case.path); + var results: [case.expected_result.len][]const u8 = undefined; + var result_count: usize = 0; + + while (dir_tokenizer.next()) |dir| { + try common.testing.expect(result_count < results.len); + try common.testing.expectEqualStrings(case.expected_result[result_count], dir); + results[result_count] = dir; + result_count += 1; + } + + try common.testing.expectEqual(case.expected_result.len, result_count); + } + } + } +}; + +pub inline fn ptrAdd(comptime T: type, ptr: *T, element_offset: usize) *T { + return @as(*T, @ptrFromInt(@intFromPtr(ptr) + @sizeOf(T) * element_offset)); +} + +pub inline fn maybePtrAdd(comptime T: type, ptr: ?*T, element_offset: usize) ?*T { + return @as(*T, @ptrFromInt(@intFromPtr(ptr) + @sizeOf(T) * element_offset)); +} + +pub inline fn ptrSub(comptime T: type, ptr: *T, element_offset: usize) *T { + return @as(*T, @ptrFromInt(@intFromPtr(ptr) - @sizeOf(T) * element_offset)); +} + +pub inline fn maybePtrSub(comptime T: type, ptr: ?*T, element_offset: usize) ?*T { + return @as(*T, @ptrFromInt(@intFromPtr(ptr) - @sizeOf(T) * element_offset)); +} + +test { + common.log.err("test not taken into the test suite"); + _ = DirectoryTokenizer; + _ = Filesystem; + _ = PartitionTable; +} + +pub const Allocator = extern struct { + callbacks: Callbacks align(8), + + pub const Allocate = struct { + pub const Result = extern struct { + address: u64, + size: u64, + + pub fn toBytes(result: Result) []u8 { + return @as([*]u8, @ptrFromInt(result.address))[0..result.size]; + } + }; + pub const Fn = fn (allocator: *Allocator, size: u64, alignment: u64) Error!Result; + pub const Error = error{ + OutOfMemory, + }; + }; + + /// Necessary to do this hack + const Callbacks = switch (common.cpu.arch) { + .x86 => extern struct { + allocate: *const Allocate.Fn, + allocate_padding: u32 = 0, + }, + .x86_64, .aarch64, .riscv64 => extern struct { + allocate: *const Allocate.Fn, + }, + else => @compileError("Architecture not supported"), + }; + + pub inline fn allocateBytes(allocator: *Allocator, size: u64, alignment: u64) Allocate.Error!Allocate.Result { + return try allocator.callbacks.allocate(allocator, size, alignment); + } + + pub inline fn allocate(allocator: *Allocator, comptime T: type, len: usize) Allocate.Error![]T { + const size = @sizeOf(T) * len; + const alignment = @alignOf(T); + const allocation_result = try allocator.callbacks.allocate(allocator, size, alignment); + const result = @as([*]T, @ptrFromInt(safeArchitectureCast(allocation_result.address)))[0..len]; + return result; + } + + pub inline fn create(allocator: *Allocator, comptime T: type) Allocate.Error!*T { + const result = try allocator.allocate(T, 1); + return &result[0]; + } + + pub fn wrap(zig_allocator: common.ZigAllocator) Wrapped { + return .{ + .allocator = .{ + .callbacks = .{ + .allocate = Wrapped.wrappedCallbackAllocate, + }, + }, + .zig = .{ + .ptr = zig_allocator.ptr, + .vtable = zig_allocator.vtable, + }, + }; + } + + pub fn zigUnwrap(allocator: *Allocator) common.ZigAllocator { + return .{ + .ptr = allocator, + .vtable = &zig_vtable, + }; + } + + pub const zig_vtable = .{ + .alloc = zigAllocate, + .resize = zigResize, + .free = zigFree, + }; + + pub fn zigAllocate(context: *anyopaque, size: usize, ptr_align: u8, return_address: usize) ?[*]u8 { + _ = context; + _ = size; + _ = ptr_align; + _ = return_address; + return null; + } + + pub fn zigResize(context: *anyopaque, buffer: []u8, buffer_alignment: u8, new_length: usize, return_address: usize) bool { + _ = context; + _ = buffer; + _ = buffer_alignment; + _ = new_length; + _ = return_address; + return false; + } + + pub fn zigFree(context: *anyopaque, buffer: []u8, buffer_alignment: u8, return_address: usize) void { + _ = context; + _ = buffer; + _ = buffer_alignment; + _ = return_address; + } + + pub const Wrapped = extern struct { + allocator: Allocator, + zig: extern struct { + ptr: *anyopaque, + vtable: *const common.ZigAllocator.VTable, + }, + + pub fn unwrap(wrapped_allocator: *Wrapped) *Allocator { + return &wrapped_allocator.allocator; + } + + pub fn zigUnwrap(wrapped_allocator: *Wrapped) common.ZigAllocator { + return .{ + .ptr = wrapped_allocator.zig.ptr, + .vtable = wrapped_allocator.zig.vtable, + }; + } + + pub fn wrappedCallbackAllocate(allocator: *Allocator, size: u64, alignment: u64) Allocator.Allocate.Error!Allocator.Allocate.Result { + const wrapped_allocator = @fieldParentPtr(Wrapped, "allocator", allocator); + const zig_allocator = wrapped_allocator.zigUnwrap(); + if (alignment > common.maxInt(u8)) { + @panic("alignment supported by Zig is less than asked"); + } + const zig_result = zig_allocator.vtable.alloc(zig_allocator.ptr, size, @as(u8, @intCast(alignment)), @returnAddress()); + return .{ + .address = @intFromPtr(zig_result), + .size = size, + }; + } + }; +}; + +pub fn ELF(comptime bits: comptime_int) type { + const is_64 = switch (bits) { + 32 => @compileError("ELF file is not supported"), + 64 => true, + else => @compileError("ELF is not supported for those bits"), + }; + + return struct { + const Address = if (is_64) u64 else u32; + + pub const Parser = struct { + file_header: *const FileHeader, + + pub fn init(file: []const u8) Error!Parser { + if (file.len < @sizeOf(FileHeader)) { + return Error.not_long_enough; + } + + const file_header: *const FileHeader = @ptrCast(@alignCast(&file[0])); + try file_header.validate(); + + return Parser{ + .file_header = file_header, + }; + } + + pub fn getEntryPoint(parser: *const Parser) Address { + return parser.file_header.entry_point; + } + + pub fn getProgramHeaders(parser: *const Parser) []const ProgramHeader { + const program_headers = @as([*]const ProgramHeader, @ptrFromInt(@intFromPtr(parser.file_header) + @as(usize, @intCast(parser.file_header.program_header_offset))))[0..parser.file_header.program_header_entry_count]; + return program_headers; + } + + pub fn getSectionHeaders(parser: *const Parser) []const SectionHeader { + const section_headers = @as([*]const SectionHeader, @ptrFromInt(@intFromPtr(parser.file_header) + @as(usize, @intCast(parser.file_header.section_header_offset))))[0..parser.file_header.section_header_entry_count]; + return section_headers; + } + + pub const Error = error{ + not_long_enough, + invalid_magic, + invalid_signature, + invalid_bits, + weird_program_header_size, + weird_section_header_size, + }; + }; + + pub const FileHeader = switch (is_64) { + true => extern struct { + // e_ident + magic: u8 = magic, + elf_id: [3]u8 = elf_signature.*, + bit_count: Bits = .b64, + endianness: Endianness = .little, + header_version: u8 = 1, + os_abi: ABI = .SystemV, + abi_version: u8 = 0, + padding: [7]u8 = [_]u8{0} ** 7, + object_type: ObjectFileType = .executable, // e_type + machine: Machine = .AMD64, + version: u32 = 1, + entry_point: u64, + program_header_offset: u64 = 0x40, + section_header_offset: u64, + flags: u32 = 0, + header_size: u16 = 0x40, + program_header_size: u16 = @sizeOf(ProgramHeader), + program_header_entry_count: u16 = 1, + section_header_size: u16 = @sizeOf(SectionHeader), + section_header_entry_count: u16, + name_section_header_index: u16, + + const magic = 0x7f; + const elf_signature = "ELF"; + const Bits = enum(u8) { + b32 = 1, + b64 = 2, + }; + + const Endianness = enum(u8) { + little = 1, + big = 2, + }; + + const ABI = enum(u8) { + SystemV = 0, + }; + + pub const ObjectFileType = enum(u16) { + none = 0, + relocatable = 1, + executable = 2, + dynamic = 3, + core = 4, + lo_os = 0xfe00, + hi_os = 0xfeff, + lo_proc = 0xff00, + hi_proc = 0xffff, + }; + + pub const Machine = enum(u16) { + AMD64 = 0x3e, + }; + + pub fn validate(file_header: *const FileHeader) Parser.Error!void { + if (file_header.magic != FileHeader.magic) { + return Parser.Error.invalid_magic; + } + + if (!common.equal(u8, &file_header.elf_id, FileHeader.elf_signature)) { + return Parser.Error.invalid_signature; + } + + switch (file_header.bit_count) { + .b32 => if (bits != 32) return Parser.Error.invalid_bits, + .b64 => if (bits != 64) return Parser.Error.invalid_bits, + } + + if (file_header.program_header_size != @sizeOf(ProgramHeader)) { + return Parser.Error.weird_program_header_size; + } + + if (file_header.section_header_size != @sizeOf(SectionHeader)) { + return Parser.Error.weird_section_header_size; + } + } + }, + false => @compileError("Not yet supported"), + }; + + pub const ProgramHeader = switch (is_64) { + true => extern struct { + type: Type = .load, + flags: Flags, //= @enumToInt(Flags.readable) | @enumToInt(Flags.executable), + offset: u64, + virtual_address: u64, + physical_address: u64, + size_in_file: u64, + size_in_memory: u64, + alignment: u64 = 0, + + const Type = enum(u32) { + null = 0, + load = 1, + dynamic = 2, + interpreter = 3, + note = 4, + shlib = 5, // reserved + program_header = 6, + tls = 7, + lo_os = 0x60000000, + gnu_eh_frame = 0x6474e550, + gnu_stack = 0x6474e551, + hi_os = 0x6fffffff, + lo_proc = 0x70000000, + hi_proc = 0x7fffffff, + _, + }; + + const Flags = packed struct { + executable: bool, + writable: bool, + readable: bool, + reserved: u29, + + comptime { + common.assert(@sizeOf(Flags) == @sizeOf(u32)); + } + }; + }, + false => @compileError("Not yet supported"), + }; + pub const SectionHeader = switch (is_64) { + true => extern struct { + name_offset: u32, + type: u32, + flags: u64, + address: u64, + offset: u64, + size: u64, + // section index + link: u32, + info: u32, + alignment: u64, + entry_size: u64, + + // type + const ID = enum(u32) { + null = 0, + program_data = 1, + symbol_table = 2, + string_table = 3, + relocation_entries_addends = 4, + symbol_hash_table = 5, + dynamic_linking_info = 6, + notes = 7, + program_space_no_data = 8, + relocation_entries = 9, + reserved = 10, + dynamic_linker_symbol_table = 11, + array_of_constructors = 14, + array_of_destructors = 15, + array_of_pre_constructors = 16, + section_group = 17, + extended_section_indices = 18, + number_of_defined_types = 19, + start_os_specific = 0x60000000, + }; + + const Flag = enum(u64) { + writable = 0x01, + alloc = 0x02, + executable = 0x04, + mergeable = 0x10, + contains_null_terminated_strings = 0x20, + info_link = 0x40, + link_order = 0x80, + os_non_conforming = 0x100, + section_group = 0x200, + tls = 0x400, + mask_os = 0x0ff00000, + mask_processor = 0xf0000000, + ordered = 0x4000000, + exclude = 0x8000000, + }; + }, + false => @compileError("Not yet supported"), + }; + }; +} + +pub inline fn safeArchitectureCast(value: anytype) usize { + return switch (@sizeOf(@TypeOf(value)) > @sizeOf(usize)) { + true => if (value <= common.maxInt(usize)) @as(usize, @truncate(value)) else { + common.log.err("PANIC: virtual address is longer than usize: 0x{x}", .{value}); + @panic("safeArchitectureCast"); + }, + false => value, + }; +} + +pub const DereferenceError = error{ + address_bigger_than_usize, +}; + +pub inline fn tryDereferenceAddress(value: anytype) DereferenceError!usize { + common.assert(@sizeOf(@TypeOf(value)) > @sizeOf(usize)); + return if (value <= common.maxInt(usize)) @as(usize, @truncate(value)) else return DereferenceError.address_bigger_than_usize; +} + +pub fn enumAddNames(comptime enum_fields: []const common.Type.EnumField, comptime names: []const []const u8) []const common.Type.EnumField { + comptime var result = enum_fields; + const previous_last_value = if (enum_fields.len > 0) enum_fields[enum_fields.len - 1].value else 0; + + inline for (names, 0..) |name, value_start| { + const value = value_start + previous_last_value; + result = result ++ .{.{ + .name = name, + .value = value, + }}; + } + + return result; +} + +pub fn ErrorSet(comptime error_names: []const []const u8, comptime predefined_fields: []const common.Type.EnumField) type { + comptime var error_fields: []const common.Type.Error = &.{}; + comptime var enum_items: []const common.Type.EnumField = predefined_fields; + comptime var enum_value = enum_items[enum_items.len - 1].value + 1; + + inline for (error_names) |error_name| { + enum_items = enum_items ++ [1]common.Type.EnumField{ + .{ + .name = error_name, + .value = enum_value, + }, + }; + + enum_value += 1; + } + + inline for (enum_items) |item| { + error_fields = error_fields ++ [1]common.Type.Error{ + .{ + .name = item.name, + }, + }; + } + + const EnumType = @Type(common.Type{ + .Enum = .{ + .tag_type = u16, + .fields = enum_items, + .decls = &.{}, + .is_exhaustive = true, + }, + }); + + const ErrorType = @Type(common.Type{ + .ErrorSet = error_fields, + }); + + return struct { + pub const Error = ErrorType; + pub const Enum = EnumType; + }; +} + +pub fn getDebugInformation(allocator: common.ZigAllocator, elf_file: []align(common.default_sector_size) const u8) !common.ModuleDebugInfo { + const elf = common.elf; + var module_debug_info: common.ModuleDebugInfo = undefined; + _ = module_debug_info; + const hdr = @as(*const elf.Ehdr, @ptrCast(&elf_file[0])); + if (!common.equal(u8, hdr.e_ident[0..4], elf.MAGIC)) return error.InvalidElfMagic; + if (hdr.e_ident[elf.EI_VERSION] != 1) return error.InvalidElfVersion; + + const endian = .Little; + + const shoff = hdr.e_shoff; + const str_section_off = shoff + @as(u64, hdr.e_shentsize) * @as(u64, hdr.e_shstrndx); + const str_shdr = @as( + *const elf.Shdr, + @ptrCast(@alignCast(&elf_file[common.cast(usize, str_section_off) orelse return error.Overflow])), + ); + const header_strings = elf_file[str_shdr.sh_offset .. str_shdr.sh_offset + str_shdr.sh_size]; + const shdrs = @as( + [*]const elf.Shdr, + @ptrCast(@alignCast(&elf_file[shoff])), + )[0..hdr.e_shnum]; + + var opt_debug_info: ?[]const u8 = null; + var opt_debug_abbrev: ?[]const u8 = null; + var opt_debug_str: ?[]const u8 = null; + var opt_debug_str_offsets: ?[]const u8 = null; + var opt_debug_line: ?[]const u8 = null; + var opt_debug_line_str: ?[]const u8 = null; + var opt_debug_ranges: ?[]const u8 = null; + var opt_debug_loclists: ?[]const u8 = null; + var opt_debug_rnglists: ?[]const u8 = null; + var opt_debug_addr: ?[]const u8 = null; + var opt_debug_names: ?[]const u8 = null; + var opt_debug_frame: ?[]const u8 = null; + + for (shdrs) |*shdr| { + if (shdr.sh_type == elf.SHT_NULL) continue; + + const name = common.sliceTo(header_strings[shdr.sh_name..], 0); + if (common.equal(u8, name, ".debug_info")) { + opt_debug_info = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_abbrev")) { + opt_debug_abbrev = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_str")) { + opt_debug_str = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_str_offsets")) { + opt_debug_str_offsets = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_line")) { + opt_debug_line = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_line_str")) { + opt_debug_line_str = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_ranges")) { + opt_debug_ranges = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_loclists")) { + opt_debug_loclists = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_rnglists")) { + opt_debug_rnglists = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_addr")) { + opt_debug_addr = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_names")) { + opt_debug_names = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } else if (common.equal(u8, name, ".debug_frame")) { + opt_debug_frame = try chopSlice(elf_file, shdr.sh_offset, shdr.sh_size); + } + } + + var di = common.dwarf.DwarfInfo{ + .endian = endian, + .debug_info = opt_debug_info orelse return error.MissingDebugInfo, + .debug_abbrev = opt_debug_abbrev orelse return error.MissingDebugInfo, + .debug_str = opt_debug_str orelse return error.MissingDebugInfo, + .debug_str_offsets = opt_debug_str_offsets, + .debug_line = opt_debug_line orelse return error.MissingDebugInfo, + .debug_line_str = opt_debug_line_str, + .debug_ranges = opt_debug_ranges, + .debug_loclists = opt_debug_loclists, + .debug_rnglists = opt_debug_rnglists, + .debug_addr = opt_debug_addr, + .debug_names = opt_debug_names, + .debug_frame = opt_debug_frame, + }; + + try common.dwarf.openDwarfDebugInfo(&di, allocator); + return di; +} + +fn chopSlice(ptr: []const u8, offset: u64, size: u64) error{Overflow}![]const u8 { + const start = common.cast(usize, offset) orelse return error.Overflow; + const end = start + (common.cast(usize, size) orelse return error.Overflow); + return ptr[start..end]; +} + +pub fn RegionInterface(comptime Region: type) type { + const type_info = @typeInfo(Region); + common.assert(type_info == .Struct); + common.assert(type_info.Struct.layout == .Extern); + common.assert(type_info.Struct.fields.len == 2); + const fields = type_info.Struct.fields; + common.assert(common.equal(u8, fields[0].name, "address")); + common.assert(common.equal(u8, fields[1].name, "size")); + const Addr = fields[0].type; + const AddrT = getAddrT(Addr); + + return struct { + pub inline fn new(info: struct { + address: Addr, + size: AddrT, + }) Region { + return Region{ + .address = info.address, + .size = info.size, + }; + } + pub inline fn fromRaw(info: struct { + raw_address: AddrT, + size: AddrT, + }) Region { + const address = Addr.new(info.raw_address); + return new(.{ + .address = address, + .size = info.size, + }); + } + + pub inline fn fromAllocation(info: struct { + allocation: Allocator.Allocate.Result, + }) Region { + return new(.{ + .address = addressToAddrT(info.allocation.address), + .size = info.allocation.size, + }); + } + + inline fn addressToAddrT(address: AddrT) Addr { + return if (Region == PhysicalMemoryRegion and address >= config.cpu_driver_higher_half_address) VirtualAddress.new(address).toPhysicalAddress() else Addr.new(address); + } + + pub inline fn fromByteSlice(info: struct { + slice: []const u8, + }) Region { + return new(.{ + .address = addressToAddrT(@intFromPtr(info.slice.ptr)), + .size = info.slice.len, + }); + } + + pub inline fn fromAnytype(any: anytype, info: struct {}) Region { + _ = info; + common.assert(@typeInfo(@TypeOf(any)) == .Pointer); + return Region{ + .address = VirtualAddress.new(@intFromPtr(any)), + .size = @sizeOf(@TypeOf(any.*)), + }; + } + + pub inline fn offset(region: Region, asked_offset: AddrT) Region { + const address = region.address.offset(asked_offset); + const size = region.size - asked_offset; + return Region{ + .address = address, + .size = size, + }; + } + + pub inline fn addOffset(region: *Region, asked_offset: AddrT) void { + region.* = region.offset(asked_offset); + } + + pub inline fn top(region: Region) Addr { + return region.address.offset(region.size); + } + + pub fn shrinked(region: Region, size: AddrT) Region { + common.assert(size <= region.size); + const result = Region{ + .address = region.address, + .size = size, + }; + + return result; + } + + pub inline fn takeSlice(region: *Region, size: AddrT) Region { + common.assert(size <= region.size); + const result = Region{ + .address = region.address, + .size = size, + }; + region.* = region.offset(size); + + return result; + } + + pub inline fn split(region: Region, comptime count: comptime_int) [count]Region { + const region_size = @divExact(region.size, count); + var result: [count]Region = undefined; + var address = region.address; + var region_offset: u64 = 0; + inline for (&result) |*split_region| { + split_region.* = Region{ + .address = address.offset(region_offset), + .size = region_size, + }; + region_offset += region_size; + } + + return result; + } + }; +} + +pub const PhysicalMemoryRegion = extern struct { + address: PhysicalAddress, + size: u64, + + pub usingnamespace RegionInterface(@This()); // This is so cool + + pub inline fn toIdentityMappedVirtualAddress(physical_memory_region: PhysicalMemoryRegion) VirtualMemoryRegion { + return .{ + .address = physical_memory_region.address.toIdentityMappedVirtualAddress(), + .size = physical_memory_region.size, + }; + } + + pub inline fn toHigherHalfVirtualAddress(physical_memory_region: PhysicalMemoryRegion) VirtualMemoryRegion { + return .{ + .address = physical_memory_region.address.toHigherHalfVirtualAddress(), + .size = physical_memory_region.size, + }; + } +}; + +pub const VirtualMemoryRegion = extern struct { + address: VirtualAddress, + size: u64, + + pub usingnamespace RegionInterface(@This()); + + pub inline fn access(virtual_memory_region: VirtualMemoryRegion, comptime T: type) []T { + const slice_len = @divExact(virtual_memory_region.size, @sizeOf(T)); + const result = virtual_memory_region.address.access([*]T)[0..safeArchitectureCast(slice_len)]; + return result; + } + + pub inline fn takeByteSlice(virtual_memory_region: *VirtualMemoryRegion, size: u64) []u8 { + return virtual_memory_region.takeSlice(size).access(u8); + } + + pub inline fn toPhysicalAddress(virtual_memory_region: VirtualMemoryRegion, info: struct {}) PhysicalMemoryRegion { + _ = info; + return PhysicalMemoryRegion{ + .address = virtual_memory_region.address.toPhysicalAddress(), + .size = virtual_memory_region.size, + }; + } +}; + +fn getAddrT(comptime AddressEnum: type) type { + const type_info = @typeInfo(AddressEnum); + common.assert(type_info == .Enum); + const AddrT = type_info.Enum.tag_type; + common.assert(switch (common.cpu.arch) { + .x86 => @sizeOf(AddrT) == 2 * @sizeOf(usize), + else => @sizeOf(AddrT) == @sizeOf(usize), + }); + + return AddrT; +} + +pub fn AddressInterface(comptime AddressEnum: type) type { + const Addr = AddressEnum; + const AddrT = getAddrT(AddressEnum); + + const Result = struct { + pub inline fn newNoChecks(addr: AddrT) Addr { + return @as(Addr, @enumFromInt(addr)); + } + + pub inline fn invalid() Addr { + return newNoChecks(0); + } + + pub inline fn value(addr: Addr) AddrT { + return @intFromEnum(addr); + } + + pub inline fn offset(addr: Addr, asked_offset: AddrT) Addr { + return newNoChecks(addr.value() + asked_offset); + } + + pub inline fn negativeOffset(addr: Addr, asked_offset: AddrT) Addr { + return newNoChecks(addr.value() - asked_offset); + } + + pub inline fn addOffset(addr: *Addr, asked_offset: AddrT) void { + addr.* = addr.offset(asked_offset); + } + + pub inline fn subOffset(addr: *Addr, asked_offset: AddrT) void { + addr.* = addr.negativeOffset(asked_offset); + } + + pub inline fn isAligned(addr: Addr, alignment: u64) bool { + const alignment_mask = alignment - 1; + return addr.value() & alignment_mask == 0; + } + }; + + return Result; +} + +pub const PhysicalAddress = enum(u64) { + null = 0, + _, + const PA = @This(); + + pub usingnamespace AddressInterface(@This()); + + pub inline fn new(address: u64) PA { + if (address >= config.cpu_driver_higher_half_address) @panic("Trying to write a higher half virtual address value into a physical address"); + return @as(PA, @enumFromInt(address)); + } + + pub inline fn toIdentityMappedVirtualAddress(physical_address: PA) VirtualAddress { + return VirtualAddress.new(physical_address.value()); + } + + pub inline fn toHigherHalfVirtualAddress(physical_address: PA) VirtualAddress { + return physical_address.toIdentityMappedVirtualAddress().offset(config.cpu_driver_higher_half_address); + } +}; + +pub const VirtualAddress = enum(u64) { + null = 0, + _, + + pub usingnamespace AddressInterface(@This()); + + pub inline fn new(address: anytype) VirtualAddress { + const T = @TypeOf(address); + return @as(VirtualAddress, @enumFromInt(switch (T) { + usize, u64, comptime_int => address, + else => switch (@typeInfo(T)) { + .Fn => @intFromPtr(&address), + .Pointer => @intFromPtr(address), + else => { + @compileLog(T); + @compileError("HA!"); + }, + }, + })); + } + + pub inline fn access(virtual_address: VirtualAddress, comptime Ptr: type) Ptr { + return @as(Ptr, @ptrFromInt(safeArchitectureCast(virtual_address.value()))); + } + + pub inline fn isValid(virtual_address: VirtualAddress) bool { + _ = virtual_address; + return true; + } + + pub inline fn toPhysicalAddress(virtual_address: VirtualAddress) PhysicalAddress { + common.assert(virtual_address.value() >= config.cpu_driver_higher_half_address); + return @as(PhysicalAddress, @enumFromInt(virtual_address.value() - config.cpu_driver_higher_half_address)); + } + + pub inline fn toGuaranteedPhysicalAddress(virtual_address: VirtualAddress) PhysicalAddress { + common.assert(virtual_address.value() < config.cpu_driver_higher_half_address); + return PhysicalAddress.new(virtual_address.value()); + } +}; + +pub const Color = enum { + black, + red, + green, + yellow, + blue, + magenta, + cyan, + white, + bright_black, + bright_red, + bright_green, + bright_yellow, + bright_blue, + bright_magenta, + bright_cyan, + bright_white, + dim, + bold, + reset, + + pub fn get(color: Color) []const u8 { + return switch (color) { + .black => "\x1b[30m", + .red => "\x1b[31m", + .green => "\x1b[32m", + .yellow => "\x1b[33m", + .blue => "\x1b[34m", + .magenta => "\x1b[35m", + .cyan => "\x1b[36m", + .white => "\x1b[37m", + .bright_black => "\x1b[90m", + .bright_red => "\x1b[91m", + .bright_green => "\x1b[92m", + .bright_yellow => "\x1b[93m", + .bright_blue => "\x1b[94m", + .bright_magenta => "\x1b[95m", + .bright_cyan => "\x1b[96m", + .bright_white => "\x1b[97m", + .bold => "\x1b[1m", + .dim => "\x1b[2m", + .reset => "\x1b[0m", + }; + } +}; diff --git a/src/lib/arch.zig b/src/lib/arch.zig new file mode 100644 index 0000000..7030f7b --- /dev/null +++ b/src/lib/arch.zig @@ -0,0 +1,34 @@ +comptime { + const os = @import("builtin").os.tag; + switch (os) { + .uefi, .freestanding => {}, + else => @compileError("This file is not to be compiled with this OS"), + } +} + +pub const current = switch (@import("builtin").cpu.arch) { + .x86 => x86, + .x86_64 => x86_64, + else => @compileError("Architecture not supported"), +}; + +pub const x86 = @import("arch/x86.zig"); +pub const x86_64 = @import("arch/x86_64.zig"); + +pub const default_page_size = current.default_page_size; +pub const reasonable_page_size = current.reasonable_page_size; + +pub const valid_page_sizes = current.valid_page_sizes; +pub const reverse_valid_page_sizes = current.reverse_valid_page_sizes; + +pub fn page_shifter(comptime asked_page_size: comptime_int) comptime_int { + return @ctz(@as(u32, asked_page_size)); +} + +pub fn page_mask(comptime asked_page_size: comptime_int) comptime_int { + return asked_page_size - 1; +} + +pub const Spinlock = current.Spinlock; + +pub const stack_alignment = current.stack_alignment; diff --git a/src/lib/arch/x86.zig b/src/lib/arch/x86.zig new file mode 100644 index 0000000..eed3e71 --- /dev/null +++ b/src/lib/arch/x86.zig @@ -0,0 +1,6 @@ +const x86 = @import("x86/common.zig"); +pub usingnamespace x86; + +pub const default_page_size = valid_page_sizes[0]; +pub const reasonable_page_size = valid_page_sizes[1]; +pub const valid_page_sizes = [2]comptime_int{ 0x1000, 0x1000 * 0x200 }; diff --git a/src/lib/arch/x86/64/registers.zig b/src/lib/arch/x86/64/registers.zig new file mode 100644 index 0000000..e05d7a6 --- /dev/null +++ b/src/lib/arch/x86/64/registers.zig @@ -0,0 +1,343 @@ +const lib = @import("lib"); +const assert = lib.assert; + +pub const RFLAGS = packed struct(u64) { + CF: bool = false, + reserved0: bool = true, + PF: bool = false, + reserved1: bool = false, + AF: bool = false, + reserved2: bool = false, + ZF: bool = false, + SF: bool = false, + TF: bool = false, + IF: bool = false, + DF: bool = false, + OF: bool = false, + IOPL: u2 = 0, + NT: bool = false, + reserved3: bool = false, + RF: bool = false, + VM: bool = false, + AC: bool = false, + VIF: bool = false, + VIP: bool = false, + ID: bool = false, + reserved4: u10 = 0, + reserved5: u32 = 0, + + comptime { + assert(@sizeOf(RFLAGS) == @sizeOf(u64)); + assert(@bitSizeOf(RFLAGS) == @bitSizeOf(u64)); + } + + pub inline fn read() RFLAGS { + return asm volatile ( + \\pushfq + \\pop %[flags] + : [flags] "=r" (-> RFLAGS), + : + : "memory" + ); + } + + pub fn user(rflags: RFLAGS) RFLAGS { + return RFLAGS{ + .IF = true, + .CF = rflags.CF, + .PF = rflags.PF, + .AF = rflags.AF, + .ZF = rflags.ZF, + .SF = rflags.SF, + .DF = rflags.DF, + .OF = rflags.OF, + }; + } +}; + +pub const SimpleRegister = enum { + rax, + rbx, + rcx, + rdx, + rbp, + rsp, + rsi, + rdi, + r8, + r9, + r10, + r11, + r12, + r13, + r14, + r15, + + gs, + cs, + + dr0, + dr1, + dr2, + dr3, + dr4, + dr5, + dr6, + dr7, + + cr2, + cr8, +}; + +pub fn SimpleR64(comptime Register: SimpleRegister) type { + return struct { + pub inline fn read() u64 { + return switch (Register) { + .rax => asm volatile ("mov %rax, %[result]" + : [result] "=r" (-> u64), + ), + .rbx => asm volatile ("mov %rbx, %[result]" + : [result] "=r" (-> u64), + ), + .rcx => asm volatile ("mov %rcx, %[result]" + : [result] "=r" (-> u64), + ), + .rdx => asm volatile ("mov %rdx, %[result]" + : [result] "=r" (-> u64), + ), + .rbp => asm volatile ("mov %rbp, %[result]" + : [result] "=r" (-> u64), + ), + .rsp => asm volatile ("mov %rsp, %[result]" + : [result] "=r" (-> u64), + ), + .rsi => asm volatile ("mov %rsi, %[result]" + : [result] "=r" (-> u64), + ), + .rdi => asm volatile ("mov %rdi, %[result]" + : [result] "=r" (-> u64), + ), + .r8 => asm volatile ("mov %r8, %[result]" + : [result] "=r" (-> u64), + ), + .r9 => asm volatile ("mov %r9, %[result]" + : [result] "=r" (-> u64), + ), + .r10 => asm volatile ("mov %r10, %[result]" + : [result] "=r" (-> u64), + ), + .r11 => asm volatile ("mov %r11, %[result]" + : [result] "=r" (-> u64), + ), + .r12 => asm volatile ("mov %r12, %[result]" + : [result] "=r" (-> u64), + ), + .r13 => asm volatile ("mov %r13, %[result]" + : [result] "=r" (-> u64), + ), + .r14 => asm volatile ("mov %r14, %[result]" + : [result] "=r" (-> u64), + ), + .r15 => asm volatile ("mov %r15, %[result]" + : [result] "=r" (-> u64), + ), + .gs => asm volatile ("mov %gs, %[result]" + : [result] "=r" (-> u64), + : + : "memory" + ), + .cs => asm volatile ("mov %cs, %[result]" + : [result] "=r" (-> u64), + : + : "memory" + ), + .dr0 => asm volatile ("mov %dr0, %[result]" + : [result] "=r" (-> u64), + ), + .dr1 => asm volatile ("mov %dr1, %[result]" + : [result] "=r" (-> u64), + ), + .dr2 => asm volatile ("mov %dr2, %[result]" + : [result] "=r" (-> u64), + ), + .dr3 => asm volatile ("mov %dr3, %[result]" + : [result] "=r" (-> u64), + ), + .dr4 => asm volatile ("mov %dr4, %[result]" + : [result] "=r" (-> u64), + ), + .dr5 => asm volatile ("mov %dr5, %[result]" + : [result] "=r" (-> u64), + ), + .dr6 => asm volatile ("mov %dr6, %[result]" + : [result] "=r" (-> u64), + ), + .dr7 => asm volatile ("mov %dr7, %[result]" + : [result] "=r" (-> u64), + ), + .cr2 => asm volatile ("mov %cr2, %[result]" + : [result] "=r" (-> u64), + : + : "memory" + ), + .cr8 => asm volatile ("mov %cr8, %[result]" + : [result] "=r" (-> u64), + : + : "memory" + ), + }; + } + + pub inline fn write(value: u64) void { + switch (Register) { + .rax => asm volatile ("mov %[in], %rax" + : + : [in] "r" (value), + ), + .rbx => asm volatile ("mov %[in], %rbx" + : + : [in] "r" (value), + ), + .rcx => asm volatile ("mov %[in], %rcx" + : + : [in] "r" (value), + ), + .rdx => asm volatile ("mov %[in], %rdx" + : + : [in] "r" (value), + ), + .rbp => asm volatile ("mov %[in], %rbp" + : + : [in] "r" (value), + ), + .rsp => asm volatile ("mov %[in], %rsp" + : + : [in] "r" (value), + ), + .rsi => asm volatile ("mov %[in], %rsi" + : + : [in] "r" (value), + ), + .rdi => asm volatile ("mov %[in], %rdi" + : + : [in] "r" (value), + ), + .r8 => asm volatile ("mov %[in], %r8" + : + : [in] "r" (value), + ), + .r9 => asm volatile ("mov %[in], %r9" + : + : [in] "r" (value), + ), + .r10 => asm volatile ("mov %[in], %r10" + : + : [in] "r" (value), + ), + .r11 => asm volatile ("mov %[in], %r11" + : + : [in] "r" (value), + ), + .r12 => asm volatile ("mov %[in], %r12" + : + : [in] "r" (value), + ), + .r13 => asm volatile ("mov %[in], %r13" + : + : [in] "r" (value), + ), + .r14 => asm volatile ("mov %[in], %r14" + : + : [in] "r" (value), + ), + .r15 => asm volatile ("mov %[in], %r15" + : + : [in] "r" (value), + ), + .gs => asm volatile ("mov %[in], %gs" + : + : [in] "r" (value), + : "memory" + ), + .cs => asm volatile ("mov %[in], %cs" + : + : [in] "r" (value), + : "memory" + ), + .dr0 => asm volatile ("mov %[in], %dr0" + : + : [in] "r" (value), + ), + .dr1 => asm volatile ("mov %[in], %dr1" + : + : [in] "r" (value), + ), + .dr2 => asm volatile ("mov %[in], %dr2" + : + : [in] "r" (value), + ), + .dr3 => asm volatile ("mov %[in], %dr3" + : + : [in] "r" (value), + ), + .dr4 => asm volatile ("mov %[in], %dr4" + : + : [in] "r" (value), + ), + .dr5 => asm volatile ("mov %[in], %dr5" + : + : [in] "r" (value), + ), + .dr6 => asm volatile ("mov %[in], %dr6" + : + : [in] "r" (value), + ), + .dr7 => asm volatile ("mov %[in], %dr7" + : + : [in] "r" (value), + ), + .cr2 => asm volatile ("mov %[in], %cr2" + : + : [in] "r" (value), + : "memory" + ), + .cr8 => asm volatile ("mov %[in], %cr8" + : + : [in] "r" (value), + : "memory" + ), + } + } + }; +} + +pub const ComplexRegister = enum { cr0, cr3, cr4 }; + +pub const rax = SimpleR64(.rax); +pub const rbx = SimpleR64(.rbx); +pub const rcx = SimpleR64(.rcx); +pub const rdx = SimpleR64(.rdx); +pub const rbp = SimpleR64(.rbp); +pub const rsp = SimpleR64(.rsp); +pub const rsi = SimpleR64(.rsi); +pub const rdi = SimpleR64(.rdi); +pub const r8 = SimpleR64(.r8); +pub const r9 = SimpleR64(.r9); +pub const r10 = SimpleR64(.r10); +pub const r11 = SimpleR64(.r11); +pub const r12 = SimpleR64(.r12); +pub const r13 = SimpleR64(.r13); +pub const r14 = SimpleR64(.r14); +pub const r15 = SimpleR64(.r15); + +pub const gs = SimpleR64(.gs); +pub const cs = SimpleR64(.cs); + +pub const dr0 = SimpleR64(.dr0); +pub const dr1 = SimpleR64(.dr1); +pub const dr2 = SimpleR64(.dr2); +pub const dr3 = SimpleR64(.dr3); +pub const dr4 = SimpleR64(.dr4); +pub const dr5 = SimpleR64(.dr5); +pub const dr6 = SimpleR64(.dr6); +pub const dr7 = SimpleR64(.dr7); diff --git a/src/lib/arch/x86/common.zig b/src/lib/arch/x86/common.zig new file mode 100644 index 0000000..e4b9e61 --- /dev/null +++ b/src/lib/arch/x86/common.zig @@ -0,0 +1,59 @@ +pub const CPUID = extern struct { + eax: u32, + ebx: u32, + edx: u32, + ecx: u32, +}; + +pub inline fn cpuid(leaf: u32) CPUID { + var eax: u32 = undefined; + var ebx: u32 = undefined; + var edx: u32 = undefined; + var ecx: u32 = undefined; + + asm volatile ( + \\cpuid + : [eax] "={eax}" (eax), + [ebx] "={ebx}" (ebx), + [edx] "={edx}" (edx), + [ecx] "={ecx}" (ecx), + : [leaf] "{eax}" (leaf), + ); + + return CPUID{ + .eax = eax, + .ebx = ebx, + .edx = edx, + .ecx = ecx, + }; +} + +pub const Spinlock = enum(u8) { + released = 0, + acquired = 1, + + pub inline fn acquire(spinlock: *volatile Spinlock) void { + asm volatile ( + \\0: + \\xchgb %[value], %[spinlock] + \\test %[value], %[value] + \\jz 2f + // If not acquire, go to spinloop + \\1: + \\pause + \\cmp %[value], %[spinlock] + // Retry + \\jne 0b + \\jmp 1b + \\2: + : + : [spinlock] "*p" (spinlock), + [value] "r" (Spinlock.acquired), + : "memory" + ); + } + + pub inline fn release(spinlock: *volatile Spinlock) void { + @atomicStore(Spinlock, spinlock, .released, .Release); + } +}; diff --git a/src/lib/arch/x86_64.zig b/src/lib/arch/x86_64.zig new file mode 100644 index 0000000..ebd1b05 --- /dev/null +++ b/src/lib/arch/x86_64.zig @@ -0,0 +1,34 @@ +const lib = @import("lib"); +const x86 = @import("x86/common.zig"); +pub usingnamespace x86; + +pub const valid_page_sizes = [3]comptime_int{ 0x1000, 0x1000 * 0x200, 0x1000 * 0x200 * 0x200 }; +pub const reverse_valid_page_sizes = blk: { + var reverse = valid_page_sizes; + lib.reverse(@TypeOf(valid_page_sizes[0]), &reverse); + // var reverse_u64: [valid_page_sizes.len]u64 = undefined; + // for (reverse, &reverse_u64) |r_el, *ru64_el| { + // ru64_el.* = r_el; + // } + + break :blk reverse; +}; +pub const default_page_size = valid_page_sizes[0]; +pub const reasonable_page_size = valid_page_sizes[1]; + +pub const registers = @import("x86/64/registers.zig"); + +pub inline fn readTimestamp() u64 { + var edx: u32 = undefined; + var eax: u32 = undefined; + + asm volatile ( + \\rdtsc + : [eax] "={eax}" (eax), + [edx] "={edx}" (edx), + ); + + return @as(u64, edx) << 32 | eax; +} + +pub const stack_alignment = 0x10; diff --git a/src/lib/config.zig b/src/lib/config.zig new file mode 100644 index 0000000..e5f6180 --- /dev/null +++ b/src/lib/config.zig @@ -0,0 +1,6 @@ +pub const cpu_driver_higher_half_address = 0xffff_8000_0000_0000; +pub const cpu_driver_start = 0xffff_ffff_8000_0000; +pub const real_hardware = false; +pub const safe_slow = false; +pub const timeslicing = true; +pub const enable_smp = false; diff --git a/src/lib/crc32.zig b/src/lib/crc32.zig new file mode 100644 index 0000000..2049cc4 --- /dev/null +++ b/src/lib/crc32.zig @@ -0,0 +1,13 @@ +const lib = @import("lib"); + +pub fn compute(bytes: []const u8) u32 { + var result: u32 = lib.maxInt(u32); + for (bytes) |byte| { + result = (result >> 8) ^ table[@as(u8, @truncate(result ^ byte))]; + } + + result ^= lib.maxInt(u32); + return result; +} + +const table = [_]u32{ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; diff --git a/src/lib/disk.zig b/src/lib/disk.zig new file mode 100644 index 0000000..9904191 --- /dev/null +++ b/src/lib/disk.zig @@ -0,0 +1,123 @@ +const lib = @import("lib"); + +const FAT32 = lib.Filesystem.FAT32; +const GPT = lib.PartitionTable.GPT; +const MBR = lib.PartitionTable.MBR; + +const ArrayListAligned = lib.ArrayListAligned; +const assert = lib.assert; +const asBytes = lib.asBytes; +const log = lib.log.scoped(.Disk); +const sliceAsBytes = lib.sliceAsBytes; + +pub const Disk = extern struct { + type: Type, + disk_size: u64, + partition_sizes: [GPT.default_max_partition_count]u64 = [1]u64{0} ** GPT.default_max_partition_count, + cache_size: u16, + sector_size: u16, + callbacks: Callbacks, + + pub const Type = lib.DiskType; + + pub const ReadFn = fn (disk: *Disk, sector_count: u64, sector_offset: u64, provided_buffer: ?[]u8) ReadError!ReadResult; + pub const ReadError = error{ + read_error, + }; + pub const ReadResult = extern struct { + sector_count: u64, + buffer: [*]u8, + }; + + pub const ReadCacheFn = fn (disk: *Disk, sector_count: u64, sector_offset: u64) ReadError!ReadResult; + + pub const WriteFn = fn (disk: *Disk, bytes: []const u8, sector_offset: u64, commit_memory_to_disk: bool) WriteError!void; + pub const WriteError = error{ + not_supported, + disk_size_overflow, + }; + + pub const Callbacks = extern struct { + read: *const ReadFn, + write: *const WriteFn, + readCache: *const ReadCacheFn, + }; + + pub inline fn getProvidedBuffer(disk: *Disk, comptime T: type, count: usize, allocator: ?*lib.Allocator, force: bool) !?[]u8 { + if ((disk.type == .memory and force) or (disk.type != .memory)) { + if (allocator) |alloc| { + const size = @sizeOf(T) * count; + const alignment = @alignOf(T); + const result = try alloc.allocateBytes(size, alignment); + const slice = @as([*]u8, @ptrFromInt(@as(usize, @intCast(result.address))))[0..@as(usize, @intCast(result.size))]; + if (slice.len != size) @panic("WTQSAD/jasD"); + return slice; + } + } + + return null; + } + + const AdvancedReadOptions = packed struct(u8) { + force: bool = false, + reserved: u7 = 0, + }; + + pub fn readTypedSectors(disk: *Disk, comptime T: type, sector_offset: u64, allocator: ?*lib.Allocator, options: AdvancedReadOptions) !*T { + const sector_count = @divExact(@sizeOf(T), disk.sector_size); + const provided_buffer = try disk.getProvidedBuffer(T, 1, allocator, options.force); + const read_result = try disk.callbacks.read(disk, sector_count, sector_offset, provided_buffer); + if (read_result.sector_count != sector_count) @panic("Sector count mismatch"); + // Don't need to write back since it's a memory disk + const result: *T = @ptrCast(@alignCast(read_result.buffer)); + return result; + } + + pub inline fn writeTypedSectors(disk: *Disk, comptime T: type, content: *T, sector_offset: u64, commit_memory_to_disk: bool) !void { + try disk.callbacks.write(disk, asBytes(content), sector_offset, commit_memory_to_disk); + } + + pub inline fn readSlice(disk: *Disk, comptime T: type, len: usize, sector_offset: u64, allocator: ?*lib.Allocator, options: AdvancedReadOptions) ![]T { + const element_count_per_sector = @divExact(disk.sector_size, @sizeOf(T)); + const sector_count = @divExact(len, element_count_per_sector); + const provided_buffer = try disk.getProvidedBuffer(T, len, allocator, options.force); + const read_result = try disk.callbacks.read(disk, sector_count, sector_offset, provided_buffer); + if (read_result.sector_count != sector_count) @panic("read_slice: sector count mismatch"); + const result = @as([*]T, @ptrCast(@alignCast(read_result.buffer)))[0..len]; + return result; + } + + pub inline fn writeSlice(disk: *Disk, comptime T: type, slice: []const T, sector_offset: u64, commit_memory_to_disk: bool) !void { + const byte_slice = sliceAsBytes(slice); + try disk.callbacks.write(disk, byte_slice, sector_offset, commit_memory_to_disk); + } + + pub fn verify(disk: *Disk) !void { + const mbr = try disk.read_typed_sectors(MBR.Struct, 0); + try mbr.verify(disk); + unreachable; + } + + pub const Work = struct { + sector_offset: u64, + sector_count: u64, + operation: Operation, + }; + + pub const Operation = enum(u1) { + read = 0, + write = 1, + + // This is used by NVMe and AHCI, so it is needed to match these values + comptime { + assert(@bitSizeOf(Operation) == @bitSizeOf(u1)); + assert(@intFromEnum(Operation.read) == 0); + assert(@intFromEnum(Operation.write) == 1); + } + }; + + pub const PartitionRange = extern struct { + first_lba: u64, + last_lba: u64, + }; +}; diff --git a/src/lib/extern_enum_array.zig b/src/lib/extern_enum_array.zig new file mode 100644 index 0000000..06d53de --- /dev/null +++ b/src/lib/extern_enum_array.zig @@ -0,0 +1,231 @@ +const lib = @import("lib"); +// ENUM ARRAY +pub fn IndexedArray(comptime I: type, comptime V: type, comptime Ext: fn (type) type) type { + comptime ensureIndexer(I); + return extern struct { + const Self = @This(); + + pub usingnamespace Ext(Self); + + /// The index mapping for this map + pub const Indexer = I; + /// The key type used to index this map + pub const Key = Indexer.Key; + /// The value type stored in this map + pub const Value = V; + /// The number of possible keys in the map + pub const len = Indexer.count; + + values: [Indexer.count]Value, + + pub fn initUndefined() Self { + return Self{ .values = undefined }; + } + + pub fn initFill(v: Value) Self { + var self: Self = undefined; + lib.set(Value, &self.values, v); + return self; + } + + /// Returns the value in the array associated with a key. + pub fn get(self: Self, key: Key) Value { + return self.values[Indexer.indexOf(key)]; + } + + /// Returns a pointer to the slot in the array associated with a key. + pub fn getPtr(self: *Self, key: Key) *Value { + return &self.values[Indexer.indexOf(key)]; + } + + /// Returns a const pointer to the slot in the array associated with a key. + pub fn getPtrConst(self: *const Self, key: Key) *const Value { + return &self.values[Indexer.indexOf(key)]; + } + + /// Sets the value in the slot associated with a key. + pub fn set(self: *Self, key: Key, value: Value) void { + self.values[Indexer.indexOf(key)] = value; + } + + /// Iterates over the items in the array, in index order. + pub fn iterator(self: *Self) Iterator { + return .{ + .values = &self.values, + }; + } + + /// An entry in the array. + pub const Entry = extern struct { + /// The key associated with this entry. + /// Modifying this key will not change the array. + key: Key, + + /// A pointer to the value in the array associated + /// with this key. Modifications through this + /// pointer will modify the underlying data. + value: *Value, + }; + + pub const Iterator = extern struct { + index: usize = 0, + values: *[Indexer.count]Value, + + pub fn next(self: *Iterator) ?Entry { + const index = self.index; + if (index < Indexer.count) { + self.index += 1; + return Entry{ + .key = Indexer.keyForIndex(index), + .value = &self.values[index], + }; + } + return null; + } + }; + }; +} + +pub fn ensureIndexer(comptime T: type) void { + comptime { + if (!@hasDecl(T, "Key")) @compileError("Indexer must have decl Key: type."); + if (@TypeOf(T.Key) != type) @compileError("Indexer.Key must be a type."); + if (!@hasDecl(T, "count")) @compileError("Indexer must have decl count: usize."); + if (@TypeOf(T.count) != usize) @compileError("Indexer.count must be a usize."); + if (!@hasDecl(T, "indexOf")) @compileError("Indexer.indexOf must be a fn(Key)usize."); + if (@TypeOf(T.indexOf) != fn (T.Key) usize) @compileError("Indexer must have decl indexOf: fn(Key)usize."); + if (!@hasDecl(T, "keyForIndex")) @compileError("Indexer must have decl keyForIndex: fn(usize)Key."); + if (@TypeOf(T.keyForIndex) != fn (usize) T.Key) @compileError("Indexer.keyForIndex must be a fn(usize)Key."); + } +} + +pub fn EnumArray(comptime E: type, comptime V: type) type { + const mixin = extern struct { + fn EnumArrayExt(comptime Self: type) type { + const Indexer = Self.Indexer; + return extern struct { + /// Initializes all values in the enum array + pub fn init(init_values: EnumFieldStruct(E, V, @as(?V, null))) Self { + return initDefault(@as(?V, null), init_values); + } + + /// Initializes values in the enum array, with the specified default. + pub fn initDefault(comptime default: ?V, init_values: EnumFieldStruct(E, V, default)) Self { + var result = Self{ .values = undefined }; + comptime var i: usize = 0; + inline while (i < Self.len) : (i += 1) { + const key = comptime Indexer.keyForIndex(i); + const tag = @tagName(key); + result.values[i] = @field(init_values, tag); + } + return result; + } + }; + } + }; + return IndexedArray(EnumIndexer(E), V, mixin.EnumArrayExt); +} +const EnumField = lib.Type.EnumField; +pub fn EnumIndexer(comptime E: type) type { + if (!@typeInfo(E).Enum.is_exhaustive) { + @compileError("Cannot create an enum indexer for a non-exhaustive enum."); + } + + const const_fields = lib.fields(E); + comptime var fields = const_fields[0..const_fields.len].*; + const fields_len = fields.len; + if (fields_len == 0) { + return extern struct { + pub const Key = E; + pub const count: usize = 0; + pub fn indexOf(e: E) usize { + _ = e; + unreachable; + } + pub fn keyForIndex(i: usize) E { + _ = i; + unreachable; + } + }; + } + const SortContext = struct { + fields: []EnumField, + + pub fn lessThan(comptime ctx: @This(), comptime a: usize, comptime b: usize) bool { + return ctx.fields[a].value < ctx.fields[b].value; + } + + pub fn swap(comptime ctx: @This(), comptime a: usize, comptime b: usize) void { + return lib.swap(EnumField, &ctx.fields[a], &ctx.fields[b]); + } + }; + lib.sort.insertionContext(0, fields_len, SortContext{ .fields = &fields }); + + const min = fields[0].value; + const max = fields[fields.len - 1].value; + if (max - min == fields.len - 1) { + return extern struct { + pub const Key = E; + pub const count = fields_len; + pub fn indexOf(e: E) usize { + return @as(usize, @intCast(@intFromEnum(e) - min)); + } + pub fn keyForIndex(i: usize) E { + // TODO fix addition semantics. This calculation + // gives up some safety to avoid artificially limiting + // the range of signed enum values to max_isize. + const enum_value = if (min < 0) @as(isize, @bitCast(i)) +% min else i + min; + return @as(E, @enumFromInt(@as(lib.Tag(E), @intCast(enum_value)))); + } + }; + } + + const keys = valuesFromFields(E, &fields); + + return extern struct { + pub const Key = E; + pub const count = fields_len; + pub fn indexOf(e: E) usize { + for (keys, 0..) |k, i| { + if (k == e) return i; + } + unreachable; + } + pub fn keyForIndex(i: usize) E { + return keys[i]; + } + }; +} +pub fn EnumFieldStruct(comptime E: type, comptime Data: type, comptime field_default: ?Data) type { + const StructField = lib.builtin.Type.StructField; + var fields: []const StructField = &[_]StructField{}; + for (lib.fields(E)) |field| { + fields = fields ++ &[_]StructField{.{ + .name = field.name, + .type = Data, + .default_value = if (field_default) |d| @as(?*const anyopaque, @ptrCast(&d)) else null, + .is_comptime = false, + .alignment = if (@sizeOf(Data) > 0) @alignOf(Data) else 0, + }}; + } + return @Type(.{ .Struct = .{ + .layout = .Extern, + .fields = fields, + .decls = &.{}, + .is_tuple = false, + } }); +} + +fn ascByValue(ctx: void, comptime a: EnumField, comptime b: EnumField) bool { + _ = ctx; + return a.value < b.value; +} +pub fn valuesFromFields(comptime E: type, comptime fields: []const EnumField) []const E { + comptime { + var result: [fields.len]E = undefined; + for (fields, 0..) |f, i| { + result[i] = @field(E, f.name); + } + return &result; + } +} diff --git a/src/lib/filesystem.zig b/src/lib/filesystem.zig new file mode 100644 index 0000000..f56bebd --- /dev/null +++ b/src/lib/filesystem.zig @@ -0,0 +1,18 @@ +const lib = @import("lib"); +pub const FAT32 = @import("filesystem/fat32.zig"); + +pub const Type = lib.FilesystemType; + +pub const ReadError = error{ + unsupported, + failed, +}; + +pub const WriteError = error{ + unsupported, + failed, +}; + +test { + _ = FAT32; +} diff --git a/src/lib/filesystem/fat32.zig b/src/lib/filesystem/fat32.zig new file mode 100644 index 0000000..ab426a2 --- /dev/null +++ b/src/lib/filesystem/fat32.zig @@ -0,0 +1,1297 @@ +const FAT32 = @This(); + +const lib = @import("lib"); +const log = lib.log; +const kb = lib.kb; +const mb = lib.mb; +const gb = lib.gb; +const assert = lib.assert; + +const Disk = lib.Disk; +const GPT = lib.PartitionTable.GPT; +const MBR = lib.PartitionTable.MBR; +const NLS = lib.NLS; + +pub const count = 2; +pub const volumes_lba = GPT.reserved_partition_size / GPT.max_block_size / 2; +pub const minimum_partition_size = 33 * mb; +pub const maximum_partition_size = 32 * gb; +pub const last_cluster = 0xffff_ffff; +pub const starting_cluster = 2; +pub const default_fs_info_sector = 1; +pub const default_backup_boot_record_sector = 6; +pub const default_reserved_sector_count = 32; + +const NameCase = packed struct(u8) { + reserved: u3 = 0, + base: Case = .upper, + extension: Case = .upper, + reserved1: u3 = 0, +}; +const Case = enum(u1) { + upper = 0, + lower = 1, +}; + +pub const FSInfo = extern struct { + lead_signature: u32 = 0x41617272, + reserved: [480]u8 = [1]u8{0} ** 480, + signature: u32 = 0x61417272, + free_cluster_count: u32, + last_allocated_cluster: u32, + reserved1: [12]u8 = [1]u8{0} ** 12, + trail_signature: u32 = 0xaa550000, + + pub fn format(fsinfo: *const FSInfo, comptime _: []const u8, _: lib.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + try lib.format(writer, "FSInfo:\n", .{}); + try lib.format(writer, "\tLead signature: 0x{x}\n", .{fsinfo.lead_signature}); + try lib.format(writer, "\tOther signature: 0x{x}\n", .{fsinfo.signature}); + try lib.format(writer, "\tFree cluster count: {}\n", .{fsinfo.free_cluster_count}); + try lib.format(writer, "\tLast allocated cluster: {}\n", .{fsinfo.last_allocated_cluster}); + try lib.format(writer, "\tTrail signature: 0x{x}\n", .{fsinfo.trail_signature}); + } +}; + +pub fn isFilesystem(file: []const u8) bool { + const magic = "FAT32 "; + return lib.equal(u8, file[0x52..], magic); +} + +pub fn isBootRecord(file: []const u8) bool { + const magic = [_]u8{ 0x55, 0xAA }; + const magic_alternative = [_]u8{ 'M', 'S', 'W', 'I', 'N', '4', '.', '1' }; + if (!lib.equal(u8, file[0x1fe..], magic)) return false; + if (!lib.equal(u8, file[0x3fe..], magic)) return false; + if (!lib.equal(u8, file[0x5fe..], magic)) return false; + if (!lib.equal(u8, file[0x03..], magic_alternative)) return false; + return true; +} + +pub fn getClusterSize(size: u64) u16 { + if (size <= 64 * mb) return lib.default_sector_size; + if (size <= 128 * mb) return 1 * kb; + if (size <= 256 * mb) return 2 * kb; + if (size <= 8 * gb) return 8 * kb; + if (size <= 16 * gb) return 16 * kb; + + return 32 * kb; +} + +pub const Date = packed struct(u16) { + day: u5, + month: u4, + year: u7, + + pub fn new(day: u5, month: u4, year: u12) Date { + return Date{ + .day = day, + .month = month, + .year = @as(u7, @intCast(year - 1980)), + }; + } +}; + +pub const Time = packed struct(u16) { + seconds_2_factor: u5, + minutes: u6, + hours: u5, + + pub fn new(seconds: u6, minutes: u6, hours: u5) Time { + return Time{ + .seconds_2_factor = @as(u5, @intCast(seconds / 2)), + .minutes = minutes, + .hours = hours, + }; + } +}; + +const max_base_len = 8; +const max_extension_len = 3; +const short_name_len = max_base_len + max_extension_len; +const long_name_max_characters = 255; + +pub const DirectoryEntry = extern struct { + name: [short_name_len]u8, + attributes: Attributes, + case: NameCase, + creation_time_tenth: u8, + creation_time: Time, + creation_date: Date, + last_access_date: Date, + first_cluster_high: u16, + last_write_time: Time, + last_write_date: Date, + first_cluster_low: u16, + file_size: u32, + + pub const Sector = [per_sector]@This(); + pub const per_sector = @divExact(lib.default_sector_size, @sizeOf(@This())); + + pub const Chain = extern struct { + previous: ?*DirectoryEntry = null, + next: ?*DirectoryEntry = null, + current: *DirectoryEntry, + }; + + pub fn format(entry: *const DirectoryEntry, comptime _: []const u8, _: lib.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + try lib.format(writer, "Directory entry:\n", .{}); + try lib.format(writer, "\tName: {s}\n", .{entry.name}); + try lib.format(writer, "\tAttributes: {}\n", .{entry.attributes}); + try lib.format(writer, "\tCreation time tenth: {}\n", .{entry.creation_time_tenth}); + try lib.format(writer, "\tCreation time: {}\n", .{entry.creation_time}); + try lib.format(writer, "\tCreation date: {}\n", .{entry.creation_date}); + try lib.format(writer, "\tLast access date: {}\n", .{entry.last_access_date}); + try lib.format(writer, "\tLast write time: {}\n", .{entry.last_write_time}); + try lib.format(writer, "\tLast write date: {}\n", .{entry.last_write_date}); + const first_cluster = @as(u32, entry.first_cluster_high) << 16 | entry.first_cluster_low; + try lib.format(writer, "\tFirst cluster: 0x{x}\n", .{first_cluster}); + try lib.format(writer, "\tFile size: 0x{x}\n", .{entry.file_size}); + } + + pub fn isFree(entry: DirectoryEntry) bool { + const first_char = entry.name[0]; + assert(first_char != 0x20); + return switch (first_char) { + 0, 0xe5, ' ' => true, + else => false, + }; + } + + pub fn setFirstCluster(entry: *DirectoryEntry, cluster: u32) void { + entry.first_cluster_low = @as(u16, @truncate(cluster)); + entry.first_cluster_high = @as(u16, @truncate(cluster >> 16)); + } + + pub fn getFirstCluster(entry: *DirectoryEntry) u32 { + return @as(u32, entry.first_cluster_high) << 16 | entry.first_cluster_low; + } + + comptime { + assert(@sizeOf(@This()) == 32); + } +}; + +pub const Attributes = packed struct(u8) { + read_only: bool = false, + hidden: bool = false, + system: bool = false, + volume_id: bool = false, + directory: bool = false, + archive: bool = false, + reserved: u2 = 0, + + pub fn hasLongName(attributes: Attributes) bool { + return attributes.read_only and attributes.hidden and attributes.system and attributes.volume_id; + } +}; + +pub const LongNameEntry = extern struct { + sequence_number: packed struct(u8) { + number: u5, + first_physical_entry: u1 = 0, + last_logical: bool, + reserved: u1 = 0, + }, + chars_0_4: [5]u16 align(1), + attributes: Attributes, + reserved: u8 = 0, + checksum: u8, + chars_5_10: [6]u16 align(1), + first_cluster: u16 align(1), + chars_11_12: [2]u16 align(1), + + pub const Sector = [per_sector]@This(); + pub const per_sector = @divExact(lib.default_sector_size, @sizeOf(@This())); + + pub fn isLast(entry: LongNameEntry) bool { + return entry.sequence_number.last_logical; + } + + fn getCharacters(entry: LongNameEntry) [13]u16 { + return entry.chars_0_4 ++ entry.chars_5_10 ++ entry.chars_11_12; + } + + fn isFree(entry: LongNameEntry) bool { + const first_char = entry.chars_0_4[0]; + assert(first_char != 0x20); + return switch (first_char) { + 0, 0xe5, ' ' => true, + else => false, + }; + } +}; + +pub const Entry = packed struct(u32) { + next_cluster: u28, + reserved: u4 = 0, + + pub const Sector = [per_sector]FAT32.Entry; + const per_sector = @divExact(lib.default_sector_size, @sizeOf(FAT32.Entry)); + + pub fn isFree(entry: Entry) bool { + return entry.next_cluster == value_free; + } + + pub fn isAllocating(entry: Entry) bool { + return entry.next_cluster == value_allocated_and_eof or (entry.next_cluster >= value_allocated_start and entry.next_cluster < value_reserved_and_should_not_be_used_end); + } + + pub fn getType(entry: Entry, max_valid_cluster_number: u32) Type { + return switch (entry.value) { + value_free => .free, + value_bad_cluster => .bad_cluster, + value_reserved_and_should_not_be_used_eof_start...value_reserved_and_should_not_be_used_eof_end => .reserved_and_should_not_be_used_eof, + value_allocated_and_eof => .allocated_and_eof, + else => if (entry.value >= value_allocated_start and entry.value <= @as(u28, @intCast(max_valid_cluster_number))) .allocated else if (entry.value >= @as(u28, @intCast(max_valid_cluster_number)) + 1 and entry.value <= value_reserved_and_should_not_be_used_end) .reserved_and_should_not_be_used else @panic("fat32: getType unexpected error"), + }; + } + + fn getEntry(t: Type) Entry { + return Entry{ + .next_cluster = switch (t) { + .free => value_free, + .allocated => value_allocated_start, + .reserved_and_should_not_be_used => value_reserved_and_should_not_be_used_end, + .bad_cluster => value_bad_cluster, + .reserved_and_should_not_be_used_eof => value_reserved_and_should_not_be_used_eof_start, + .allocated_and_eof => value_allocated_and_eof, + }, + }; + } + + pub const free = getEntry(.free); + pub const allocated = getEntry(.allocated); + pub const reserved_and_should_not_be_used = getEntry(.reserved_and_should_not_be_used); + pub const bad_cluster = getEntry(.bad_cluster); + pub const reserved_and_should_not_be_used_eof = getEntry(.reserved_and_should_not_be_used_eof); + pub const allocated_and_eof = getEntry(.allocated_and_eof); + + const value_free = 0; + const value_allocated_start = 2; + const value_reserved_and_should_not_be_used_end = 0xfff_fff6; + const value_bad_cluster = 0xfff_fff7; + const value_reserved_and_should_not_be_used_eof_start = 0xfff_fff8; + const value_reserved_and_should_not_be_used_eof_end = 0xfff_fffe; + const value_allocated_and_eof = 0xfff_ffff; + + pub const Type = enum { + free, + allocated, + reserved_and_should_not_be_used, + bad_cluster, + reserved_and_should_not_be_used_eof, + allocated_and_eof, + }; +}; + +pub fn getMinCluster(comptime filesystem: lib.FilesystemType) comptime_int { + return switch (filesystem) { + .fat32 => 65525, + else => @compileError("Filesystem not supported"), + }; +} + +pub fn getMaxCluster(comptime filesystem: lib.FilesystemType) comptime_int { + return switch (filesystem) { + .fat32 => 268435446, + else => @compileError("Filesystem not supported"), + }; +} + +const dot_entry_name: [short_name_len]u8 = ".".* ++ ([1]u8{' '} ** 10); +const dot_dot_entry_name: [short_name_len]u8 = "..".* ++ ([1]u8{' '} ** 9); + +pub const NameConfiguration = packed struct(u8) { + display: Display, + create: Create, + reserved: u5 = 0, + + const Create = enum(u1) { + windows_95 = 0, + windows_nt = 1, + }; + const Display = enum(u2) { + lower = 0, + windows_95 = 1, + windows_nt = 2, + }; +}; + +const lower = NameConfiguration{ .display = .lower, .create = .windows_95 }; +const windows_95 = NameConfiguration{ .display = .windows_95, .create = .windows_95 }; +const windows_nt = NameConfiguration{ .display = .windows_nt, .create = .windows_nt }; +const mixed = NameConfiguration{ .display = .windows_nt, .create = .windows_95 }; + +pub const Cache = extern struct { + disk: *Disk, + partition_range: Disk.PartitionRange, + mbr: *MBR.Partition, + fs_info: *FSInfo, + name_configuration: NameConfiguration = mixed, + allocator: ?*lib.Allocator, + + fn get_backup_boot_record_sector(cache: Cache) u64 { + return cache.partition_range.first_lba + cache.mbr.bpb.backup_boot_record_sector; + } + + pub fn readFile(cache: Cache, allocator: ?*lib.Allocator, file_path: []const u8) ![]u8 { + const directory_entry_result = try cache.getDirectoryEntry(file_path, null); + const directory_entry = directory_entry_result.directory_entry; + const first_cluster = directory_entry.getFirstCluster(); + const file_size = directory_entry.file_size; + const aligned_file_size = lib.alignForward(file_size, cache.disk.sector_size); + const lba = cache.clusterToSector(first_cluster); + const result = try cache.disk.readSlice(u8, aligned_file_size, lba, allocator, .{}); + return result[0..file_size]; + } + + pub fn readFileToBuffer(cache: Cache, file_path: []const u8, file_buffer: []u8) ![]u8 { + const directory_entry_result = try cache.getDirectoryEntry(file_path, null); + const directory_entry = directory_entry_result.directory_entry; + const first_cluster = directory_entry.getFirstCluster(); + const file_size = directory_entry.file_size; + const aligned_file_size = lib.alignForward(usize, file_size, cache.disk.sector_size); + const lba = cache.clusterToSector(first_cluster); + + log.debug("Start disk callback", .{}); + + const result = try cache.disk.callbacks.read(cache.disk, @divExact(aligned_file_size, cache.disk.sector_size), lba, file_buffer); + log.debug("End disk callback", .{}); + return result.buffer[0..file_size]; + } + + pub fn readFileToCache(cache: Cache, file_path: []const u8, size: usize) ![]const u8 { + const directory_entry_result = try cache.getDirectoryEntry(file_path, null); + const directory_entry = directory_entry_result.directory_entry; + const first_cluster = directory_entry.getFirstCluster(); + const file_size = directory_entry.file_size; + const lba = cache.clusterToSector(first_cluster); + + const read_size = @min(file_size, size); + const aligned_read_size = lib.alignForward(usize, read_size, cache.disk.sector_size); + + const result = try cache.disk.callbacks.readCache(cache.disk, @divExact(aligned_read_size, cache.disk.sector_size), lba); + const result_slice = result.buffer[0..read_size]; + return result_slice; + } + + pub fn getFileSize(cache: Cache, file_path: []const u8) !u32 { + const directory_entry_result = try cache.getDirectoryEntry(file_path, null); + return directory_entry_result.directory_entry.file_size; + } + + pub fn fromGPTPartitionCache(allocator: *lib.Allocator, gpt_partition_cache: GPT.Partition.Cache) !FAT32.Cache { + const partition_range = Disk.PartitionRange{ + .first_lba = gpt_partition_cache.partition.first_lba, + .last_lba = gpt_partition_cache.partition.last_lba, + }; + const disk = gpt_partition_cache.gpt.disk; + + const partition_mbr = try disk.readTypedSectors(MBR.Partition, partition_range.first_lba, allocator, .{}); + assert(partition_mbr.bpb.dos3_31.dos2_0.cluster_sector_count == 1); + const fs_info_sector = partition_range.first_lba + partition_mbr.bpb.fs_info_sector; + const fs_info = try disk.readTypedSectors(FAT32.FSInfo, fs_info_sector, allocator, .{}); + + return .{ + .disk = disk, + .partition_range = partition_range, + .mbr = partition_mbr, + .fs_info = fs_info, + .allocator = allocator, + }; + } + + pub fn reserveDirectoryEntries(cache: Cache, cluster: u32, entry_count: usize) !ReserveDirectoryEntries { + const root_cluster = cache.get_root_cluster(); + const root_cluster_lba = cache.get_data_lba(); + const cluster_directory_entry_offset_lba = cache.getClusterSectorCount() * (cluster - root_cluster); + const cluster_directory_entry_lba = root_cluster_lba + cluster_directory_entry_offset_lba; + const cluster_sector_count = cache.getClusterSectorCount(); + assert(cluster_sector_count == 1); + + // TODO: what to do when there's more than one cluster per directory? + const top_cluster_lba = cluster_directory_entry_lba + cluster_sector_count; + var cluster_lba = cluster_directory_entry_lba; + + while (cluster_lba < top_cluster_lba) : (cluster_lba += 1) { + const fat_directory_entries = try cache.disk.readTypedSectors(DirectoryEntry.Sector, cluster_lba); + + for (fat_directory_entries, 0..) |*entry, entry_index| { + if (entry.is_free()) { + const free_entries_in_sector = fat_directory_entries.len - entry_index; + assert(entry_count <= free_entries_in_sector); + return .{ + .cluster_lba = cluster_lba, + .first_entry_index = entry_index, + }; + } + } + } + + return ReserveDirectoryEntries.Error.no_free_space; + } + + const ReserveDirectoryEntries = extern struct { + cluster_lba: u64, + first_entry_index: usize, + + const Error = error{ + no_free_space, + }; + }; + + pub const GetError = error{ + not_found, + entry_already_exist, + }; + + fn getDirectoryEntryCluster(cache: Cache, dir: []const u8) !u32 { + if (lib.equal(u8, dir, "/")) { + return cache.getRootCluster(); + } else { + const containing_dir_entry = try cache.getDirectoryEntry(dir, null); + return containing_dir_entry.directory_entry.getFirstCluster(); + } + } + + pub fn makeNewDirectory(cache: Cache, absolute_path: []const u8, allocator: ?*lib.Allocator, copy_cache: ?FAT32.Cache, miliseconds: u64) !void { + const copy_entry: ?*DirectoryEntry = if (copy_cache) |my_copy_cache| (try my_copy_cache.getDirectoryEntry(absolute_path, null)).directory_entry else null; + const last_slash_index = lib.lastIndexOf(u8, absolute_path, "/") orelse @panic("there must be a slash"); + const containing_dir = absolute_path[0..if (last_slash_index == 0) 1 else last_slash_index]; + const containing_dir_cluster = try cache.getDirectoryEntryCluster(containing_dir); + const content_cluster = try cache.allocateNewDirectory(containing_dir_cluster, allocator, copy_cache); + const last_element = absolute_path[last_slash_index + 1 ..]; + try cache.addEntry(.{ .name = last_element, .is_dir = true, .content_cluster = content_cluster, .containing_cluster = containing_dir_cluster }, allocator, copy_entry, miliseconds); + } + + pub fn makeNewFile(cache: Cache, file_path: []const u8, file_content: []const u8, allocator: ?*lib.Allocator, copy_cache: ?FAT32.Cache, milliseconds: u64) !void { + const copy_entry: ?*DirectoryEntry = if (copy_cache) |my_copy_cache| (try my_copy_cache.getDirectoryEntry(file_path, null)).directory_entry else null; + const last_slash_index = lib.lastIndexOf(u8, file_path, "/") orelse @panic("there must be a slash"); + const containing_dir = file_path[0..if (last_slash_index == 0) 1 else last_slash_index]; + const containing_dir_cluster = try cache.getDirectoryEntryCluster(containing_dir); + const content_cluster = try cache.allocateNewFile(file_content, allocator); + const file_name = file_path[last_slash_index + 1 ..]; + try cache.addEntry(.{ .name = file_name, .size = @as(u32, @intCast(file_content.len)), .is_dir = false, .content_cluster = content_cluster, .containing_cluster = containing_dir_cluster }, allocator, copy_entry, milliseconds); + } + + fn allocateNewFile(cache: Cache, file_content: []const u8, maybe_allocator: ?*lib.Allocator) !u32 { + assert(file_content.len > 0); + const cluster_byte_count = cache.getClusterSectorCount() * cache.disk.sector_size; + const aligned_file_size = lib.alignForward(usize, file_content.len, cluster_byte_count); + const cluster_count = @divExact(aligned_file_size, cluster_byte_count); + // log.debug("Need to allocate {} clusters for file", .{cluster_count}); + const allocator = maybe_allocator orelse @panic("We need an allocator"); + const clusters = blk: { + const alloc_result = try allocator.allocateBytes(@sizeOf(u32) * cluster_count, @alignOf(u32)); + break :blk @as([*]u32, @ptrFromInt(alloc_result.address))[0..cluster_count]; + }; + try cache.allocateClusters(clusters, allocator); + + for (clusters, 0..) |cluster, cluster_index| { + const cluster_byte_offset = cluster_byte_count * cluster_index; + const slice_start = cluster_byte_offset; + const slice_end = cluster_byte_offset + cluster_byte_count; + const slice = file_content[slice_start..if (slice_end > file_content.len) file_content.len else slice_end]; + const lba = cache.clusterToSector(cluster); + try cache.disk.writeSlice(u8, slice, lba, true); + } + + return clusters[0]; + } + + const Size = struct { + len: u16, + size: u16, + }; + + fn translateToUnicode(name: []const u8, buffer: []u16) !Size { + // Using always UTF8 + const len = try lib.unicode.utf8ToUtf16Le(buffer, name); + var size = len; + if (size % character_count_per_long_entry != 0) { + buffer[size] = 0; + size += 1; + const remainder = size % character_count_per_long_entry; + if (remainder != 0) { + const characters_to_fill = character_count_per_long_entry - remainder; + + for (buffer[size .. size + characters_to_fill]) |*wide_char| { + wide_char.* = lib.maxInt(u16); + } + + size += characters_to_fill; + } + } + + return .{ .len = @as(u16, @intCast(len)), .size = @as(u16, @intCast(size)) }; + } + + const BadChar = error{ + bad_value, + last_character_space, + }; + + fn checkBadCharacters(string: []u16) !void { + for (string) |wchar| { + if (wchar < 0x20 or wchar == '*' or wchar == '?' or wchar == '<' or wchar == '>' or wchar == '|' or wchar == '"' or wchar == ':' or wchar == '/' or wchar == '\\') return BadChar.bad_value; + } + + if (string[string.len - 1] == ' ') return BadChar.last_character_space; + } + + fn isSkipCharacter(wchar: u16) bool { + return wchar == '.' or wchar == ' '; + } + + fn isReplaceCharacter(wchar: u16) bool { + return wchar == '[' or wchar == ']' or wchar == ';' or wchar == ',' or wchar == '+' or wchar == '='; + } + + const ShortNameInfo = packed struct(u8) { + len: u5 = 0, + lower: bool = true, + upper: bool = true, + valid: bool = true, + }; + + fn toShortNameCharacter(nls: *const NLS.Table, wchar: u16, char_buffer: []u8) !ShortNameInfo { + var is_lower = true; + var is_upper = true; + var is_valid = true; + + if (isSkipCharacter(wchar)) @panic("short names must not contain skip characters"); + if (isReplaceCharacter(wchar)) @panic("short names must not contain replace characters"); + + try nls.unicode_to_character(wchar, char_buffer); + + // TODO: + const len = 1; + if (len == 0) { + @panic("nls: character length 0"); + } else if (len == 1) { + const previous = char_buffer[0]; + + if (previous >= 0x7f) @panic("nls: character value is too high"); + + char_buffer[0] = nls.to_upper(previous); + if (lib.isAlphabetic(char_buffer[0])) { + if (char_buffer[0] == previous) { + is_lower = false; + } else { + is_upper = false; + } + } + } else @panic("nls: unexpected length"); + + return ShortNameInfo{ + .len = @as(u5, @intCast(len)), + .lower = is_lower, + .upper = is_upper, + .valid = is_valid, + }; + } + + const ShortNameResult = extern struct { + name: [short_name_len]u8, + case: NameCase, + }; + + fn createShortName(cache: Cache, nls: *const NLS.Table, long_name: []u16, cluster: u32, short_name_result: *ShortNameResult, allocator: ?*lib.Allocator) !bool { + var is_short_name = true; + const end = lib.ptrAdd(u16, &long_name[0], long_name.len); + var extension_start: ?*u16 = end; + var size: usize = 0; + + while (true) { + extension_start = lib.maybePtrSub(u16, extension_start, 1); + if (@intFromPtr(extension_start) < @intFromPtr(&long_name[0])) break; + + if (extension_start.?.* == '.') { + if (extension_start == lib.ptrSub(u16, end, 1)) { + size = long_name.len; + extension_start = null; + } + + break; + } + } + + if (extension_start == lib.ptrSub(u16, &long_name[0], 1)) { + size = long_name.len; + extension_start = null; + } else if (extension_start) |ext_start| { + const extension_start_index = @divExact(@intFromPtr(ext_start) - @intFromPtr(&long_name[0]), @sizeOf(u16)); + const index = blk: { + const slice = long_name[0..extension_start_index]; + + for (slice, 0..) |wchar, index| { + if (!isSkipCharacter(wchar)) break :blk index; + } + + break :blk slice.len; + }; + + if (index != extension_start_index) { + size = extension_start_index; + extension_start = lib.maybePtrAdd(u16, extension_start, 1); + } else { + size = long_name.len; + extension_start = null; + } + } + + var numtail_base_len: usize = 6; + var numtail2_base_len: usize = 2; + + var char_buffer: [NLS.max_charset_size]u8 = undefined; + var base: [9]u8 = undefined; + var long_name_index: usize = 0; + var base_len: usize = 0; + var pointer_it: usize = 0; + var base_info = ShortNameInfo{}; + var extension_info = ShortNameInfo{}; + + while (long_name_index < size) : (long_name_index += 1) { + const wchar = long_name[long_name_index]; + // TODO: chl + // TODO: shortname_info + base_info = try toShortNameCharacter(nls, wchar, &char_buffer); + + const chl = 1; + if (chl == 0) continue; + + if (base_len < 2 and (base_len + chl) > 2) { + numtail2_base_len = base_len; + } + + if (base_len < 6 and (base_len + chl) > 6) { + numtail_base_len = base_len; + } + + var char_index: usize = 0; + while (char_index < chl) : ({ + char_index += 1; + }) { + const char = char_buffer[char_index]; + base[pointer_it] = char; + pointer_it += 1; + base_len += 1; + if (base_len >= 8) break; + } + + if (base_len >= 8) { + if ((char_index < chl - 1) or (long_name_index + 1) < size) { + is_short_name = false; + } + break; + } + } + + if (base_len == 0) @panic("fat32: base length is 0"); + + var extension_len: usize = 0; + var extension: [4]u8 = undefined; + if (extension_start) |ext_start| { + const extension_start_index = @divExact(@intFromPtr(ext_start) - @intFromPtr(&long_name[0]), @sizeOf(u16)); + const extension_slice = long_name[extension_start_index..]; + var extension_index: usize = 0; + for (extension_slice, 0..) |extension_u16, extension_pointer_index| { + extension_info = toShortNameCharacter(nls, extension_u16, &char_buffer) catch continue; + + if (extension_len + extension_info.len > 3) { + is_short_name = false; + break; + } + + for (char_buffer[0..extension_info.len]) |ch| { + extension[extension_index] = ch; + extension_index += 1; + extension_len += 1; + } + + if (extension_len >= 3) { + if (extension_pointer_index + extension_start_index + 1 != long_name.len) { + is_short_name = false; + } + + break; + } + } + } + + extension[extension_len] = 0; + base[base_len] = 0; + + if (base[0] == 0xe5) base[0] = 0x05; + + short_name_result.* = ShortNameResult{ + .name = blk: { + var name = [1]u8{' '} ** short_name_len; + @memcpy(name[0..base_len], base[0..base_len]); + @memcpy(name[max_base_len .. max_base_len + extension_len], extension[0..extension_len]); + break :blk name; + }, + .case = .{ .base = .upper, .extension = .upper }, + }; + + if (is_short_name and base_info.valid and extension_info.valid) { + if (try cache.exists(&short_name_result.name, cluster, allocator)) @panic("fat32: entry with such name already exists"); + const result = switch (cache.name_configuration.create) { + .windows_95 => base_info.upper and extension_info.upper, + .windows_nt => @panic("fat32: unsupported name configuration"), + }; + return result; + } + + @panic("fat32: cannot create shortname"); + } + + pub fn scan(cache: Cache, name: []const u8, cluster: u32, allocator: ?*lib.Allocator) !?*DirectoryEntry { + var iterator = DirectoryEntryIterator(DirectoryEntry).init(cluster); + + while (try iterator.next(cache, allocator)) |entry| { + if (lib.equal(u8, &entry.name, name)) { + return entry; + } + } + + return null; + } + + pub fn exists(cache: Cache, name: []const u8, cluster: u32, allocator: ?*lib.Allocator) !bool { + return (try cache.scan(name, cluster, allocator)) != null; + } + + const GenericEntry = struct { + long_name_entries: []LongNameEntry = &.{}, + normal_entry: DirectoryEntry, + + pub fn isExtended(entry: GenericEntry) bool { + return entry.long_name_entries.len != 0; + } + + pub fn getSlots(entry: GenericEntry) usize { + return entry.long_name_entries.len + 1; + } + }; + + pub fn buildSlots(cache: Cache, entry_setup: EntrySetup, maybe_allocator: ?*lib.Allocator, copy_entry: ?*DirectoryEntry) !void { + var long_name_array = [1]u16{0} ** (long_name_max_characters + 2); + const size = try translateToUnicode(entry_setup.name, &long_name_array); + const long_name = long_name_array[0..size.len]; + try checkBadCharacters(long_name); + + var short_name_result: ShortNameResult = undefined; + const can_get_away_with_short_name = try cache.createShortName(&NLS.ascii.table, long_name, entry_setup.content_cluster, &short_name_result, maybe_allocator); + // TODO: timestamp + var entry = GenericEntry{ + .normal_entry = DirectoryEntry{ + .name = short_name_result.name, + .attributes = .{ + .directory = entry_setup.is_dir, + .archive = !entry_setup.is_dir, + }, + .case = short_name_result.case, + .creation_time_tenth = if (copy_entry) |e| e.creation_time_tenth else 0, + .creation_time = if (copy_entry) |e| e.creation_time else .{ + .seconds_2_factor = 0, + .minutes = 0, + .hours = 0, + }, + .creation_date = if (copy_entry) |e| e.creation_date else .{ + .day = 0, + .month = 0, + .year = 0, + }, + .last_access_date = if (copy_entry) |e| e.last_access_date else .{ + .day = 0, + .month = 0, + .year = 0, + }, + .first_cluster_high = @as(u16, @truncate(entry_setup.content_cluster >> 16)), + .last_write_time = if (copy_entry) |e| e.last_write_time else .{ + .seconds_2_factor = 0, + .minutes = 0, + .hours = 0, + }, + .last_write_date = if (copy_entry) |e| e.last_write_date else .{ + .day = 0, + .month = 0, + .year = 0, + }, + .first_cluster_low = @as(u16, @truncate(entry_setup.content_cluster)), + .file_size = entry_setup.size, + }, + }; + + if (!can_get_away_with_short_name) { + const checksum = shortNameCheckSum(&short_name_result.name); + + const long_slot_count = @as(u5, @intCast(size.size / character_count_per_long_entry)); + entry.long_name_entries = blk: { + const allocator = maybe_allocator orelse @panic("fat32: allocator not provided"); + const alloc_result = try allocator.allocateBytes(@as(usize, @intCast(@sizeOf(LongNameEntry))) * long_slot_count, @alignOf(LongNameEntry)); + break :blk @as([*]LongNameEntry, @ptrFromInt(alloc_result.address))[0..long_slot_count]; + }; + var reverse_index = long_slot_count; + + for (entry.long_name_entries) |*long_name_entry| { + const offset = (reverse_index - 1) * character_count_per_long_entry; + long_name_entry.* = .{ + .sequence_number = .{ + .number = reverse_index, + .last_logical = reverse_index == long_slot_count, + }, + .chars_0_4 = long_name_array[offset .. offset + 6][0..5].*, + .attributes = .{ + .read_only = true, + .hidden = true, + .system = true, + .volume_id = true, + }, + .checksum = checksum, + .chars_5_10 = long_name_array[offset + 5 .. offset + 11][0..6].*, + .first_cluster = 0, + .chars_11_12 = long_name_array[offset + 11 .. offset + 13][0..2].*, + }; + } + } + + const total_slots = entry.getSlots(); + var free_slots: usize = 0; + var entry_iterator = DirectoryEntryIterator(DirectoryEntry).init(entry_setup.containing_cluster); + var current_cluster: u32 = 0; + + while (try entry_iterator.next(cache, maybe_allocator)) |cluster_entry| { + if (cluster_entry.isFree()) { + if (free_slots == 0) current_cluster = @as(u32, @intCast(entry_iterator.cluster)); + free_slots += 1; + + if (free_slots == total_slots) { + const last_current_cluster = @as(u32, @intCast(entry_iterator.cluster)); + assert(last_current_cluster == current_cluster); + const element_offset = @divExact(@intFromPtr(cluster_entry) - @intFromPtr(&entry_iterator.cluster_entries[0]), @sizeOf(DirectoryEntry)); + const entry_start_index = element_offset - (free_slots - 1); + + var entry_index = entry_start_index; + for (entry.long_name_entries) |*long_name_entry| { + entry_iterator.cluster_entries[entry_index] = @as(DirectoryEntry, @bitCast(long_name_entry.*)); + entry_index += 1; + } + + entry_iterator.cluster_entries[entry_index] = entry.normal_entry; + + try cache.disk.writeSlice(DirectoryEntry, entry_iterator.cluster_entries, entry_iterator.getCurrentLBA(cache), false); + + return; + } + } else { + free_slots = 0; + } + } + + @panic("fat32: cannot build slots"); + } + + const EntrySetup = struct { + name: []const u8, + size: u32 = 0, + content_cluster: u32, + containing_cluster: u32, + is_dir: bool, + }; + + pub fn addEntry(cache: Cache, entry_setup: EntrySetup, maybe_allocator: ?*lib.Allocator, copy_entry: ?*DirectoryEntry, miliseconds: u64) !void { + _ = miliseconds; + + // TODO: + if (entry_setup.name[entry_setup.name.len - 1] == '.') @panic("todo: unexpected trailing dot"); + + try cache.buildSlots(entry_setup, maybe_allocator, copy_entry); + } + + pub fn shortNameCheckSum(name: []const u8) u8 { + var result = name[0]; + + result = (result << 7) + (result >> 1) +% name[1]; + result = (result << 7) + (result >> 1) +% name[2]; + result = (result << 7) + (result >> 1) +% name[3]; + result = (result << 7) + (result >> 1) +% name[4]; + result = (result << 7) + (result >> 1) +% name[5]; + result = (result << 7) + (result >> 1) +% name[6]; + result = (result << 7) + (result >> 1) +% name[7]; + result = (result << 7) + (result >> 1) +% name[8]; + result = (result << 7) + (result >> 1) +% name[9]; + result = (result << 7) + (result >> 1) +% name[10]; + + return result; + } + + pub fn allocateNewDirectory(cache: Cache, containing_cluster: u32, allocator: ?*lib.Allocator, copy_cache: ?FAT32.Cache) !u32 { + var clusters = [1]u32{0}; + try cache.allocateClusters(&clusters, allocator); + const cluster = clusters[0]; + const lba = cache.clusterToSector(cluster); + const fat_directory_entries = try cache.disk.readTypedSectors(FAT32.DirectoryEntry.Sector, lba, allocator, .{}); + + var copy_entry: ?*FAT32.DirectoryEntry = null; + if (copy_cache) |cp_cache| { + const entries = try cp_cache.disk.readTypedSectors(FAT32.DirectoryEntry.Sector, cp_cache.clusterToSector(cluster), allocator, .{}); + copy_entry = &entries[0]; + } + const attributes = Attributes{ + .read_only = false, + .hidden = false, + .system = false, + .volume_id = false, + .directory = true, + .archive = false, + }; + + const date = .{ + .day = 0, + .month = 0, + .year = 0, + }; + const time = .{ + .seconds_2_factor = 0, + .minutes = 0, + .hours = 0, + }; + fat_directory_entries[0] = FAT32.DirectoryEntry{ + .name = dot_entry_name, + .attributes = attributes, + .case = .{}, + .creation_time_tenth = if (copy_entry) |ce| ce.creation_time_tenth else 0, + .creation_time = if (copy_entry) |ce| ce.creation_time else time, + .creation_date = if (copy_entry) |ce| ce.creation_date else date, + .first_cluster_high = @as(u16, @truncate(cluster >> 16)), + .first_cluster_low = @as(u16, @truncate(cluster)), + .last_access_date = if (copy_entry) |ce| ce.last_access_date else date, + .last_write_time = if (copy_entry) |ce| ce.last_write_time else time, + .last_write_date = if (copy_entry) |ce| ce.last_write_date else date, + .file_size = 0, + }; + // Copy the values and only modify the necessary ones + fat_directory_entries[1] = fat_directory_entries[0]; + fat_directory_entries[1].name = dot_dot_entry_name; + // TODO: Fix this + fat_directory_entries[1].setFirstCluster(if (containing_cluster == cache.getRootCluster()) 0 else containing_cluster); + // if (copy_entry) |cp_entry| { + // const copy_cluster = cp_entry.get_first_cluster(); + // const dot_entry_cluster = fat_directory_entries[0].get_first_cluster(); + // const dot_dot_entry_cluster = fat_directory_entries[1].get_first_cluster(); + // } + + // TODO: zero initialize the unused part of the cluster + try cache.disk.writeTypedSectors(FAT32.DirectoryEntry.Sector, fat_directory_entries, lba, false); + + return cluster; + } + + pub inline fn clusterToSector(cache: Cache, cluster: u32) u64 { + return (@as(u64, cluster) - cache.getRootCluster()) * cache.getClusterSectorCount() + cache.getDataLBA(); + } + + pub fn registerCluster(cache: Cache, cluster: u32, entry: Entry, allocator: ?*lib.Allocator) !void { + const fat_lba = cache.getFATLBA(); + const fat_entry_count = cache.mbr.bpb.dos3_31.dos2_0.fat_count; + const fat_entry_sector_count = cache.mbr.bpb.fat_sector_count_32; + + if (entry.isAllocating()) { + cache.fs_info.last_allocated_cluster = cluster; + cache.fs_info.free_cluster_count -= 1; + } + + // Actually allocate FAT entry + + var fat_index: u8 = 0; + + const fat_entry_sector_index = cluster % FAT32.Entry.per_sector; + + const cluster_offset = cluster * @sizeOf(u32) / cache.disk.sector_size; + while (fat_index < fat_entry_count) : (fat_index += 1) { + const fat_entry_lba = fat_lba + (fat_index * fat_entry_sector_count) + cluster_offset; + const fat_entry_sector = try cache.disk.readTypedSectors(FAT32.Entry.Sector, fat_entry_lba, allocator, .{}); + fat_entry_sector[fat_entry_sector_index] = entry; + try cache.disk.writeTypedSectors(FAT32.Entry.Sector, fat_entry_sector, fat_entry_lba, false); + } + } + + pub fn allocateClusters(cache: Cache, clusters: []u32, maybe_allocator: ?*lib.Allocator) !void { + var fat_entry_iterator = try FATEntryIterator.init(cache, maybe_allocator); + var cluster_index: usize = 0; + + var previous_cluster: ?u32 = null; + + while (try fat_entry_iterator.next(cache, maybe_allocator)) |cluster| { + const entry = &fat_entry_iterator.entries[cluster % Entry.per_sector]; + if (entry.isFree()) { + if (previous_cluster) |pc| { + if (pc != cluster - 1) { + // log.debug("PC: 0x{x}. CC: 0x{x}", .{ pc, cluster }); + @panic("allocateClusters: unreachable"); + } + } + const should_return = cluster_index == clusters.len - 1; + try cache.registerCluster(cluster, if (should_return) Entry.allocated_and_eof else Entry{ + .next_cluster = @as(u28, @intCast(cluster + 1)), + }, maybe_allocator); + clusters[cluster_index] = cluster; + cluster_index += 1; + + if (should_return) { + // const first_cluster = @intCast(u32, cluster - clusters.len + 1); + // log.debug("First cluster: 0x{x}. Last cluster: 0x{x}", .{ first_cluster, cluster }); + // log.debug("Allocated cluster range: {}-{}. LBA range: 0x{x}-0x{x}", .{ first_cluster, cluster, cache.clusterToSector(first_cluster), cache.clusterToSector(cluster) }); + return; + } + + previous_cluster = cluster; + } else if (cluster_index > 0) { + @panic("cluster index unreachable"); + } + } + + @panic("fat32: allocateClusters"); + } + + pub fn getDirectoryEntry(cache: Cache, absolute_path: []const u8, copy_cache: ?Cache) !EntryResult(DirectoryEntry) { + const fat_lba = cache.partition_range.first_lba + cache.mbr.bpb.dos3_31.dos2_0.reserved_sector_count; + const root_cluster = cache.mbr.bpb.root_directory_cluster_offset; + const data_lba = fat_lba + (cache.mbr.bpb.fat_sector_count_32 * cache.mbr.bpb.dos3_31.dos2_0.fat_count); + + const root_cluster_sector = data_lba; + var upper_cluster = root_cluster; + var dir_tokenizer = lib.DirectoryTokenizer.init(absolute_path); + var directories: usize = 0; + + const first_dir = dir_tokenizer.next() orelse @panic("fat32: there must be at least one directory in the path"); + assert(lib.equal(u8, first_dir, "/")); + + entry_loop: while (dir_tokenizer.next()) |entry_name| : (directories += 1) { + const is_last = dir_tokenizer.is_last(); + + const copy_entry: ?*FAT32.DirectoryEntry = blk: { + if (copy_cache) |cc| { + const name = absolute_path[0..dir_tokenizer.index]; + const entry_result = try cc.getDirectoryEntry(name, null); + break :blk entry_result.directory_entry; + } else break :blk null; + }; + _ = copy_entry; + + const normalized_name = packString(entry_name, .{ + .len = short_name_len, + .fill_with = ' ', + .upper = true, + }); + + while (true) : (upper_cluster += 1) { + const cluster_sector_offset = root_cluster_sector + cache.getClusterSectorCount() * (upper_cluster - root_cluster); + const directory_entries_in_cluster = try cache.disk.readTypedSectors(DirectoryEntry.Sector, cluster_sector_offset, cache.allocator, .{}); + + var entry_index: usize = 0; + while (entry_index < directory_entries_in_cluster.len) : ({ + entry_index += 1; + }) { + const directory_entry = &directory_entries_in_cluster[entry_index]; + const is_empty = directory_entry.name[0] == 0; + const is_unused = directory_entry.name[0] == 0xe5; + const is_long_name = directory_entry.attributes.hasLongName(); + + // At this point all entries in the given directory have been checked, so it's safe to say the directory doesn't contain the wanted entry + if (is_empty) { + return GetError.not_found; + } else { + if (is_unused) { + @panic("fat32: unused entry found"); + } else if (is_long_name) { + const long_name_entry = @as(*FAT32.LongNameEntry, @ptrCast(directory_entry)); + const original_starting_index = entry_index; + + if (long_name_entry.isLast()) { + entry_index += 1; + assert(entry_index < directory_entries_in_cluster.len); + const long_name_u16 = long_name_entry.getCharacters(); + var arr: [long_name_u16.len]u8 = [1]u8{0} ** long_name_u16.len; + const long_name_u8 = blk: { + for (long_name_u16, 0..) |u16_ch, index| { + if (u16_ch == 0) { + break :blk arr[0..index]; + } else if (u16_ch <= lib.maxInt(u8)) { + arr[index] = @as(u8, @intCast(u16_ch)); + } else { + @panic("fat32: u16 unreachable"); + } + } + + @panic("long_name_u8 unreachable"); + }; + + // TODO: compare long name entry + if (lib.equal(u8, long_name_u8, entry_name)) { + const normal_entry = &directory_entries_in_cluster[entry_index]; + if (is_last) { + return .{ .cluster = upper_cluster, .entry_starting_index = @as(u32, @intCast(original_starting_index)), .directory_entry = normal_entry }; + } else { + upper_cluster = normal_entry.getFirstCluster(); + continue :entry_loop; + } + } + } else { + @panic("fat32: not last entry"); + } + } else { + if (lib.equal(u8, &directory_entry.name, &normalized_name)) { + if (is_last) { + return .{ .cluster = upper_cluster, .entry_starting_index = @as(u32, @intCast(entry_index)), .directory_entry = directory_entry }; + } else { + upper_cluster = directory_entry.getFirstCluster(); + continue :entry_loop; + } + } + } + } + } + + return GetError.not_found; + } + } + + @panic("fat32: unable to get directory entry"); + } + + pub inline fn getFATLBA(cache: Cache) u64 { + const fat_lba = cache.partition_range.first_lba + cache.mbr.bpb.dos3_31.dos2_0.reserved_sector_count; + return fat_lba; + } + + pub inline fn getDataLBA(cache: Cache) u64 { + const data_lba = cache.getFATLBA() + (cache.mbr.bpb.fat_sector_count_32 * cache.mbr.bpb.dos3_31.dos2_0.fat_count); + return data_lba; + } + + pub inline fn getRootCluster(cache: Cache) u32 { + const root_cluster = cache.mbr.bpb.root_directory_cluster_offset; + return root_cluster; + } + + pub inline fn getClusterSectorCount(cache: Cache) u32 { + return cache.mbr.bpb.dos3_31.dos2_0.cluster_sector_count; + } +}; + +const PackStringOptions = packed struct(u64) { + fill_with: u8, + len: u8, + upper: bool, + reserved: u47 = 0, +}; + +pub inline fn packString(name: []const u8, comptime options: PackStringOptions) [options.len]u8 { + var result = [1]u8{options.fill_with} ** options.len; + if (name.len > 0) { + if (options.upper) { + _ = lib.upperString(&result, name); + } else { + @memcpy(&result, name); + } + } + + return result; +} + +const character_count_per_long_entry = 13; + +fn EntryResult(comptime EntryType: type) type { + return extern struct { + entry_starting_index: usize, + directory_entry: *EntryType, + cluster: u32, + }; +} + +// Sadly we have to wrap shell commands into scripts because of shell redirection usages + +const FATEntryIterator = struct { + entries: []FAT32.Entry = &.{}, + cluster: u32, + + fn init(cache: Cache, allocator: ?*lib.Allocator) !FATEntryIterator { + const cluster = cache.fs_info.last_allocated_cluster + 1; + assert(cache.disk.sector_size == @sizeOf(FAT32.Entry.Sector)); + const lba_offset = cache.getFATLBA() + (cluster / FAT32.Entry.per_sector); + + return .{ + .entries = try cache.disk.readTypedSectors(FAT32.Entry.Sector, lba_offset, allocator, .{}), + .cluster = cluster, + }; + } + + fn next(iterator: *FATEntryIterator, cache: Cache, allocator: ?*lib.Allocator) !?u32 { + var cluster_count: usize = starting_cluster; + // TODO: replace with proper variable + const max_clusters = 100000; + if (cache.disk.sector_size != @sizeOf(FAT32.Entry.Sector)) @panic("Unexpected disk sector size"); + + while (cluster_count < max_clusters) { + if (cluster_count >= max_clusters) cluster_count = starting_cluster; + + if (iterator.cluster != 0 and iterator.cluster % iterator.entries.len == 0) { + const lba_offset = cache.getFATLBA() + (iterator.cluster / FAT32.Entry.per_sector); + iterator.entries = try cache.disk.readTypedSectors(FAT32.Entry.Sector, lba_offset, allocator, .{}); + } + + const result = iterator.cluster; + iterator.cluster += 1; + return result; + } + + @panic("fat32: entry iterator unreachable"); + } +}; + +fn DirectoryEntryIterator(comptime EntryType: type) type { + assert(EntryType == DirectoryEntry or EntryType == LongNameEntry); + + return struct { + cluster_entries: []EntryType = &.{}, + cluster_it: u32 = 0, + cluster: u32, + cluster_fetched: bool = false, + + const Iterator = @This(); + + pub fn init(cluster: u32) Iterator { + return Iterator{ + .cluster = cluster, + }; + } + + pub fn getCurrentLBA(iterator: *Iterator, cache: Cache) u64 { + const cluster_lba = cache.clusterToSector(iterator.cluster); + return cluster_lba; + } + + pub fn next(iterator: *Iterator, cache: Cache, allocator: ?*lib.Allocator) !?*EntryType { + if (iterator.cluster_fetched) iterator.cluster_it += 1; + + const cluster_sector_count = cache.getClusterSectorCount(); + const cluster_entry_count = @divExact(cluster_sector_count * cache.disk.sector_size, @sizeOf(EntryType)); + assert(iterator.cluster_it <= cluster_entry_count); + if (iterator.cluster_it == cluster_entry_count) return null; // TODO: Should we early return like this? + + if (!iterator.cluster_fetched or iterator.cluster_it == cluster_entry_count) { + if (iterator.cluster_it == cluster_entry_count) iterator.cluster += 1; + + const cluster_lba = cache.clusterToSector(iterator.cluster); + iterator.cluster_entries = try cache.disk.readSlice(EntryType, cluster_entry_count, cluster_lba, allocator, .{}); + iterator.cluster_it = 0; + iterator.cluster_fetched = true; + } + + return &iterator.cluster_entries[iterator.cluster_it]; + } + }; +} diff --git a/src/lib/graphics.zig b/src/lib/graphics.zig new file mode 100644 index 0000000..545a437 --- /dev/null +++ b/src/lib/graphics.zig @@ -0,0 +1,184 @@ +pub const Driver = @This(); + +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.Graphics); + +pub const Rectangle = @import("graphics/rectangle.zig"); +pub const Rect = Rectangle.Rectangle; + +const Type = enum(u64) { + limine = 0, + virtio = 1, + sdl_software_renderer_prototype = 2, +}; + +const UpdateScreenFunction = fn (graphics: *Driver, drawing_area: DrawingArea, destination: Point) void; + +type: Type, +frontbuffer: Framebuffer, +backbuffer: DrawingArea, +callback_update_screen: *const UpdateScreenFunction, + +pub const DrawingArea = struct { + bytes: [*]u8 = undefined, + width: u32 = 0, + height: u32 = 0, + stride: u32 = 0, +}; + +pub const Point = struct { + x: u32 = 0, + y: u32 = 0, +}; + +pub const Framebuffer = struct { + area: DrawingArea = .{}, + modified_region: Rect = Rectangle.zero(), + + pub fn get_pixel_count(framebuffer: Framebuffer) u32 { + return framebuffer.area.width * framebuffer.area.height; + } + + pub fn get_pointer(framebuffer: Framebuffer) [*]u32 { + return @as([*]u32, @ptrCast(@alignCast(@alignOf(u32), framebuffer.area.bytes))); + } + + pub fn resize(framebuffer: *Framebuffer, allocator: lib.CustomAllocator, width: u32, height: u32) bool { + // TODO: copy old bytes + // TODO: free old bytes + if (width == 0 or height == 0) return false; + + const old_width = framebuffer.area.width; + const old_height = framebuffer.area.height; + + if (width == old_width and height == old_height) return true; + + // TODO: stop hardcoding the 4 + const new_buffer_memory = allocator.allocate_bytes(width * height * 4, 0x1000) catch unreachable; + framebuffer.area = DrawingArea{ + .bytes = @as([*]u8, @ptrFromInt(new_buffer_memory.address)), + .width = width, + .height = height, + .stride = width * 4, + }; + + // Clear it with white to debug it + framebuffer.fill(0); + + return true; + } + + pub fn fill(framebuffer: *Framebuffer, color: u32) void { + assert(@divExact(framebuffer.area.stride, framebuffer.area.width) == @sizeOf(u32)); + + for (@as([*]u32, @ptrCast(@alignCast(@alignOf(u32), framebuffer.area.bytes)))[0..framebuffer.get_pixel_count()]) |*pixel| { + pixel.* = color; + } + } + + pub fn copy(framebuffer: *Framebuffer, source: *Framebuffer, destination_point: Point, source_region: Rect, add_to_modified_region: bool) void { + const destination_region = Rectangle.from_point_and_rectangle(destination_point, source_region); + + const surface_clip = Rectangle.from_area(framebuffer.area); + + if (add_to_modified_region) { + framebuffer.update_modified_region(destination_region); + } + + const source_ptr = @as([*]u32, @ptrCast(@alignCast(@alignOf(u32), source.area.bytes + source.area.stride * Rectangle.top(source_region) + 4 * Rectangle.left(source_region)))); + framebuffer.draw_bitmap(surface_clip, destination_region, source_ptr, source.area.stride, .opaque_mode); + } + + fn update_modified_region(framebuffer: *Framebuffer, destination_region: Rect) void { + framebuffer.modified_region = Rectangle.bounding(destination_region, framebuffer.modified_region); + framebuffer.modified_region = Rectangle.clip(framebuffer.modified_region, Rectangle.from_area(framebuffer.area)).intersection; + } + + pub fn draw(framebuffer: *Framebuffer, source: *Framebuffer, destination_region: Rect, source_offset: Point, alpha: DrawBitmapMode) void { + const surface_clip = Rectangle.from_area(framebuffer.area); + framebuffer.update_modified_region(destination_region); + const source_ptr = @as([*]u32, @ptrCast(@alignCast(@alignOf(u32), source.area.bytes + source.area.stride * source_offset.y + 4 * source_offset.x))); + framebuffer.draw_bitmap(surface_clip, destination_region, source_ptr, source.area.stride, alpha); + } + + pub fn draw_bitmap(framebuffer: *Framebuffer, clip_area: Rect, region: Rect, source_ptr: [*]const u32, asked_source_stride: u32, mode: DrawBitmapMode) void { + const result = Rectangle.clip(region, clip_area); + if (result.clip) { + const bounds = result.intersection; + const source_stride = asked_source_stride / @sizeOf(u32); + const stride = framebuffer.area.stride / @sizeOf(u32); + const line_start_index = Rectangle.top(bounds) * stride + Rectangle.left(bounds); + var line_start = @as([*]u32, @ptrCast(@alignCast(@alignOf(u32), framebuffer.area.bytes))) + line_start_index; + const source_line_start_index = Rectangle.left(bounds) - Rectangle.left(region) + source_stride * (Rectangle.top(bounds) - Rectangle.top(region)); + var source_line_start = source_ptr + source_line_start_index; + + var i: u64 = 0; + const bounds_width = Rectangle.width(bounds); + const bounds_height = Rectangle.height(bounds); + + while (i < bounds_height) : ({ + i += 1; + line_start += stride; + source_line_start += source_stride; + }) { + var destination = line_start; + var source = source_line_start; + + var j = bounds_width; + if (@intFromEnum(mode) == 0xff) { + while (true) { + blend_pixel(&destination[0], source[0]); + destination += 1; + source += 1; + j -= 1; + if (j == 0) break; + } + } else if (@intFromEnum(mode) <= 0xff) { + @panic("todo: mode <= 0xff"); + } else if (mode == .xor) { + @panic("todo: mode xor"); + } else if (mode == .opaque_mode) { + // todo: refactor + while (j > 0) : ({ + destination += 1; + source += 1; + j -= 1; + }) { + destination[0] = 0xff_00_00_00 | source[0]; + } + } + } + } + } +}; + +// TODO: full alpha +fn blend_pixel(destination_pixel: *u32, modified: u32) void { + if (modified & 0xff_00_00_00 == 0xff_00_00_00) { + destination_pixel.* = modified; + return; + } else if (modified & 0xff_00_00_00 == 0) { + return; + } + + const original = destination_pixel.*; + + const m1 = (modified & 0xff_00_00_00) >> 24; + const m2 = 255 - m1; + const a = 0xff_00_00_00; + + const r2 = m2 * (original & 0x00FF00FF); + const g2 = m2 * (original & 0x0000FF00); + const r1 = m1 * (modified & 0x00FF00FF); + const g1 = m1 * (modified & 0x0000FF00); + const result = a | (0x0000FF00 & ((g1 + g2) >> 8)) | (0x00FF00FF & ((r1 + r2) >> 8)); + destination_pixel.* = result; +} + +pub const DrawBitmapMode = enum(u16) { + blend = 0, + xor = 0xfffe, + opaque_mode = 0xffff, + _, +}; diff --git a/src/lib/nls.zig b/src/lib/nls.zig new file mode 100644 index 0000000..bd70eec --- /dev/null +++ b/src/lib/nls.zig @@ -0,0 +1,21 @@ +pub const Error = error{ + name_too_long, + bad_value, +}; + +pub const max_charset_size = 6; + +pub const Table = struct { + character_set: []const u8, + unicode_to_character: *const fn (wchar: u16, char_string: []u8) Error!void, + character_to_unicode: *const fn (char_string: []u8) Error!u16, + character_set_to_lower: []const u8, + character_set_to_upper: []const u8, + + pub fn to_upper(table: *const Table, char: u8) u8 { + const possible_result = table.character_set_to_upper[char]; + return if (possible_result != 0) possible_result else char; + } +}; + +pub const ascii = @import("nls/ascii.zig"); diff --git a/src/lib/nls/ascii.zig b/src/lib/nls/ascii.zig new file mode 100644 index 0000000..c24e3ba --- /dev/null +++ b/src/lib/nls/ascii.zig @@ -0,0 +1,125 @@ +const lib = @import("lib"); +const NLS = lib.NLS; + +const charset_to_unicode: [256]u16 = [128]u16{ + 0x0000, 0x0001, 0x0002, 0x0003, + 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, + 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, + 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, + 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, + 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, + 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, + 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, + 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, + 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, + 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, + 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, + 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, + 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, + 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, + 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, + 0x007c, 0x007d, 0x007e, 0x007f, +} ++ [1]u16{0} ** 128; + +const page00: [256]u8 = [128]u8{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, +} ++ [1]u8{0} ** 128; + +const page_unicode_to_charset = [_]*const [256]u8{ + &page00, +}; + +const charset_to_lower: [256]u8 = [128]u8{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, +} ++ [1]u8{0} ** 128; + +const charset_to_upper: [256]u8 = [128]u8{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, +} ++ [1]u8{0} ** 128; + +fn unicode_to_character(wchar: u16, char_string: []u8) NLS.Error!void { + //const unsigned char *uni2charset; + const wchar_low = @as(u8, @truncate(wchar)); + const wchar_high = @as(u8, @truncate(wchar >> 8)); + + if (char_string.len == 0) return NLS.Error.name_too_long; + + const unicode_to_charset = page_unicode_to_charset[wchar_high]; + const possible_out = unicode_to_charset[wchar_low]; + if (possible_out == 0) return NLS.Error.bad_value; + char_string[0] = possible_out; +} + +fn character_to_unicode(char_string: []const u8) NLS.Error!u16 { + const unicode = charset_to_unicode[char_string[0]]; + if (unicode == 0) return NLS.Error.bad_value; + return unicode; +} + +pub const table = NLS.Table{ + .character_set = "ascii", + .unicode_to_character = unicode_to_character, + .character_to_unicode = character_to_unicode, + .character_set_to_lower = &charset_to_lower, + .character_set_to_upper = &charset_to_upper, +}; diff --git a/src/lib/partition_table.zig b/src/lib/partition_table.zig new file mode 100644 index 0000000..0f5cd34 --- /dev/null +++ b/src/lib/partition_table.zig @@ -0,0 +1,11 @@ +const lib = @import("lib"); + +pub const GPT = @import("partition_table/gpt.zig"); +pub const MBR = @import("partition_table/mbr.zig"); + +test { + _ = GPT; + _ = MBR; +} + +pub const Type = lib.PartitionTableType; diff --git a/src/lib/partition_table/gpt.zig b/src/lib/partition_table/gpt.zig new file mode 100644 index 0000000..c65cc54 --- /dev/null +++ b/src/lib/partition_table/gpt.zig @@ -0,0 +1,416 @@ +const GPT = @This(); + +const lib = @import("lib"); +const assert = lib.assert; +const kb = lib.kb; +const mb = lib.mb; +const gb = lib.gb; +const CRC32 = lib.CRC32; +const Disk = lib.Disk; +const Filesystem = lib.Filesystem; +const FAT32 = Filesystem.FAT32; +const log = lib.log.scoped(.GPT); +const MBR = lib.PartitionTable.MBR; +const GUID = lib.uefi.Guid; +const Allocator = lib.Allocator; + +pub const default_max_partition_count = 128; +pub const min_block_size = lib.default_sector_size; +pub const max_block_size = 0x1000; + +pub const Header = extern struct { + signature: [8]u8 = "EFI PART".*, + revision: [4]u8 = .{ 0, 0, 1, 0 }, + header_size: u32 = @sizeOf(Header), + header_crc32: u32 = 0, + reserved: u32 = 0, + header_lba: u64, + backup_lba: u64, + first_usable_lba: u64, + last_usable_lba: u64, + disk_guid: GUID, + partition_array_lba: u64, + partition_entry_count: u32, + partition_entry_size: u32 = @sizeOf(Partition), + partition_array_crc32: u32, + reserved1: [420]u8 = [1]u8{0} ** 420, + + pub fn updateCrc32(header: *Header) void { + header.header_crc32 = 0; + header.header_crc32 = CRC32.compute(lib.asBytes(header)[0..header.header_size]); + } + + pub fn getPartititonCountInSector(header: *const Header, disk: *const Disk) u32 { + return @divExact(disk.sector_size, header.partition_entry_size); + } + + pub fn format(header: *const Header, comptime _: []const u8, _: lib.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + try lib.format(writer, "GPT header:\n", .{}); + try lib.format(writer, "\tSignature: {s}\n", .{header.signature}); + try lib.format(writer, "\tRevision: {any}\n", .{header.revision}); + try lib.format(writer, "\tHeader size: {}\n", .{header.header_size}); + try lib.format(writer, "\tHeader CRC32: 0x{x}\n", .{header.header_crc32}); + try lib.format(writer, "\tHeader LBA: 0x{x}\n", .{header.header_lba}); + try lib.format(writer, "\tAlternate header LBA: 0x{x}\n", .{header.backup_lba}); + try lib.format(writer, "\tFirst usable LBA: 0x{x}\n", .{header.first_usable_lba}); + try lib.format(writer, "\tLast usable LBA: 0x{x}\n", .{header.last_usable_lba}); + try lib.format(writer, "\tDisk GUID: {}\n", .{header.disk_guid}); + try lib.format(writer, "\tPartition array LBA: 0x{x}\n", .{header.partition_array_lba}); + try lib.format(writer, "\tPartition entry count: {}\n", .{header.partition_entry_count}); + try lib.format(writer, "\tPartition entry size: {}\n", .{header.partition_entry_size}); + try lib.format(writer, "\tPartition array CRC32: 0x{x}\n", .{header.partition_array_crc32}); + } + + pub fn compare(header: *const Header, other: *align(1) const Header) void { + log.debug("{}", .{header}); + log.debug("{}", .{other}); + + if (!lib.equal(u8, &header.signature, &other.signature)) { + log.debug("Signature mismatch: {s}, {s}", .{ header.signature, other.signature }); + } + if (!lib.equal(u8, &header.revision, &other.revision)) { + log.debug("Revision mismatch: {any}, {any}", .{ header.revision, other.revision }); + } + if (header.header_size != other.header_size) { + log.debug("Header size mismatch: {}, {}", .{ header.header_size, other.header_size }); + } + if (header.header_crc32 != other.header_crc32) { + log.debug("Header CRC32 mismatch: {}, {}", .{ header.header_crc32, other.header_crc32 }); + } + if (header.header_lba != other.header_lba) { + log.debug("Header LBA mismatch: {}, {}", .{ header.header_lba, other.header_lba }); + } + if (header.backup_lba != other.backup_lba) { + log.debug("Backup LBA mismatch: {}, {}", .{ header.backup_lba, other.backup_lba }); + } + if (header.first_usable_lba != other.first_usable_lba) { + log.debug("First usable LBA mismatch: {}, {}", .{ header.first_usable_lba, other.first_usable_lba }); + } + if (header.last_usable_lba != other.last_usable_lba) { + log.debug("Last usable LBA mismatch: {}, {}", .{ header.last_usable_lba, other.last_usable_lba }); + } + if (!header.disk_guid.eql(other.disk_guid)) { + log.debug("Disk GUID mismatch: {}, {}", .{ header.disk_guid, other.disk_guid }); + } + if (header.partition_array_lba != other.partition_array_lba) { + log.debug("Partition array LBA mismatch: {}, {}", .{ header.partition_array_lba, other.partition_array_lba }); + } + if (header.partition_entry_count != other.partition_entry_count) { + log.debug("Partition entry count mismatch: {}, {}", .{ header.partition_entry_count, other.partition_entry_count }); + } + if (header.partition_entry_size != other.partition_entry_size) { + log.debug("Partition entry size mismatch: {}, {}", .{ header.partition_entry_size, other.partition_entry_size }); + } + if (header.partition_array_crc32 != other.partition_array_crc32) { + log.debug("Partition array CRC32 mismatch: {}, {}", .{ header.partition_array_crc32, other.partition_array_crc32 }); + } + } + + pub const Cache = extern struct { + mbr: *MBR.Partition, + header: *GPT.Header, + disk: *Disk, + gpt: *GPT.Partition, + + pub fn getFreePartitionSlot(cache: Cache) !*GPT.Partition { + assert(cache.header.partition_entry_size == @sizeOf(GPT.Partition)); + // TODO: undo hack + + return cache.gpt; + + // for (cache.partition_entries[0..cache.header.partition_entry_count]) |*partition_entry| { + // if (partition_entry.first_lba == 0 and partition_entry.last_lba == 0) { + // return partition_entry; + // } + // } + + //@panic("todo: get_free_partition_slot"); + } + + pub fn getPartitionIndex(cache: Cache, partition: *GPT.Partition, partition_entries: []GPT.Partition) u32 { + assert(cache.header.partition_entry_size == @sizeOf(GPT.Partition)); + return @divExact(@as(u32, @intCast(@intFromPtr(partition) - @intFromPtr(partition_entries.ptr))), cache.header.partition_entry_size); + } + + pub fn getPartitionSector(cache: Cache, partition: *GPT.Partition, partition_entries: []GPT.Partition) u32 { + return getPartitionIndex(cache, partition, partition_entries) / cache.header.getPartititonCountInSector(cache.disk); + } + + pub fn getPartitionEntries(cache: Cache, allocator: ?*lib.Allocator) ![]GPT.Partition { + const partition_entries = try cache.disk.readSlice(GPT.Partition, cache.header.partition_entry_count, cache.header.partition_array_lba, allocator, .{}); + return partition_entries; + } + + pub inline fn updatePartitionEntry(cache: Cache, partition: *GPT.Partition, new_value: GPT.Partition) !void { + if (cache.disk.type != .memory) @panic("Disk is not memory"); + assert(cache.header.partition_entry_size == @sizeOf(GPT.Partition)); + const partition_entries = try cache.getPartitionEntries(null); + const partition_entry_bytes = lib.sliceAsBytes(partition_entries); + partition.* = new_value; + cache.header.partition_array_crc32 = CRC32.compute(partition_entry_bytes); + cache.header.updateCrc32(); + + const backup_gpt_header = try cache.disk.readTypedSectors(GPT.Header, cache.header.backup_lba, null, .{}); + backup_gpt_header.partition_array_crc32 = cache.header.partition_array_crc32; + backup_gpt_header.updateCrc32(); + + const partition_entry_sector_offset = cache.getPartitionSector(partition, partition_entries); + const partition_entry_byte_offset = partition_entry_sector_offset * cache.disk.sector_size; + // Only commit to disk the modified sector + const partition_entry_modified_sector_bytes = partition_entry_bytes[partition_entry_byte_offset .. partition_entry_byte_offset + cache.disk.sector_size]; + try cache.disk.writeSlice(u8, partition_entry_modified_sector_bytes, cache.header.partition_array_lba + partition_entry_sector_offset, false); + // Force write because for memory disk we only hold a pointer to the main partition entry array + try cache.disk.writeSlice(u8, partition_entry_modified_sector_bytes, backup_gpt_header.partition_array_lba + partition_entry_sector_offset, true); + try cache.disk.writeTypedSectors(GPT.Header, cache.header, cache.header.header_lba, false); + try cache.disk.writeTypedSectors(GPT.Header, backup_gpt_header, backup_gpt_header.header_lba, false); + } + + pub fn addPartition(cache: Cache, comptime filesystem: lib.Filesystem.Type, partition_name: []const u16, lba_start: u64, lba_end: u64, gpt_partition: ?*const GPT.Partition) !GPT.Partition.Cache { + // TODO: check if we are not overwriting a partition + // TODO: check filesystem specific stuff + const new_partition_entry = try cache.getFreePartitionSlot(); + try updatePartitionEntry(cache, new_partition_entry, GPT.Partition{ + .partition_type_guid = switch (filesystem) { + .fat32 => efi_guid, + else => @panic("unexpected filesystem"), + }, + .unique_partition_guid = if (gpt_partition) |gpt_part| gpt_part.unique_partition_guid else getRandomGuid(), + .first_lba = lba_start, + .last_lba = lba_end, + .attributes = .{}, + .partition_name = blk: { + var name = [1]u16{0} ** 36; + @memcpy(name[0..partition_name.len], partition_name); + break :blk name; + }, + }); + + return .{ + .gpt = cache, + .partition = new_partition_entry, + }; + } + + pub fn load(disk: *Disk, allocator: ?*Allocator) !GPT.Header.Cache { + _ = allocator; + _ = disk; + } + }; + + comptime { + assert(@sizeOf(Header) == lib.default_sector_size); + } + + pub fn get(disk: *Disk) !*GPT.Header { + return try disk.readTypedSectors(GPT.Header, 1); + } + + pub fn getBackup(gpt_header: *GPT.Header, disk: *Disk) !*GPT.Header { + return try disk.readTypedSectors(GPT.Header, gpt_header.backup_lba); + } +}; + +var prng = lib.random.DefaultPrng.init(0); +pub fn getRandomGuid() GUID { + const random_array = blk: { + var arr: [16]u8 = undefined; + const random = prng.random(); + random.bytes(&arr); + break :blk arr; + }; + var guid = GUID{ + .time_low = (@as(u32, random_array[0]) << 24) | (@as(u32, random_array[1]) << 16) | (@as(u32, random_array[2]) << 8) | random_array[3], + .time_mid = (@as(u16, random_array[4]) << 8) | random_array[5], + .time_high_and_version = (@as(u16, random_array[6]) << 8) | random_array[7], + .clock_seq_high_and_reserved = random_array[8], + .clock_seq_low = random_array[9], + .node = .{ random_array[10], random_array[11], random_array[12], random_array[13], random_array[14], random_array[15] }, + }; + + guid.clock_seq_high_and_reserved = (2 << 6) | (guid.clock_seq_high_and_reserved >> 2); + guid.time_high_and_version = (4 << 12) | (guid.time_high_and_version >> 4); + + return guid; +} + +pub const efi_system_partition_guid = GUID{ .time_low = 0xC12A7328, .time_mid = 0xF81F, .time_hi_and_version = 0x11D2, .clock_seq_hi_and_reserved = 0xBA, .clock_seq_low = 0x4B, .node = [_]u8{ 0x00, 0xA0, 0xC9, 0x3E, 0xC9, 0x3B } }; +pub const microsoft_basic_data_partition_guid = GUID{ .time_low = 0xEBD0A0A2, .time_mid = 0xB9E5, .time_hi_and_version = 0x4433, .clock_seq_hi_and_reserved = 0x87, .clock_seq_low = 0xC0, .node = [_]u8{ 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7 } }; + +pub const Partition = extern struct { + partition_type_guid: GUID, + unique_partition_guid: GUID, + first_lba: u64, + last_lba: u64, + attributes: Attributes, + partition_name: [36]u16, + + pub const per_sector = @divExact(lib.default_sector_size, @sizeOf(Partition)); + + pub const Cache = extern struct { + gpt: GPT.Header.Cache, + partition: *GPT.Partition, + + pub fn fromPartitionIndex(disk: *Disk, partition_index: usize, allocator: ?*lib.Allocator) !GPT.Partition.Cache { + const mbr_lba = MBR.default_lba; + const mbr = try disk.readTypedSectors(MBR.Partition, mbr_lba, allocator, .{}); + const primary_gpt_header_lba = mbr_lba + 1; + const gpt_header = try disk.readTypedSectors(GPT.Header, primary_gpt_header_lba, allocator, .{}); + if (gpt_header.partition_entry_count == 0) @panic("No GPT partition entries"); + assert(gpt_header.partition_entry_size == @sizeOf(GPT.Partition)); + // TODO: undo hack + if (partition_index < gpt_header.partition_entry_count) { + if (partition_index != 0) @panic("Unsupported partition index"); + const partition_entries_first_sector = try disk.readSlice(GPT.Partition, GPT.Partition.per_sector, gpt_header.partition_array_lba, allocator, .{}); + const partition_entry = &partition_entries_first_sector[0]; + + return .{ + .gpt = .{ + .mbr = mbr, + .header = gpt_header, + .disk = disk, + .gpt = partition_entry, + }, + .partition = partition_entry, + }; + } + + @panic("todo: fromPartitionIndex"); + } + }; + + pub const Attributes = packed struct(u64) { + required_partition: bool = false, + no_block_io_protocol: bool = false, + legacy_bios_bootable: bool = false, + reserved: u45 = 0, + guid_reserved: u16 = 0, + }; + + pub fn compare(partition: *const Partition, other: *align(1) const Partition) void { + log.debug("{}", .{partition}); + if (partition.first_lba != other.first_lba) { + log.debug("First LBA mismatch: 0x{x}, 0x{x}", .{ partition.first_lba, other.first_lba }); + } + if (partition.last_lba != other.last_lba) { + log.debug("Last LBA mismatch: 0x{x}, 0x{x}", .{ partition.last_lba, other.last_lba }); + } + for (partition.partition_name, 0..) |partition_char, char_index| { + const other_char = other.partition_name[char_index]; + if (partition_char != other_char) { + log.debug("Char is different: {u}(0x{x}), {u}(0x{x})", .{ partition_char, partition_char, other_char, other_char }); + } + } + } + + pub fn format(partition: *const Partition, comptime _: []const u8, _: lib.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + try lib.format(writer, "GPT partition:\n", .{}); + try lib.format(writer, "\tPartition type GUID: {}\n", .{partition.partition_type_guid}); + try lib.format(writer, "\tUnique partition GUID: {}\n", .{partition.unique_partition_guid}); + try lib.format(writer, "\tFirst LBA: 0x{x}\n", .{partition.first_lba}); + try lib.format(writer, "\tLast LBA: 0x{x}\n", .{partition.last_lba}); + try lib.format(writer, "\tAttributes: {}\n", .{partition.attributes}); + try lib.format(writer, "\tPartition name: {}\n", .{lib.std.unicode.fmtUtf16le(&partition.partition_name)}); + } +}; + +pub fn create(disk: *Disk, copy_gpt_header: ?*const Header) !GPT.Header.Cache { + if (disk.type != .memory) @panic("gpt: creation is only supported for memory disks"); + // 1. Create MBR fake partition + const mbr_lba = MBR.default_lba; + const mbr = try disk.readTypedSectors(MBR.Partition, mbr_lba, null, .{}); + const first_lba = mbr_lba + 1; + const primary_header_lba = first_lba; + mbr.partitions[0] = MBR.LegacyPartition{ + .boot_indicator = 0, + .starting_chs = lib.default_sector_size, + .os_type = 0xee, + .ending_chs = 0xff_ff_ff, + .first_lba = first_lba, + .size_in_lba = @as(u32, @intCast(@divExact(disk.disk_size, disk.sector_size) - 1)), + }; + mbr.signature = .{ 0x55, 0xaa }; + try disk.writeTypedSectors(MBR.Partition, mbr, mbr_lba, false); + + // 2. Write GPT header + const partition_count = default_max_partition_count; + const partition_array_sector_count = @divExact(@sizeOf(Partition) * partition_count, disk.sector_size); + // TODO: properly compute header LBA + const gpt_header = try disk.readTypedSectors(GPT.Header, first_lba, null, .{}); + const secondary_header_lba = mbr.partitions[0].size_in_lba; + const partition_array_lba_start = first_lba + 1; + const partition_entries = try disk.readSlice(GPT.Partition, partition_count, partition_array_lba_start, null, .{}); + gpt_header.* = GPT.Header{ + .signature = "EFI PART".*, + .revision = .{ 0, 0, 1, 0 }, + .header_size = @offsetOf(GPT.Header, "reserved1"), + .header_crc32 = 0, // TODO + .header_lba = primary_header_lba, + .backup_lba = secondary_header_lba, + .first_usable_lba = partition_array_lba_start + partition_array_sector_count, + .last_usable_lba = secondary_header_lba - primary_header_lba - partition_array_sector_count, + .disk_guid = if (copy_gpt_header) |gpth| gpth.disk_guid else getRandomGuid(), + .partition_array_lba = partition_array_lba_start, + .partition_entry_count = partition_count, + .partition_array_crc32 = CRC32.compute(lib.sliceAsBytes(partition_entries)), + }; + + gpt_header.updateCrc32(); + try disk.writeTypedSectors(GPT.Header, gpt_header, primary_header_lba, false); + + var backup_gpt_header = gpt_header.*; + backup_gpt_header.partition_array_lba = secondary_header_lba - primary_header_lba - partition_array_sector_count + 1; + backup_gpt_header.header_lba = gpt_header.backup_lba; + backup_gpt_header.backup_lba = gpt_header.header_lba; + backup_gpt_header.updateCrc32(); + try disk.writeTypedSectors(GPT.Header, &backup_gpt_header, secondary_header_lba, true); + + return .{ + .mbr = mbr, + .header = gpt_header, + .disk = disk, + .gpt = &partition_entries[0], + }; +} + +const efi_guid = GUID{ + .time_low = 0xC12A7328, + .time_mid = 0xF81F, + .time_high_and_version = 0x11D2, + .clock_seq_high_and_reserved = 0xBA, + .clock_seq_low = 0x4B, + //00A0C93EC93B + .node = .{ 0x00, 0xa0, 0xc9, 0x3e, 0xc9, 0x3b }, +}; + +const limine_disk_guid = GUID{ + .time_low = 0xD2CB8A76, + .time_mid = 0xACB3, + .time_high_and_version = 0x4D4D, + .clock_seq_high_and_reserved = 0x93, + .clock_seq_low = 0x55, + .node = .{ 0xAC, 0xAE, 0xA4, 0x6B, 0x46, 0x92 }, +}; + +const limine_unique_partition_guid = GUID{ + .time_low = 0x26D6E02E, + .time_mid = 0xEED8, + .time_high_and_version = 0x4802, + .clock_seq_high_and_reserved = 0xba, + .clock_seq_low = 0xa2, + .node = .{ 0xE5, 0xAA, 0x43, 0x7F, 0xC2, 0xC5 }, +}; + +const FilesystemCacheTypes = blk: { + var types: [Filesystem.Type.count]type = undefined; + types[@intFromEnum(Filesystem.Type.rise)] = void; + types[@intFromEnum(Filesystem.Type.ext2)] = void; + types[@intFromEnum(Filesystem.Type.fat32)] = FAT32.Cache; + + break :blk types; +}; + +test "gpt size" { + comptime { + assert(@sizeOf(Header) == 0x200); + } +} diff --git a/src/lib/partition_table/mbr.zig b/src/lib/partition_table/mbr.zig new file mode 100644 index 0000000..d97e49e --- /dev/null +++ b/src/lib/partition_table/mbr.zig @@ -0,0 +1,357 @@ +const MBR = @This(); + +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.MBR); +const Disk = lib.Disk.Descriptor; +const GPT = lib.PartitionTable.GPT; +const FAT32 = lib.Filesystem.FAT32; + +pub const default_lba = 0; + +pub const BIOSParameterBlock = extern struct { + pub const DOS2_0 = extern struct { + jmp_code: [3]u8 = .{ 0xeb, 0x58, 0x90 }, + oem_identifier: [8]u8, + sector_size: u16 align(1), + cluster_sector_count: u8, + reserved_sector_count: u16, + fat_count: u8, + root_entry_count: u16 align(1), // only for FAT12 and FAT16 + total_sector_count_16: u16 align(1), + media_descriptor: u8, + fat_sector_count_16: u16, + + comptime { + assert(@sizeOf(@This()) == 24); + } + + fn compare(bpb_2_0: *const DOS2_0, other: *align(1) const DOS2_0) void { + if (!lib.equal(u8, &bpb_2_0.jmp_code, &other.jmp_code)) log.debug("Jump code differs: {any}, {any}", .{ bpb_2_0.jmp_code, other.jmp_code }); + if (!lib.equal(u8, &bpb_2_0.oem_identifier, &other.oem_identifier)) log.debug("OEM identifier differs: {any}, {any}", .{ bpb_2_0.oem_identifier, other.oem_identifier }); + if (bpb_2_0.sector_size != other.sector_size) log.debug("Sector size differs: {}, {}", .{ bpb_2_0.sector_size, other.sector_size }); + if (bpb_2_0.cluster_sector_count != other.cluster_sector_count) log.debug("Cluster sector count differs: {}, {}", .{ bpb_2_0.cluster_sector_count, other.cluster_sector_count }); + if (bpb_2_0.reserved_sector_count != other.reserved_sector_count) log.debug("Reserved sector count differs: {}, {}", .{ bpb_2_0.reserved_sector_count, other.reserved_sector_count }); + if (bpb_2_0.fat_count != other.fat_count) log.debug("FAT count differs: {}, {}", .{ bpb_2_0.fat_count, other.fat_count }); + if (bpb_2_0.root_entry_count != other.root_entry_count) log.debug("Root entry count differs: {}, {}", .{ bpb_2_0.root_entry_count, other.root_entry_count }); + if (bpb_2_0.total_sector_count_16 != other.total_sector_count_16) log.debug("Total sector count(16) differs: {}, {}", .{ bpb_2_0.total_sector_count_16, other.total_sector_count_16 }); + if (bpb_2_0.media_descriptor != other.media_descriptor) log.debug("Media descriptor differs: {}, {}", .{ bpb_2_0.media_descriptor, other.media_descriptor }); + if (bpb_2_0.fat_sector_count_16 != other.fat_sector_count_16) log.debug("FAT sector count (16) differs: {}, {}", .{ bpb_2_0.fat_sector_count_16, other.fat_sector_count_16 }); + } + }; + + pub const DOS3_31 = extern struct { + dos2_0: DOS2_0, + physical_sectors_per_track: u16, + disk_head_count: u16, + hidden_sector_count: u32, + total_sector_count_32: u32, + + fn compare(bpb_3_31: *align(1) const DOS3_31, other: *align(1) const DOS3_31) void { + bpb_3_31.dos2_0.compare(&other.dos2_0); + + if (bpb_3_31.physical_sectors_per_track != other.physical_sectors_per_track) log.debug("Physical sectors per track differs: {}, {}", .{ bpb_3_31.physical_sectors_per_track, other.physical_sectors_per_track }); + if (bpb_3_31.disk_head_count != other.disk_head_count) log.debug("Disk head count differs: {}, {}", .{ bpb_3_31.disk_head_count, other.disk_head_count }); + if (bpb_3_31.hidden_sector_count != other.hidden_sector_count) log.debug("Hidden sector count differs: {}, {}", .{ bpb_3_31.hidden_sector_count, other.hidden_sector_count }); + if (bpb_3_31.total_sector_count_32 != other.total_sector_count_32) log.debug("Total sector count differs: {}, {}", .{ bpb_3_31.total_sector_count_32, other.total_sector_count_32 }); + } + + comptime { + assert(@sizeOf(@This()) == 36); + } + }; + + pub const DOS7_1_79 = extern struct { + dos3_31: DOS3_31 align(1), + fat_sector_count_32: u32 align(1), + drive_description: u16 align(1), + version: [2]u8 align(1), + root_directory_cluster_offset: u32 align(1), + fs_info_sector: u16 align(1), + backup_boot_record_sector: u16 align(1), + reserved: [12]u8 = [1]u8{0} ** 12, + drive_number: u8, + reserved1: u8 = 0, + extended_boot_signature: u8, + serial_number: u32 align(1), + volume_label: [11]u8, + filesystem_type: [8]u8, + + pub fn get_free_cluster_count(bpb: *const DOS7_1_79) u32 { + const total_sector_count = bpb.dos3_31.total_sector_count_32; + const reserved_sector_count = bpb.dos3_31.dos2_0.reserved_sector_count; + const sector_count_per_fat = bpb.fat_sector_count_32; + const fat_count = bpb.dos3_31.dos2_0.fat_count; + return total_sector_count - reserved_sector_count - (sector_count_per_fat * fat_count); + } + + fn compare(this: *const DOS7_1_79, other: *align(1) const DOS7_1_79) void { + this.dos3_31.compare(&other.dos3_31); + + if (this.fat_sector_count_32 != other.fat_sector_count_32) log.debug("FAT sector count (32) differs: {}, {}", .{ this.fat_sector_count_32, other.fat_sector_count_32 }); + if (this.drive_description != other.drive_description) log.debug("Drive description differs: {}, {}", .{ this.drive_description, other.drive_description }); + if (!lib.equal(u8, &this.version, &other.version)) log.debug("Version differs: {any}, {any}", .{ this.version, other.version }); + if (this.root_directory_cluster_offset != other.root_directory_cluster_offset) log.debug("Root directory cluster differs: {}, {}", .{ this.root_directory_cluster_offset, other.root_directory_cluster_offset }); + if (this.fs_info_sector != other.fs_info_sector) log.debug("FS info differs: {}, {}", .{ this.fs_info_sector, other.fs_info_sector }); + if (this.backup_boot_record_sector != other.backup_boot_record_sector) log.debug("Backup boot record sector differs: {}, {}", .{ this.backup_boot_record_sector, other.backup_boot_record_sector }); + if (this.drive_number != other.drive_number) log.debug("Drive number differs: {}, {}", .{ this.drive_number, other.drive_number }); + if (this.extended_boot_signature != other.extended_boot_signature) log.debug("Extended boot signature differs: {}, {}", .{ this.extended_boot_signature, other.extended_boot_signature }); + if (this.serial_number != other.serial_number) log.debug("Serial number differs: 0x{x}, 0x{x}", .{ this.serial_number, other.serial_number }); + if (!lib.equal(u8, &this.volume_label, &other.volume_label)) log.debug("Volume label differs: {s}, {s}", .{ this.volume_label, other.volume_label }); + if (!lib.equal(u8, &this.filesystem_type, &other.filesystem_type)) log.debug("Filesystem type differs: {s}, {s}", .{ this.filesystem_type, other.filesystem_type }); + } + + comptime { + assert(@sizeOf(@This()) == 90); + } + }; +}; + +pub const LegacyPartition = packed struct(u128) { + boot_indicator: u8, + starting_chs: u24, + os_type: u8, + ending_chs: u24, + first_lba: u32, + size_in_lba: u32, + + comptime { + assert(@sizeOf(@This()) == 0x10); + } +}; + +pub const VerificationError = error{ + jmp_code, + sector_size, + cluster_sector_count, + reserved_sector_count, + fat_count, + root_entry_count, + total_sector_count_16, + media_type, + fat_sector_count_16, + hidden_sector_count, + total_sector_count_32, + fat_sector_count_32, + fat_version, + root_directory_cluster_offset, + fs_info_sector, + backup_boot_record_sector, + filesystem_type, +}; + +pub const Partition = extern struct { + bpb: BIOSParameterBlock.DOS7_1_79, + code: [356]u8, + partitions: [4]LegacyPartition align(2), + signature: [2]u8 = [_]u8{ 0x55, 0xaa }, + + comptime { + assert(@sizeOf(@This()) == lib.default_sector_size); + } + + pub fn compare(mbr: *Partition, other: *MBR.Partition) void { + log.debug("Comparing MBRs...", .{}); + mbr.bpb.compare(&other.bpb); + + if (!lib.equal(u8, &mbr.code, &other.code)) { + @panic("mbr: code does not match"); + } + + for (mbr.partitions, 0..) |this_partition, partition_i| { + const other_partition = other.partitions[partition_i]; + + if (this_partition.boot_indicator != other_partition.boot_indicator) log.debug("Mismatch: {}, .{}", .{ this_partition.boot_indicator, other_partition.boot_indicator }); + if (this_partition.starting_chs != other_partition.starting_chs) log.debug("Mismatch: {}, .{}", .{ this_partition.starting_chs, other_partition.starting_chs }); + if (this_partition.os_type != other_partition.os_type) log.debug("Mismatch: {}, .{}", .{ this_partition.os_type, other_partition.os_type }); + if (this_partition.ending_chs != other_partition.ending_chs) log.debug("Mismatch: {}, .{}", .{ this_partition.ending_chs, other_partition.ending_chs }); + if (this_partition.first_lba != other_partition.first_lba) log.debug("Mismatch: {}, .{}", .{ this_partition.first_lba, other_partition.first_lba }); + if (this_partition.size_in_lba != other_partition.size_in_lba) log.debug("Mismatch: {}, .{}", .{ this_partition.size_in_lba, other_partition.size_in_lba }); + } + } + + pub fn verify(mbr: *const MBR.Partition, disk: *Disk) VerificationError!void { + const bpb_2_0 = mbr.bpb.dos3_31.dos2_0; + const jmp_code = bpb_2_0.jmp_code; + const is_allowed_jmp_code = (jmp_code[0] == 0xeb and jmp_code[2] == 0x90) or jmp_code[0] == 0xe9; + log.debug("Checking jump code: [0x{x}, 0x{x}, 0x{x}]", .{ jmp_code[0], jmp_code[1], jmp_code[2] }); + if (!is_allowed_jmp_code) { + return VerificationError.jmp_code; + } + + const sector_size = bpb_2_0.sector_size; + log.debug("Checking sector size: 0x{x}", .{sector_size}); + if (sector_size != lib.default_sector_size) { + log.warn("Sector size different than 0x{x}: 0x{x}", .{ lib.default_sector_size, sector_size }); + return VerificationError.sector_size; + } + + if (sector_size != lib.default_sector_size and sector_size != 0x400 and sector_size != 0x800 and sector_size != 0x1000) { + return VerificationError.sector_size; + } + + const cluster_sector_count = bpb_2_0.cluster_sector_count; + log.debug("Checking cluster sector count: {}", .{cluster_sector_count}); + if (cluster_sector_count != 1 and cluster_sector_count != 2 and cluster_sector_count != 4 and cluster_sector_count != 8 and cluster_sector_count != 16 and cluster_sector_count != 32 and cluster_sector_count != 64 and cluster_sector_count != 128) { + return VerificationError.cluster_sector_count; + } + + const reserved_sector_count = bpb_2_0.reserved_sector_count; + log.debug("Checking reserved sector count: {}", .{cluster_sector_count}); + // TODO: 32 is recommended, not mandatory + if (reserved_sector_count != 32) { + return VerificationError.cluster_sector_count; + } + + const fat_count = bpb_2_0.fat_count; + log.debug("Checking FAT count: {}", .{fat_count}); + if (fat_count != 2) { + return VerificationError.fat_count; + } + + const root_entry_count = bpb_2_0.root_entry_count; + log.debug("Checking root entry count: {}", .{root_entry_count}); + if (root_entry_count != 0) { + return VerificationError.root_entry_count; + } + + const total_sector_count_16 = bpb_2_0.total_sector_count_16; + log.debug("Checking total sector count (16): {}", .{total_sector_count_16}); + if (total_sector_count_16 != 0) { + return VerificationError.total_sector_count_16; + } + + const media_type = bpb_2_0.media_descriptor; + log.debug("Checking media type: 0x{x}", .{media_type}); + if (media_type != 0xf8 and media_type != 0xf0) { + log.warn("Not a standard media type: 0x{x}", .{media_type}); + } + + if (media_type != 0xf0 and media_type != 0xf8 and media_type != 0xf9 and media_type != 0xfa and media_type != 0xfb and media_type != 0xfc and media_type != 0xfd and media_type != 0xfe and media_type != 0xff) { + return VerificationError.media_type; + } + + const fat_sector_count_16 = bpb_2_0.fat_sector_count_16; + log.debug("Checking FAT sector count (16): {}", .{fat_sector_count_16}); + if (fat_sector_count_16 != 0) { + return VerificationError.fat_sector_count_16; + } + + const bpb_3_31 = mbr.bpb.dos3_31; + + const hidden_sector_count = bpb_3_31.hidden_sector_count; + log.debug("Checking hidden sector count: {}", .{hidden_sector_count}); + if (hidden_sector_count != 0) { + return VerificationError.hidden_sector_count; + } + + const total_sector_count_32 = bpb_3_31.total_sector_count_32; + log.debug("Checking total sector count (32): {}", .{total_sector_count_32}); + if (total_sector_count_32 != @divExact(disk.disk_size, disk.sector_size)) { + return VerificationError.total_sector_count_32; + } + + const fat_sector_count_32 = mbr.bpb.fat_sector_count_32; + log.debug("Checking FAT sector count (32): {}", .{fat_sector_count_32}); + if (fat_sector_count_32 == 0) { + return VerificationError.fat_sector_count_32; + } + + const fat_version = mbr.bpb.version; + log.debug("Checking FAT version: {}.{}", .{ fat_version[0], fat_version[1] }); + if (fat_version[0] != 0 or fat_version[1] != 0) { + return VerificationError.fat_version; + } + + const root_directory_cluster_offset = mbr.bpb.root_directory_cluster_offset; + log.debug("Checking root directory cluster offset: {}", .{root_directory_cluster_offset}); + if (root_directory_cluster_offset != 2) { + return VerificationError.root_directory_cluster_offset; + } + + const fs_info_sector = mbr.bpb.fs_info_sector; + log.debug("Checking FSInfo sector: {}", .{fs_info_sector}); + if (fs_info_sector != 1) { + return VerificationError.fs_info_sector; + } + + const backup_boot_record_sector = mbr.bpb.backup_boot_record_sector; + log.debug("Checking backup boot record sector: {}", .{backup_boot_record_sector}); + if (backup_boot_record_sector != 6) { + return VerificationError.backup_boot_record_sector; + } + + const filesystem_type = mbr.bpb.filesystem_type; + log.debug("Checking filesystem type...", .{}); + if (!lib.equal(u8, &filesystem_type, "FAT32 ")) { + return VerificationError.filesystem_type; + } + + @panic("mbr: unexpected verification error"); + } + + pub fn format(mbr: *const MBR.Partition, comptime _: []const u8, _: lib.FormatOptions, writer: anytype) @TypeOf(writer).Error!void { + try lib.format(writer, "MBR:\n", .{}); + const bpb_2_0 = mbr.bpb.dos3_31.dos2_0; + try lib.format(writer, "\tJump code: [0x{x}, 0x{x}, 0x{x}]\n", .{ bpb_2_0.jmp_code[0], bpb_2_0.jmp_code[1], bpb_2_0.jmp_code[2] }); + try lib.format(writer, "\tOEM identifier: {s}\n", .{bpb_2_0.oem_identifier}); + try lib.format(writer, "\tSector size: {}\n", .{bpb_2_0.sector_size}); + try lib.format(writer, "\tCluster sector count: {}\n", .{bpb_2_0.cluster_sector_count}); + try lib.format(writer, "\tReserved sector count: {}\n", .{bpb_2_0.reserved_sector_count}); + try lib.format(writer, "\tFAT count: {}\n", .{bpb_2_0.fat_count}); + try lib.format(writer, "\tRoot entry count: {}\n", .{bpb_2_0.root_entry_count}); + try lib.format(writer, "\tTotal sector count(16): {}\n", .{bpb_2_0.total_sector_count_16}); + try lib.format(writer, "\tMedia descriptor: {}\n", .{bpb_2_0.media_descriptor}); + try lib.format(writer, "\tFAT sector count (16): {}\n", .{bpb_2_0.fat_sector_count_16}); + + const bpb_3_31 = mbr.bpb.dos3_31; + try lib.format(writer, "\tPhysical sectors per track: {}\n", .{bpb_3_31.physical_sectors_per_track}); + try lib.format(writer, "\tDisk head count: {}\n", .{bpb_3_31.disk_head_count}); + try lib.format(writer, "\tHidden sector count: {}\n", .{bpb_3_31.hidden_sector_count}); + try lib.format(writer, "\tTotal sector count: {}\n", .{bpb_3_31.total_sector_count_32}); + + const bpb_7_1_79 = mbr.bpb; + + try lib.format(writer, "\tFAT sector count (32): {}\n", .{bpb_7_1_79.fat_sector_count_32}); + try lib.format(writer, "\tDrive description: {}\n", .{bpb_7_1_79.drive_description}); + try lib.format(writer, "\tVersion: {}.{}\n", .{ bpb_7_1_79.version[0], bpb_7_1_79.version[1] }); + try lib.format(writer, "\tRoot directory cluster offset: {}\n", .{bpb_7_1_79.root_directory_cluster_offset}); + try lib.format(writer, "\tFS info sector: {}\n", .{bpb_7_1_79.fs_info_sector}); + try lib.format(writer, "\tBackup boot record sector: {}\n", .{bpb_7_1_79.backup_boot_record_sector}); + try lib.format(writer, "\tDrive number: {}\n", .{bpb_7_1_79.drive_number}); + try lib.format(writer, "\tExtended boot signature: {}\n", .{bpb_7_1_79.extended_boot_signature}); + try lib.format(writer, "\tSerial number: {}\n", .{bpb_7_1_79.serial_number}); + try lib.format(writer, "\tVolume label: {s}\n", .{bpb_7_1_79.volume_label}); + try lib.format(writer, "\tFilesystem type: {s}\n", .{bpb_7_1_79.filesystem_type}); + + try lib.format(writer, "\nCode:\n", .{}); + for (mbr.code) |code_byte| { + try lib.format(writer, "0x{x}, ", .{code_byte}); + } + + try lib.format(writer, "\n\nPartitions:\n", .{}); + for (mbr.partitions, 0..) |partition, partition_index| { + if (partition.size_in_lba != 0) { + try lib.format(writer, "[{}]\n", .{partition_index}); + try lib.format(writer, "\tBoot indicator: 0x{x}\n", .{partition.boot_indicator}); + try lib.format(writer, "\tStarting CHS: 0x{x}\n", .{partition.starting_chs}); + try lib.format(writer, "\tOS type: 0x{x}\n", .{partition.os_type}); + try lib.format(writer, "\tEnding CHS: 0x{x}\n", .{partition.ending_chs}); + try lib.format(writer, "\tFirst LBA: 0x{x}\n", .{partition.first_lba}); + try lib.format(writer, "\tSize in LBA: 0x{x}\n", .{partition.size_in_lba}); + } + } + } +}; + +pub const DAP = extern struct { + size: u16 = 0x10, + sector_count: u16, + offset: u16, + segment: u16, + lba: u64, + + comptime { + assert(@sizeOf(DAP) == 0x10); + } +}; diff --git a/src/lib/psf1.zig b/src/lib/psf1.zig new file mode 100644 index 0000000..005a27d --- /dev/null +++ b/src/lib/psf1.zig @@ -0,0 +1,13 @@ +const lib = @import("lib"); + +pub const Header = extern struct { + magic: [2]u8, + mode: u8, + character_size: u8, + + pub const magic = .{ 0x36, 0x04 }; +}; + +pub const Error = error{ + invalid_magic, +}; diff --git a/src/privileged.zig b/src/privileged.zig new file mode 100644 index 0000000..b73372e --- /dev/null +++ b/src/privileged.zig @@ -0,0 +1,114 @@ +// This package provides of privileged data structures and routines to both kernel and bootloaders, for now + +const lib = @import("lib"); +// const PhysicalAddress = lib.PhysicalAddress; +// const VirtualAddress = lib.VirtualAddress; +// const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +// const VirtualMemoryRegion = lib.VirtualMemoryRegion; + +const assert = lib.assert; +const log = lib.log; +const maxInt = lib.maxInt; +const Allocator = lib.Allocator; + +const bootloader = @import("bootloader"); + +pub const ACPI = @import("privileged/acpi.zig"); +pub const arch = @import("privileged/arch.zig"); + +pub const writer = E9Writer{ .context = {} }; + +pub const E9WriterError = error{}; +pub const E9Writer = lib.Writer(void, E9WriterError, writeToE9); + +fn writeToE9(_: void, bytes: []const u8) E9WriterError!usize { + return arch.io.writeBytes(0xe9, bytes); +} + +pub const default_stack_size = 0x4000; + +pub const ResourceOwner = enum(u2) { + bootloader = 0, + kernel = 1, + user = 2, +}; + +const panic_logger = lib.log.scoped(.PANIC); + +inline fn exitFromQEMU(exit_code: lib.QEMU.ExitCode) noreturn { + comptime assert(@sizeOf(lib.QEMU.ExitCode) == @sizeOf(u32)); + arch.io.write(u32, lib.QEMU.isa_debug_exit.io_base, @intFromEnum(exit_code)); + + arch.stopCPU(); +} + +pub inline fn shutdown(exit_code: lib.QEMU.ExitCode) noreturn { + if (lib.is_test) { + exitFromQEMU(exit_code); + } else { + arch.stopCPU(); + } +} + +pub const Mapping = extern struct { + physical: lib.PhysicalAddress = lib.PhysicalAddress.invalid(), + virtual: lib.VirtualAddress = .null, + size: u64 = 0, + flags: Flags = .{}, + reserved: u32 = 0, + + pub const Flags = packed struct(u32) { + write: bool = false, + cache_disable: bool = false, + global: bool = false, + execute: bool = false, + user: bool = false, + secret: bool = false, + reserved: u26 = 0, + + pub inline fn empty() Flags { + return .{}; + } + + pub inline fn toArchitectureSpecific(flags: Flags) arch.paging.MemoryFlags { + return arch.paging.newFlags(flags); + } + }; +}; + +pub const PageAllocator = struct { + allocate: *const fn (context: ?*anyopaque, size: u64, alignment: u64, allocate_options: AllocateOptions) Allocator.Allocate.Error!lib.PhysicalMemoryRegion, + context: ?*anyopaque, + context_type: ContextType, + reserved: u32 = 0, + + pub const AllocatePageTablesOptions = packed struct { + count: u16 = 1, + level: arch.paging.Level, + user: bool, + }; + + pub inline fn allocatePageTable(page_allocator: PageAllocator, options: AllocatePageTablesOptions) !lib.PhysicalMemoryRegion { + const result = try page_allocator.allocate(page_allocator.context, arch.paging.page_table_size, arch.paging.page_table_alignment, .{ + .count = options.count, + .level = options.level, + .level_valid = true, + .user = options.user, + }); + return result; + } + + pub const AllocateOptions = packed struct { + count: u16 = 1, + space_waste_allowed_to_guarantee_alignment: u8 = 0, + level: arch.paging.Level = undefined, + level_valid: bool = false, + user: bool = false, + }; + + const ContextType = enum(u32) { + invalid = 0, + bootloader = 1, + cpu = 2, + }; +}; diff --git a/src/privileged/acpi.zig b/src/privileged/acpi.zig new file mode 100644 index 0000000..0a78ae4 --- /dev/null +++ b/src/privileged/acpi.zig @@ -0,0 +1,247 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log; + +pub const RSDP = extern struct { + pub const Descriptor1 = extern struct { + signature: [8]u8, + checksum: u8, + OEM_ID: [6]u8, + revision: u8, + RSDT_address: u32, + + comptime { + assert(@sizeOf(Descriptor1) == 20); + } + + const RSDPError = error{ + version_corrupted, + table_not_found, + xsdt_32_bit, + }; + + pub fn findTable(rsdp: *RSDP.Descriptor1, table_signature: Signature) !*align(1) const Header { + switch (switch (rsdp.revision) { + 0 => false, + 2 => true, + else => return RSDPError.version_corrupted, + }) { + inline else => |is_xsdt| { + if (is_xsdt and lib.cpu.arch == .x86) return RSDPError.xsdt_32_bit; + + const root_table_address = switch (is_xsdt) { + false => rsdp.RSDT_address, + true => @fieldParentPtr(RSDP.Descriptor2, "descriptor1", rsdp).XSDT_address, + }; + + const root_table_header = @as(*align(1) Header, @ptrFromInt(root_table_address)); + const EntryType = switch (is_xsdt) { + false => u32, + true => u64, + }; + + const entry_count = @divExact(root_table_header.length - @sizeOf(Header), @sizeOf(EntryType)); + const entries = @as([*]align(1) const EntryType, @ptrFromInt(@intFromPtr(root_table_header) + @sizeOf(Header)))[0..entry_count]; + for (entries) |entry| { + const table_header = @as(*align(1) const Header, @ptrFromInt(entry)); + if (table_signature == table_header.signature) { + return table_header; + } + } + + return RSDPError.table_not_found; + }, + } + } + }; + + pub const Descriptor2 = extern struct { + descriptor1: Descriptor1, + length: u32, + XSDT_address: u64 align(4), + cheksum: u8, + reserved: [3]u8, + + comptime { + assert(@alignOf(Descriptor1) == 4); + assert(@alignOf(Descriptor2) == 4); + assert(@sizeOf(Descriptor2) == 36); + } + }; +}; + +const Signature = enum(u32) { + APIC = @as(*align(1) const u32, @ptrCast("APIC")).*, + FACP = @as(*align(1) const u32, @ptrCast("FACP")).*, + HPET = @as(*align(1) const u32, @ptrCast("HPET")).*, + MCFG = @as(*align(1) const u32, @ptrCast("MCFG")).*, + WAET = @as(*align(1) const u32, @ptrCast("WAET")).*, + BGRT = @as(*align(1) const u32, @ptrCast("BGRT")).*, + _, +}; + +pub const Header = extern struct { + signature: Signature, + length: u32, + revision: u8, + checksum: u8, + OEM_ID: [6]u8, + OEM_table_ID: [8]u8, + OEM_revision: u32, + creator_ID: u32, + creator_revision: u32, + + comptime { + assert(@sizeOf(@This()) == 0x24); + } +}; + +pub const MADT = extern struct { + header: Header, + LAPIC_address: u32, + flags: MADTFlags, + + pub const MADTFlags = packed struct(u32) { + pcat_compatibility: bool, + reserved: u31 = 0, + }; + + comptime { + assert(@sizeOf(@This()) == 0x2c); + } + + pub fn getIterator(madt: *align(1) const MADT) Iterator { + return .{ + .madt = madt, + }; + } + + pub fn getCPUCount(madt: *align(1) const MADT) u32 { + var cpu_count: u32 = 0; + var iterator = madt.getIterator(); + while (iterator.next()) |entry| { + cpu_count += switch (entry.type) { + .LAPIC => blk: { + const lapic_entry = @fieldParentPtr(LAPIC, "record", entry); + break :blk @intFromBool((lapic_entry.flags.enabled and !lapic_entry.flags.online_capable) or (lapic_entry.flags.online_capable and !lapic_entry.flags.enabled)); + }, + .x2APIC => @panic("x2apic not implemented"), + else => continue, + }; + } + + return cpu_count; + } + + pub const Record = extern struct { + type: Type, + length: u8, + + const Type = enum(u8) { + LAPIC = 0, + IO_APIC = 1, + ISO = 2, + NMI_source = 3, + LAPIC_NMI = 4, + LAPIC_address_override = 5, + IO_SAPIC = 6, + LSAPIC = 7, + platform_interrupt_sources = 8, + x2APIC = 9, + x2APIC_NMI = 0xa, + GIC_CPU_interface = 0xb, + GIC_distributor = 0xc, + GIC_MSI_frame = 0xd, + GIC_redistributor = 0xe, + GIC_interrupt_translation_service = 0xf, + }; + }; + + pub const Iterator = extern struct { + madt: *align(1) const MADT, + index: usize = 0, + offset: usize = @sizeOf(MADT), + + pub fn next(iterator: *Iterator) ?*const Record { + if (iterator.offset < iterator.madt.header.length) { + const record = @as(*const Record, @ptrFromInt(@intFromPtr(iterator.madt) + iterator.offset)); + iterator.offset += record.length; + return record; + } + + return null; + } + }; + + pub const LAPIC = extern struct { + record: Record, + ACPI_processor_UID: u8, + APIC_ID: u8, + flags: Flags, + + const Flags = packed struct(u32) { + enabled: bool, + online_capable: bool, + reserved: u30 = 0, + }; + }; + + const IO_APIC = extern struct { + record: Record, + IO_APIC_ID: u8, + reserved: u8, + IO_APIC_address: u32, + global_system_interrupt_base: u32, + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u64) + @sizeOf(u32)); + } + }; + + const InterruptSourceOverride = extern struct { + record: Record, + bus: u8, + source: u8, + global_system_interrupt: u32 align(2), + flags: u16 align(2), + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u64) + @sizeOf(u16)); + } + }; + + const LAPIC_NMI = extern struct { + record: Record, + ACPI_processor_UID: u8, + flags: u16 align(1), + LAPIC_lint: u8, + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u32) + @sizeOf(u16)); + } + }; +}; + +const MCFG = extern struct { + header: Header, + reserved: u64, + + fn getConfigurations(mcfg: *align(1) MCFG) []Configuration { + const entry_count = (mcfg.header.length - @sizeOf(MCFG)) / @sizeOf(Configuration); + const configuration_base = @intFromPtr(mcfg) + @sizeOf(MCFG); + return @as([*]Configuration, @ptrFromInt(configuration_base))[0..entry_count]; + } + + comptime { + assert(@sizeOf(MCFG) == @sizeOf(Header) + @sizeOf(u64)); + assert(@sizeOf(Configuration) == 0x10); + } + + const Configuration = extern struct { + base_address: u64, + segment_group_number: u16, + start_bus: u8, + end_bus: u8, + reserved: u32, + }; +}; diff --git a/src/privileged/arch.zig b/src/privileged/arch.zig new file mode 100644 index 0000000..fee6004 --- /dev/null +++ b/src/privileged/arch.zig @@ -0,0 +1,22 @@ +const lib = @import("lib"); +const privileged = @import("privileged"); + +pub const x86 = @import("arch/x86.zig"); +pub const x86_64 = @import("arch/x86_64.zig"); + +pub const current = switch (lib.cpu.arch) { + .x86 => x86, + .x86_64 => x86_64, + else => @compileError("Architecture not supported"), +}; + +pub const CPUPageTables = current.paging.CPUPageTables; +pub const stopCPU = current.stopCPU; +pub const paging = current.paging; +pub const Registers = current.Registers; +pub const disableInterrupts = current.disableInterrupts; + +pub const dispatch_count = current.dispatch_count; +pub var max_physical_address_bit: u6 = 40; + +pub const io = current.io; diff --git a/src/privileged/arch/x86.zig b/src/privileged/arch/x86.zig new file mode 100644 index 0000000..9b8587c --- /dev/null +++ b/src/privileged/arch/x86.zig @@ -0,0 +1,11 @@ +const lib = @import("lib"); +const assert = lib.assert; +const privileged = @import("privileged"); + +const x86 = @import("x86/common.zig"); +pub usingnamespace x86; + +pub const io = @import("x86/32/io.zig"); + +/// Use x86_64 paging for VirtualAddressSpace +pub const paging = privileged.arch.x86_64.paging; diff --git a/src/privileged/arch/x86/32/io.zig b/src/privileged/arch/x86/32/io.zig new file mode 100644 index 0000000..3d4aa08 --- /dev/null +++ b/src/privileged/arch/x86/32/io.zig @@ -0,0 +1,14 @@ +const privileged = @import("privileged"); +pub inline fn writeBytes(port: u16, bytes: []const u8) usize { + const bytes_left = asm volatile ( + \\rep outsb + : [ret] "={ecx}" (-> usize), + : [dest] "{dx}" (port), + [src] "{esi}" (bytes.ptr), + [len] "{ecx}" (bytes.len), + : "esi", "ecx" + ); + return bytes.len - bytes_left; +} +pub const read = privileged.arch.x86.read; +pub const write = privileged.arch.x86.write; diff --git a/src/privileged/arch/x86/64/apic.zig b/src/privileged/arch/x86/64/apic.zig new file mode 100644 index 0000000..f47e4a9 --- /dev/null +++ b/src/privileged/arch/x86/64/apic.zig @@ -0,0 +1,148 @@ +const APIC = @This(); + +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.APIC); +const cpuid = lib.arch.x86_64.cpuid; +const maxInt = lib.maxInt; + +const privileged = @import("privileged"); +const VirtualAddress = privileged.arch.VirtualAddress; + +const arch = privileged.arch; +const x86_64 = privileged.arch.x86_64; +const IA32_APIC_BASE = x86_64.registers.IA32_APIC_BASE; +const io = x86_64.io; + +const ID = packed struct(u32) { + reserved: u24, + apic_id: u8, + + pub inline fn read() ID { + return APIC.read(.id); + } +}; + +pub const TaskPriorityRegister = packed struct(u32) { + subclass: u4 = 0, + class: u4 = 0, + reserved: u24 = 0, + + pub inline fn write(tpr: TaskPriorityRegister) void { + APIC.write(.tpr, @as(u32, @bitCast(tpr))); + } +}; + +pub const LVTTimer = packed struct(u32) { + vector: u8 = 0xfa, + reserved: u4 = 0, + delivery_status: bool = false, + reserved1: u3 = 0, + mask: bool = true, + mode: Mode = .oneshot, + reserved2: u13 = 0, + + const Mode = enum(u2) { + oneshot = 0, + periodic = 1, + tsc_deadline = 2, + }; + + pub inline fn write(timer: LVTTimer) void { + APIC.write(.lvt_timer, @as(u32, @bitCast(timer))); + } +}; + +const DivideConfigurationRegister = packed struct(u32) { + divide: Divide = .by_1, + reserved1: u28 = 0, + + // Divide[bit 2] is always 0 + const Divide = enum(u4) { + by_2 = 0b0000, + by_4 = 0b0001, + by_8 = 0b0010, + by_16 = 0b0011, + by_32 = 0b1000, + by_64 = 0b1001, + by_128 = 0b1010, + by_1 = 0b1011, + }; + + inline fn read() DivideConfigurationRegister { + return APIC.read(.timer_div); + } + + inline fn write(dcr: DivideConfigurationRegister) void { + APIC.write(.timer_div, @as(u32, @bitCast(dcr))); + } +}; + +pub inline fn access(register: Register) *volatile u32 { + const physical_address = IA32_APIC_BASE.read().getAddress(); + const virtual_address = switch (lib.cpu.arch) { + .x86 => physical_address.toIdentityMappedVirtualAddress(), + .x86_64 => switch (lib.os) { + .freestanding => physical_address.toHigherHalfVirtualAddress(), + .uefi => physical_address.toIdentityMappedVirtualAddress(), + else => @compileError("Operating system not supported"), + }, + else => @compileError("Architecture not supported"), + }; + + return virtual_address.offset(@intFromEnum(register)).access(*volatile u32); +} + +pub inline fn read(register: Register) u32 { + return access(register).*; +} + +pub inline fn write(register: Register, value: u32) void { + access(register).* = value; +} + +pub const Register = enum(u32) { + id = 0x20, + version = 0x30, + tpr = 0x80, + apr = 0x90, + ppr = 0xa0, + eoi = 0xB0, + spurious = 0xF0, + error_status_register = 0x280, + icr_low = 0x300, + icr_high = 0x310, + lvt_timer = 0x320, + timer_div = 0x3e0, + timer_initcnt = 0x380, + timer_current_count = 0x390, +}; + +pub fn calibrateTimer() privileged.arch.x86_64.TicksPerMS { + //calibrate_timer_with_rtc(apic_base); + const timer_calibration_start = lib.arch.x86_64.readTimestamp(); + var times_i: u64 = 0; + const times = 8; + + APIC.write(.timer_initcnt, lib.maxInt(u32)); + + while (times_i < times) : (times_i += 1) { + io.write(u8, io.Ports.PIT_command, 0x30); + io.write(u8, io.Ports.PIT_data, 0xa9); + io.write(u8, io.Ports.PIT_data, 0x04); + + while (true) { + io.write(u8, io.Ports.PIT_command, 0xe2); + if (io.read(u8, io.Ports.PIT_data) & (1 << 7) != 0) break; + } + } + + const ticks_per_ms = (maxInt(u32) - read(.timer_current_count)) >> 4; + const timer_calibration_end = lib.arch.x86_64.readTimestamp(); + const timestamp_ticks_per_ms = @as(u32, @intCast((timer_calibration_end - timer_calibration_start) >> 3)); + + return .{ + .tsc = timestamp_ticks_per_ms, + .lapic = ticks_per_ms, + }; +} diff --git a/src/privileged/arch/x86/64/io.zig b/src/privileged/arch/x86/64/io.zig new file mode 100644 index 0000000..c9d9b8b --- /dev/null +++ b/src/privileged/arch/x86/64/io.zig @@ -0,0 +1,44 @@ +const common = @import("common"); +const log = common.log.scoped(.IO); + +const privileged = @import("privileged"); + +pub const Ports = struct { + pub const DMA1 = 0x0000; + pub const PIC1 = 0x0020; + pub const Cyrix_MSR = 0x0022; + pub const PIT_data = 0x0040; + pub const PIT_command = 0x0043; + pub const PS2 = 0x0060; + pub const CMOS_RTC = 0x0070; + pub const DMA_page_registers = 0x0080; + pub const A20 = 0x0092; + pub const PIC2 = 0x00a0; + pub const DMA2 = 0x00c0; + pub const E9_hack = 0x00e9; + pub const ATA2 = 0x0170; + pub const ATA1 = 0x01f0; + pub const parallel_port = 0x0278; + pub const serial2 = 0x02f8; + pub const IBM_VGA = 0x03b0; + pub const floppy = 0x03f0; + pub const serial1 = 0x03f8; + pub const PCI_config = 0x0cf8; + pub const PCI_data = 0x0cfc; +}; + +pub inline fn writeBytes(port: u16, bytes: []const u8) usize { + const bytes_left = asm volatile ( + \\rep outsb + : [ret] "={rcx}" (-> usize), + : [dest] "{dx}" (port), + [src] "{rsi}" (bytes.ptr), + [len] "{rcx}" (bytes.len), + : "rsi", "rcx" + ); + + return bytes.len - bytes_left; +} + +pub const read = privileged.arch.x86_64.read; +pub const write = privileged.arch.x86_64.write; diff --git a/src/privileged/arch/x86/64/paging.zig b/src/privileged/arch/x86/64/paging.zig new file mode 100644 index 0000000..fe00fed --- /dev/null +++ b/src/privileged/arch/x86/64/paging.zig @@ -0,0 +1,1088 @@ +const lib = @import("lib"); +const alignForward = lib.alignForward; +const alignBackward = lib.alignBackward; +const isAligned = lib.isAligned; +const isAlignedGeneric = lib.isAlignedGeneric; +const assert = lib.assert; +const enumCount = lib.enumCount; +const log = lib.log.scoped(.VAS); +const zeroes = lib.zeroes; +const Allocator = lib.Allocator; + +const privileged = @import("privileged"); +const Heap = privileged.Heap; +const PageAllocator = privileged.PageAllocator; + +const valid_page_sizes = lib.arch.x86_64.valid_page_sizes; +const reverse_valid_page_sizes = lib.arch.x86_64.reverse_valid_page_sizes; + +const x86_64 = privileged.arch.x86_64; +const cr3 = x86_64.registers.cr3; +const PhysicalAddress = lib.PhysicalAddress; +const VirtualAddress = lib.VirtualAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const PhysicalAddressSpace = lib.PhysicalAddressSpace; +const Mapping = privileged.Mapping; + +const bootloader = @import("bootloader"); + +const page_table_level_count = 4; +pub const page_table_mask = page_table_entry_count - 1; + +pub fn entryCount(comptime level: Level, limit: u64) u10 { + const index = baseFromVirtualAddress(level, limit - 1); + const result = @as(u10, index) + 1; + // @compileLog(limit, index, result); + return result; +} + +// Comptime test +comptime { + const va = 134217728; + const indices = computeIndices(va); + const pml4_index = baseFromVirtualAddress(.PML4, va); + const pdp_index = baseFromVirtualAddress(.PDP, va); + const pd_index = baseFromVirtualAddress(.PD, va); + const pt_index = baseFromVirtualAddress(.PT, va); + assert(pml4_index == indices[@intFromEnum(Level.PML4)]); + assert(pdp_index == indices[@intFromEnum(Level.PDP)]); + assert(pd_index == indices[@intFromEnum(Level.PD)]); + assert(pt_index == indices[@intFromEnum(Level.PT)]); +} + +const max_level_possible = 5; +pub const IndexedVirtualAddress = packed struct(u64) { + page_offset: u12 = 0, + PT: u9 = 0, + PD: u9 = 0, + PDP: u9 = 0, + PML4: u9 = 0, + _: u16 = 0, + + pub fn toVirtualAddress(indexed_virtual_address: IndexedVirtualAddress) VirtualAddress { + const raw = @as(u64, @bitCast(indexed_virtual_address)); + if (indexed_virtual_address.PML4 & 0x100 != 0) { + return VirtualAddress.new(raw | 0xffff_0000_0000_0000); + } else { + return VirtualAddress.new(raw); + } + } +}; + +pub fn baseFromVirtualAddress(comptime level: Level, virtual_address: u64) u9 { + const indexed = @as(IndexedVirtualAddress, @bitCast(virtual_address)); + return @field(indexed, @tagName(level)); +} + +pub const CPUPageTables = extern struct { + pml4_table: PhysicalAddress, + pdp_table: PhysicalAddress, + pd_table: PhysicalAddress, + p_table: PhysicalAddress, + + const base = 0xffff_ffff_8000_0000; + const top = base + pte_count * lib.arch.valid_page_sizes[0]; + const pte_count = page_table_entry_count - left_ptables; + pub const left_ptables = 4; + pub const pml4_index = 0x1ff; + pub const pdp_index = 0x1fe; + pub const pd_index = 0; + const allocated_table_count = + 1 + // PML4 + 1 + // PDP + 1 + // PD + 1; // PT + const allocated_size = allocated_table_count * 0x1000; + + const page_table_base = top; + + comptime { + assert(top + (left_ptables * lib.arch.valid_page_sizes[0]) == base + lib.arch.valid_page_sizes[1]); + } + + pub fn initialize(page_allocator: PageAllocator) !CPUPageTables { + const page_table_allocation = try page_allocator.allocate(page_allocator.context, allocated_size, lib.arch.valid_page_sizes[0], .{}); + + const page_tables = CPUPageTables{ + .pml4_table = page_table_allocation.address, + .pdp_table = page_table_allocation.address.offset(0x1000), + .pd_table = page_table_allocation.address.offset(0x2000), + .p_table = page_table_allocation.address.offset(0x3000), + }; + + page_tables.pml4_table.toIdentityMappedVirtualAddress().access(*PML4Table)[pml4_index] = PML4TE{ + .present = true, + .write = true, + .address = packAddress(PML4TE, page_tables.pdp_table.value()), + }; + + page_tables.pdp_table.toIdentityMappedVirtualAddress().access(*PDPTable)[pdp_index] = PDPTE{ + .present = true, + .write = true, + .address = packAddress(PDPTE, page_tables.pd_table.value()), + }; + + page_tables.pd_table.toIdentityMappedVirtualAddress().access(*PDTable)[pd_index] = PDTE{ + .present = true, + .write = true, + .address = packAddress(PDTE, page_tables.p_table.value()), + }; + + const p_table = page_tables.p_table.toIdentityMappedVirtualAddress().access(*PTable); + p_table[0x200 - 4] = .{ + .present = true, + .write = true, + .address = packAddress(PTE, page_tables.pml4_table.value()), + }; + p_table[0x200 - 3] = .{ + .present = true, + .write = true, + .address = packAddress(PTE, page_tables.pdp_table.value()), + }; + p_table[0x200 - 2] = .{ + .present = true, + .write = true, + .address = packAddress(PTE, page_tables.pd_table.value()), + }; + p_table[0x200 - 1] = .{ + .present = true, + .write = true, + .address = packAddress(PTE, page_tables.p_table.value()), + }; + + return page_tables; + } + + pub const MapError = error{ + lower_limit_exceeded, + upper_limit_exceeded, + }; + + pub fn map(cpu_page_tables: CPUPageTables, asked_physical_address: PhysicalAddress, asked_virtual_address: VirtualAddress, size: u64, general_flags: Mapping.Flags) CPUPageTables.MapError!void { + if (asked_virtual_address.value() < base) return CPUPageTables.MapError.lower_limit_exceeded; + if (asked_virtual_address.offset(size).value() > top) return CPUPageTables.MapError.upper_limit_exceeded; + + const flags = general_flags.toArchitectureSpecific(); + const indices = computeIndices(asked_virtual_address.value()); + const index = indices[indices.len - 1]; + const iteration_count = @as(u32, @intCast(size >> lib.arch.page_shifter(lib.arch.valid_page_sizes[0]))); + const p_table = cpu_page_tables.p_table.toIdentityMappedVirtualAddress().access(*PTable); + const p_table_slice = p_table[index .. index + iteration_count]; + + var physical_address = asked_physical_address.value(); + + for (p_table_slice) |*pte| { + pte.* = @as(PTE, @bitCast(getPageEntry(PTE, physical_address, flags))); + physical_address += 0x1000; + } + } +}; + +pub const Specific = extern struct { + cr3: cr3 align(8), + + pub inline fn makeCurrent(specific: Specific) void { + specific.getUserCr3().write(); + } + + pub fn fromPageTables(cpu_page_tables: CPUPageTables) Specific { + return .{ + .cr3 = cr3.fromAddress(cpu_page_tables.pml4_table), + }; + } + + pub noinline fn map(specific: Specific, asked_physical_address: PhysicalAddress, asked_virtual_address: VirtualAddress, size: u64, general_flags: Mapping.Flags, page_allocator: PageAllocator) !void { + const flags = general_flags.toArchitectureSpecific(); + const top_virtual_address = asked_virtual_address.offset(size); + + inline for (reverse_valid_page_sizes, 0..) |reverse_page_size, reverse_page_index| { + if (size >= reverse_page_size) { + const is_smallest_page_size = reverse_page_index == reverse_valid_page_sizes.len - 1; + + if (is_smallest_page_size) { + var virtual_address = asked_virtual_address.value(); + var physical_address = asked_physical_address.value(); + + while (virtual_address < top_virtual_address.value()) : ({ + physical_address += reverse_page_size; + virtual_address += reverse_page_size; + }) { + try specific.map4KPage(physical_address, virtual_address, flags, page_allocator); + } + + return; + } else { + const aligned_page_address = alignForward(u64, asked_virtual_address.value(), reverse_page_size); + const prologue_misalignment = aligned_page_address - asked_virtual_address.value(); + const aligned_size_left = size - prologue_misalignment; + + if (aligned_size_left >= reverse_page_size) { + if (prologue_misalignment != 0) { + try specific.map(asked_physical_address, asked_virtual_address, prologue_misalignment, general_flags, page_allocator); + } + + const virtual_address = VirtualAddress.new(aligned_page_address); + const physical_address = asked_physical_address.offset(prologue_misalignment); + const this_page_top_physical_address = PhysicalAddress.new(alignBackward(u64, physical_address.offset(aligned_size_left).value(), reverse_page_size)); + const this_page_top_virtual_address = VirtualAddress.new(alignBackward(u64, virtual_address.offset(aligned_size_left).value(), reverse_page_size)); + const this_huge_page_size = this_page_top_virtual_address.value() - virtual_address.value(); + try specific.mapGeneric(physical_address, virtual_address, this_huge_page_size, reverse_page_size, flags, page_allocator); + + const epilogue_misalignment = top_virtual_address.value() - this_page_top_virtual_address.value(); + + if (epilogue_misalignment != 0) { + const epilogue_physical_address = this_page_top_physical_address; + const epilogue_virtual_address = this_page_top_virtual_address; + + try specific.map(epilogue_physical_address, epilogue_virtual_address, epilogue_misalignment, general_flags, page_allocator); + } + + return; + } + } + } + } + + return MapError.no_region_found; + } + + fn mapGeneric(specific: Specific, asked_physical_address: PhysicalAddress, asked_virtual_address: VirtualAddress, size: u64, comptime asked_page_size: comptime_int, flags: MemoryFlags, page_allocator: PageAllocator) !void { + if (!isAlignedGeneric(u64, asked_physical_address.value(), asked_page_size)) { + //log.debug("PA: {}. Page size: 0x{x}", .{ asked_physical_address, asked_page_size }); + @panic("Misaligned physical address in mapGeneric"); + } + if (!isAlignedGeneric(u64, asked_virtual_address.value(), asked_page_size)) { + @panic("Misaligned virtual address in mapGeneric"); + } + if (!isAlignedGeneric(u64, size, asked_page_size)) { + //log.debug("Asked size: 0x{x}. Asked page size: 0x{x}", .{ size, asked_page_size }); + @panic("Misaligned size in mapGeneric"); + } + + var virtual_address = asked_virtual_address.value(); + var physical_address = asked_physical_address.value(); + const top_virtual_address = asked_virtual_address.offset(size).value(); + + // TODO: batch better + switch (asked_page_size) { + // 1 GB + lib.arch.valid_page_sizes[0] * page_table_entry_count * page_table_entry_count => { + while (virtual_address < top_virtual_address) : ({ + physical_address += asked_page_size; + virtual_address += asked_page_size; + }) { + try specific.map1GBPage(physical_address, virtual_address, flags, page_allocator); + } + }, + // 2 MB + lib.arch.valid_page_sizes[0] * page_table_entry_count => { + while (virtual_address < top_virtual_address) : ({ + physical_address += asked_page_size; + virtual_address += asked_page_size; + }) { + try specific.map2MBPage(physical_address, virtual_address, flags, page_allocator); + } + }, + // Smallest: 4 KB + lib.arch.valid_page_sizes[0] => { + while (virtual_address < top_virtual_address) : ({ + physical_address += asked_page_size; + virtual_address += asked_page_size; + }) { + try specific.map4KPage(physical_address, virtual_address, flags, page_allocator); + } + }, + else => @compileError("Invalid reverse valid page size"), + } + } + + fn map1GBPage(specific: Specific, physical_address: u64, virtual_address: u64, flags: MemoryFlags, page_allocator: PageAllocator) !void { + const indices = computeIndices(virtual_address); + + const pml4_table = try getPML4Table(specific.cr3); + const pdp_table = try getPDPTable(pml4_table, indices, flags, page_allocator); + try mapPageTable1GB(pdp_table, indices, physical_address, flags); + } + + fn map2MBPage(specific: Specific, physical_address: u64, virtual_address: u64, flags: MemoryFlags, page_allocator: PageAllocator) !void { + const indices = computeIndices(virtual_address); + + const pml4_table = try getPML4Table(specific.cr3); + const pdp_table = try getPDPTable(pml4_table, indices, flags, page_allocator); + const pd_table = try getPDTable(pdp_table, indices, flags, page_allocator); + + mapPageTable2MB(pd_table, indices, physical_address, flags) catch |err| { + log.err("Virtual address: 0x{x}. Physical address: 0x{x}", .{ virtual_address, physical_address }); + return err; + }; + } + + fn map4KPage(specific: Specific, physical_address: u64, virtual_address: u64, flags: MemoryFlags, page_allocator: PageAllocator) !void { + const indices = computeIndices(virtual_address); + + const pml4_table = try getPML4Table(specific.cr3); + const pdp_table = try getPDPTable(pml4_table, indices, flags, page_allocator); + const pd_table = try getPDTable(pdp_table, indices, flags, page_allocator); + const p_table = try getPTable(pd_table, indices, flags, page_allocator); + try mapPageTable4KB(p_table, indices, physical_address, flags); + } + + pub inline fn switchTo(specific: *Specific, execution_mode: lib.TraditionalExecutionMode) void { + const mask = ~@as(u64, 1 << 12); + const masked_cr3 = (@as(u64, @bitCast(specific.cr3)) & mask); + const privileged_or = (@as(u64, @intFromEnum(execution_mode)) << 12); + const new_cr3 = @as(cr3, @bitCast(masked_cr3 | privileged_or)); + specific.cr3 = new_cr3; + } + + pub inline fn copyHigherHalfCommon(cpu_specific: Specific, pml4_physical_address: PhysicalAddress) void { + const cpu_side_pml4_table = pml4_physical_address.toHigherHalfVirtualAddress().access(*PML4Table); + const privileged_cpu_pml4_table = try getPML4Table(cpu_specific.cr3); + for (cpu_side_pml4_table[0x100..], privileged_cpu_pml4_table[0x100..]) |*pml4_entry, cpu_pml4_entry| { + pml4_entry.* = cpu_pml4_entry; + } + } + + pub fn copyHigherHalfPrivileged(cpu_specific: Specific, pml4_physical_address: PhysicalAddress) void { + cpu_specific.copyHigherHalfCommon(pml4_physical_address); + } + + pub fn copyHigherHalfUser(cpu_specific: Specific, pml4_physical_address: PhysicalAddress, page_allocator: *PageAllocator) !void { + cpu_specific.copyHigherHalfCommon(pml4_physical_address); + + const pml4_table = pml4_physical_address.toHigherHalfVirtualAddress().access(*PML4Table); + const pml4_entry = pml4_table[0x1ff]; + const pml4_entry_address = PhysicalAddress.new(unpackAddress(pml4_entry)); + const pdp_table = pml4_entry_address.toHigherHalfVirtualAddress().access(*PDPTable); + const new_pdp_table_allocation = try page_allocator.allocate(0x1000, 0x1000); + const new_pdp_table = new_pdp_table_allocation.toHigherHalfVirtualAddress().access(PDPTE); + @memcpy(new_pdp_table, pdp_table); + new_pdp_table[0x1fd] = @as(PDPTE, @bitCast(@as(u64, 0))); + } + + pub const TranslateError = error{ + pml4_entry_not_present, + pml4_entry_address_null, + pdp_entry_not_present, + pdp_entry_address_null, + pd_entry_not_present, + pd_entry_address_null, + pt_entry_not_present, + pt_entry_address_null, + flags_not_respected, + }; + + pub fn translateAddress(specific: Specific, virtual_address: VirtualAddress, flags: MemoryFlags) !PhysicalAddress { + const indices = computeIndices(virtual_address.value()); + const is_desired = virtual_address.value() == 0xffff_ffff_8001_f000; + + const pml4_table = try getPML4Table(specific.cr3); + // if (is_desired) { + // _ = try specific.translateAddress(VirtualAddress.new(@ptrToInt(pml4_table)), .{}); + // } + + //log.debug("pml4 table: 0x{x}", .{@ptrToInt(pml4_table)}); + const pml4_index = indices[@intFromEnum(Level.PML4)]; + const pml4_entry = pml4_table[pml4_index]; + if (!pml4_entry.present) { + log.err("Virtual address: 0x{x}.\nPML4 index: {}.\nValue: {}\n", .{ virtual_address.value(), pml4_index, pml4_entry }); + return TranslateError.pml4_entry_not_present; + } + + if (pml4_entry.execute_disable and !flags.execute_disable) { + return TranslateError.flags_not_respected; + } + + const pml4_entry_address = PhysicalAddress.new(unpackAddress(pml4_entry)); + if (pml4_entry_address.value() == 0) { + return TranslateError.pml4_entry_address_null; + } + + const pdp_table = try getPDPTable(pml4_table, indices, undefined, null); + if (is_desired) { + _ = try specific.translateAddress(VirtualAddress.new(@intFromPtr(pdp_table)), .{}); + } + //log.debug("pdp table: 0x{x}", .{@ptrToInt(pdp_table)}); + const pdp_index = indices[@intFromEnum(Level.PDP)]; + const pdp_entry = &pdp_table[pdp_index]; + if (!pdp_entry.present) { + log.err("PDP index {} not present in PDP table 0x{x}", .{ pdp_index, @intFromPtr(pdp_table) }); + return TranslateError.pdp_entry_not_present; + } + + if (pdp_entry.execute_disable and !flags.execute_disable) { + return TranslateError.flags_not_respected; + } + + if (pdp_entry.page_size) { + const pdp_entry_1gb = @as(PDPTE_1GB, @bitCast(pdp_entry.*)); + const entry_address_value = unpackAddress(pdp_entry_1gb); + const physical_address = PhysicalAddress.new(entry_address_value); + if (lib.isAlignedGeneric(u64, virtual_address.value(), lib.gb)) { + return physical_address; + } else { + @panic("unaligned 1gb"); + } + } + + const pdp_entry_address = PhysicalAddress.new(unpackAddress(pdp_entry.*)); + if (pdp_entry_address.value() == 0) { + return TranslateError.pdp_entry_address_null; + } + + const pd_table = try accessPageTable(pdp_entry_address, *PDTable); + if (is_desired) { + _ = try specific.translateAddress(VirtualAddress.new(@intFromPtr(pd_table)), .{}); + } + //log.debug("pd table: 0x{x}", .{@ptrToInt(pd_table)}); + const pd_index = indices[@intFromEnum(Level.PD)]; + const pd_entry = &pd_table[pd_index]; + if (!pd_entry.present) { + log.err("PD index: {}", .{pd_index}); + log.err("PD entry: 0x{x}", .{@intFromPtr(pd_entry)}); + return TranslateError.pd_entry_not_present; + } + + if (pd_entry.execute_disable and !flags.execute_disable) { + return TranslateError.flags_not_respected; + } + + if (pd_entry.page_size) { + const pd_entry_2mb = @as(PDTE_2MB, @bitCast(pd_entry.*)); + const entry_address_value = unpackAddress(pd_entry_2mb); + const physical_address = PhysicalAddress.new(entry_address_value); + if (lib.isAlignedGeneric(u64, virtual_address.value(), 2 * lib.mb)) { + return physical_address; + } else { + @panic("unaligned 2mb"); + } + } + + const pd_entry_address = PhysicalAddress.new(unpackAddress(pd_entry)); + if (pd_entry_address.value() == 0) { + return TranslateError.pd_entry_address_null; + } + + const p_table = try accessPageTable(pd_entry_address, *PTable); + if (is_desired) { + _ = try specific.translateAddress(VirtualAddress.new(@intFromPtr(p_table)), .{}); + } + // log.debug("p table: 0x{x}", .{@ptrToInt(p_table)}); + const pt_index = indices[@intFromEnum(Level.PT)]; + const pt_entry = &p_table[pt_index]; + if (!pt_entry.present) { + log.err("Virtual address 0x{x} not mapped", .{virtual_address.value()}); + log.err("Indices: {any}", .{indices}); + log.err("PTE: 0x{x}", .{@intFromPtr(pt_entry)}); + log.err("PDE: 0x{x}", .{@intFromPtr(pd_entry)}); + log.err("PDPE: 0x{x}", .{@intFromPtr(pdp_entry)}); + return TranslateError.pt_entry_not_present; + } + + if (pt_entry.execute_disable and !flags.execute_disable) { + return TranslateError.flags_not_respected; + } + + const pt_entry_address = PhysicalAddress.new(unpackAddress(pt_entry.*)); + if (pt_entry_address.value() == 0) { + return TranslateError.pt_entry_address_null; + } + + return pt_entry_address; + } + + pub fn setMappingFlags(specific: Specific, virtual_address: u64, flags: Mapping.Flags) !void { + const indices = computeIndices(virtual_address); + + const vas_cr3 = specific.cr3; + + const pml4_physical_address = vas_cr3.getAddress(); + + const pml4_table = try accessPageTable(pml4_physical_address, *PML4Table); + const pml4_entry = pml4_table[indices[@intFromEnum(Level.PML4)]]; + if (!pml4_entry.present) { + return TranslateError.pml4_entry_not_present; + } + + const pml4_entry_address = PhysicalAddress.new(unpackAddress(pml4_entry)); + if (pml4_entry_address.value() == 0) { + return TranslateError.pml4_entry_address_null; + } + + const pdp_table = try accessPageTable(pml4_entry_address, *PDPTable); + const pdp_entry = pdp_table[indices[@intFromEnum(Level.PDP)]]; + if (!pdp_entry.present) { + return TranslateError.pdp_entry_not_present; + } + + const pdp_entry_address = PhysicalAddress.new(unpackAddress(pdp_entry)); + if (pdp_entry_address.value() == 0) { + return TranslateError.pdp_entry_address_null; + } + + const pd_table = try accessPageTable(pdp_entry_address, *PDTable); + const pd_entry = pd_table[indices[@intFromEnum(Level.PD)]]; + if (!pd_entry.present) { + return TranslateError.pd_entry_not_present; + } + + const pd_entry_address = PhysicalAddress.new(unpackAddress(pd_entry)); + if (pd_entry_address.value() == 0) { + return TranslateError.pd_entry_address_null; + } + + const pt_table = try accessPageTable(pd_entry_address, *PTable); + const pt_entry = &pt_table[indices[@intFromEnum(Level.PT)]]; + if (!pt_entry.present) { + return TranslateError.pd_entry_not_present; + } + + pt_entry.write = flags.write; + pt_entry.user = flags.user; + pt_entry.page_level_cache_disable = flags.cache_disable; + pt_entry.global = flags.global; + pt_entry.execute_disable = !flags.execute; + } + + pub fn debugMemoryMap(specific: Specific) !void { + log.debug("[START] Memory map dump 0x{x}\n", .{specific.cr3.getAddress().value()}); + + const pml4 = try specific.getCpuPML4Table(); + + for (pml4, 0..) |*pml4te, pml4_index| { + if (pml4te.present) { + const pdp_table = try accessPageTable(PhysicalAddress.new(unpackAddress(pml4te.*)), *PDPTable); + + for (pdp_table, 0..) |*pdpte, pdp_index| { + if (pdpte.present) { + if (pdpte.page_size) { + continue; + } + + const pd_table = try accessPageTable(PhysicalAddress.new(unpackAddress(pdpte.*)), *PDTable); + + for (pd_table, 0..) |*pdte, pd_index| { + if (pdte.present) { + if (pdte.page_size) @panic("bbbb"); + + const p_table = try accessPageTable(PhysicalAddress.new(unpackAddress(pdte.*)), *PTable); + + for (p_table, 0..) |*pte, pt_index| { + if (pte.present) { + const indexed_virtual_address = IndexedVirtualAddress{ + .PML4 = @as(u9, @intCast(pml4_index)), + .PDP = @as(u9, @intCast(pdp_index)), + .PD = @as(u9, @intCast(pd_index)), + .PT = @as(u9, @intCast(pt_index)), + }; + + const virtual_address = indexed_virtual_address.toVirtualAddress(); + const physical_address = unpackAddress(pte.*); + log.debug("0x{x} -> 0x{x}", .{ virtual_address.value(), physical_address }); + } + } + } + } + } + } + } + } + + log.debug("[END] Memory map dump", .{}); + } + + inline fn getUserCr3(specific: Specific) cr3 { + assert(@as(u64, @bitCast(specific.cr3)) & page_table_size == 0); + return @as(cr3, @bitCast(@as(u64, @bitCast(specific.cr3)) | page_table_size)); + } + + pub inline fn getCpuPML4Table(specific: Specific) !*PML4Table { + assert(@as(u64, @bitCast(specific.cr3)) & page_table_size == 0); + return try specific.getPML4TableUnchecked(); + } + pub inline fn getUserPML4Table(specific: Specific) !*PML4Table { + return try getPML4Table(specific.getUserCr3()); + } + + pub inline fn getPML4TableUnchecked(specific: Specific) !*PML4Table { + return try getPML4Table(specific.cr3); + } +}; + +const Indices = [enumCount(Level)]u16; + +const MapError = error{ + already_present_4kb, + already_present_2mb, + already_present_1gb, + validation_failed, + no_region_found, +}; + +pub const Error = error{ + invalid_physical, + invalid_virtual, + invalid_size, + unaligned_physical, + unaligned_virtual, + unaligned_size, +}; + +pub fn accessPageTable(physical_address: PhysicalAddress, comptime Pointer: type) !Pointer { + const virtual_address = switch (lib.cpu.arch) { + .x86 => physical_address.toIdentityMappedVirtualAddress(), + .x86_64 => switch (lib.os) { + .freestanding => physical_address.toHigherHalfVirtualAddress(), + .uefi => physical_address.toIdentityMappedVirtualAddress(), + else => @compileError("OS not supported"), + }, + else => @compileError("Architecture not supported"), + }; + + return switch (lib.cpu.arch) { + .x86 => @as(Pointer, @ptrFromInt(try lib.tryDereferenceAddress(virtual_address.value()))), + else => virtual_address.access(Pointer), + }; +} + +fn getPML4Table(cr3r: cr3) !*PML4Table { + const pml4_table = try accessPageTable(cr3r.getAddress(), *PML4Table); + return pml4_table; +} + +fn getPDPTable(pml4_table: *PML4Table, indices: Indices, flags: MemoryFlags, maybe_page_allocator: ?PageAllocator) !*PDPTable { + const index = indices[@intFromEnum(Level.PML4)]; + const entry_pointer = &pml4_table[index]; + + const table_physical_address = physical_address_blk: { + const entry_value = entry_pointer.*; + if (entry_value.present) { + const entry_address = unpackAddress(entry_value); + break :physical_address_blk PhysicalAddress.new(entry_address); + } else { + if (maybe_page_allocator) |page_allocator| { + const entry_allocation = try page_allocator.allocatePageTable(.{ + .level = .PDP, + .user = flags.user, + }); + + entry_pointer.* = PML4TE{ + .present = true, + .write = true, + .user = flags.user, + .address = packAddress(PML4TE, entry_allocation.address.value()), + }; + + break :physical_address_blk entry_allocation.address; + } else { + return Allocator.Allocate.Error.OutOfMemory; + } + } + }; + + return try accessPageTable(table_physical_address, *PDPTable); +} + +pub inline fn getPageEntry(comptime Entry: type, physical_address: u64, flags: MemoryFlags) Entry { + return if (@hasDecl(Entry, "page_size") and flags.page_size) Entry{ + .present = true, + .write = flags.write, + .user = flags.user, + .page_level_cache_disable = flags.cache_disable, + .global = flags.global, + .pat = flags.pat, + .address = packAddress(Entry, physical_address), + .execute_disable = flags.execute_disable, + .page_size = flags.page_size, + } else Entry{ + .present = true, + .write = flags.write, + .user = flags.user, + .page_level_cache_disable = flags.cache_disable, + .global = flags.global, + .pat = flags.pat, + .address = packAddress(Entry, physical_address), + .execute_disable = flags.execute_disable, + }; +} + +fn mapPageTable1GB(pdp_table: *PDPTable, indices: Indices, physical_address: u64, flags: MemoryFlags) MapError!void { + const entry_index = indices[@intFromEnum(Level.PDP)]; + const entry_pointer = &pdp_table[entry_index]; + + if (entry_pointer.present) return MapError.already_present_1gb; + + assert(isAlignedGeneric(u64, physical_address, valid_page_sizes[2])); + + entry_pointer.* = @as(PDPTE, @bitCast(getPageEntry(PDPTE_1GB, physical_address, flags))); +} + +fn mapPageTable2MB(pd_table: *PDTable, indices: Indices, physical_address: u64, flags: MemoryFlags) !void { + const entry_index = indices[@intFromEnum(Level.PD)]; + const entry_pointer = &pd_table[entry_index]; + const entry_value = entry_pointer.*; + + if (entry_value.present) { + log.err("Already mapped to: 0x{x}", .{unpackAddress(entry_value)}); + return MapError.already_present_2mb; + } + + assert(isAlignedGeneric(u64, physical_address, valid_page_sizes[1])); + + entry_pointer.* = @as(PDTE, @bitCast(getPageEntry(PDTE_2MB, physical_address, flags))); +} + +fn mapPageTable4KB(p_table: *PTable, indices: Indices, physical_address: u64, flags: MemoryFlags) !void { + const entry_index = indices[@intFromEnum(Level.PT)]; + const entry_pointer = &p_table[entry_index]; + + if (entry_pointer.present) { + log.err("Trying to map to 0x{x}. Already mapped to 0x{x}", .{ physical_address, unpackAddress(entry_pointer.*) }); + return MapError.already_present_4kb; + } + + assert(isAlignedGeneric(u64, physical_address, valid_page_sizes[0])); + + entry_pointer.* = @as(PTE, @bitCast(getPageEntry(PTE, physical_address, flags))); +} + +const ToImplementError = error{ + page_size, +}; + +fn getPDTable(pdp_table: *PDPTable, indices: Indices, flags: MemoryFlags, page_allocator: PageAllocator) !*PDTable { + const entry_index = indices[@intFromEnum(Level.PDP)]; + const entry_pointer = &pdp_table[entry_index]; + + const table_physical_address = physical_address_blk: { + const entry_value = entry_pointer.*; + if (entry_value.present) { + // The address is mapped with a 1GB page + if (entry_value.page_size) { + return ToImplementError.page_size; + } else break :physical_address_blk PhysicalAddress.new(unpackAddress(entry_value)); + } else { + const entry_allocation = try page_allocator.allocatePageTable(.{ + .level = .PD, + .user = flags.user, + }); + + entry_pointer.* = PDPTE{ + .present = true, + .write = true, + .user = flags.user, + .address = packAddress(PDPTE, entry_allocation.address.value()), + }; + + break :physical_address_blk entry_allocation.address; + } + }; + + return try accessPageTable(table_physical_address, *PDTable); +} + +fn getPTable(pd_table: *PDTable, indices: Indices, flags: MemoryFlags, page_allocator: PageAllocator) !*PTable { + const entry_pointer = &pd_table[indices[@intFromEnum(Level.PD)]]; + const table_physical_address = physical_address_blk: { + const entry_value = entry_pointer.*; + if (entry_value.present) { + // The address is mapped with a 2MB page + if (entry_value.page_size) { + return ToImplementError.page_size; + } else break :physical_address_blk PhysicalAddress.new(unpackAddress(entry_value)); + } else { + const entry_allocation = try page_allocator.allocatePageTable(.{ .level = .PT, .user = flags.user }); + + entry_pointer.* = PDTE{ + .present = true, + .write = true, + .user = flags.user, + .address = packAddress(PDTE, entry_allocation.address.value()), + }; + + break :physical_address_blk entry_allocation.address; + } + }; + + return try accessPageTable(table_physical_address, *PTable); +} + +const half_entry_count = (@sizeOf(PML4Table) / @sizeOf(PML4TE)) / 2; + +const needed_physical_memory_for_bootstrapping_cpu_driver_address_space = @sizeOf(PML4Table) + @sizeOf(PDPTable) * 256; + +pub fn computeIndices(virtual_address: u64) Indices { + var indices: Indices = undefined; + var va = virtual_address; + va = va >> 12; + indices[3] = @as(u9, @truncate(va)); + va = va >> 9; + indices[2] = @as(u9, @truncate(va)); + va = va >> 9; + indices[1] = @as(u9, @truncate(va)); + va = va >> 9; + indices[0] = @as(u9, @truncate(va)); + + return indices; +} + +pub inline fn newFlags(general_flags: Mapping.Flags) MemoryFlags { + return MemoryFlags{ + .write = general_flags.write, + .user = general_flags.user, + .cache_disable = general_flags.cache_disable, + .global = general_flags.global, + .execute_disable = !general_flags.execute, + }; +} + +pub const MemoryFlags = packed struct(u64) { + present: bool = true, + write: bool = false, + user: bool = false, + write_through: bool = false, + cache_disable: bool = false, + dirty: bool = false, + global: bool = false, + pat: bool = false, + page_size: bool = false, + reserved: u54 = 0, + execute_disable: bool = false, + + comptime { + assert(@sizeOf(u64) == @sizeOf(MemoryFlags)); + } +}; + +const address_mask: u64 = 0x0000_00ff_ffff_f000; + +pub const Level = Level4; + +pub const Level4 = enum(u2) { + PML4 = 0, + PDP = 1, + PD = 2, + PT = 3, + + pub const count = lib.enumCount(@This()); +}; + +pub const Level5 = enum(u3) {}; + +pub fn EntryTypeMapSize(comptime page_size: comptime_int) usize { + return switch (Level) { + Level4 => switch (page_size) { + lib.arch.valid_page_sizes[0] => 4, + lib.arch.valid_page_sizes[1] => 3, + lib.arch.valid_page_sizes[2] => 2, + else => @compileError("Unknown page size"), + }, + Level5 => @compileError("TODO"), + else => @compileError("unreachable"), + }; +} + +pub fn EntryTypeMap(comptime page_size: comptime_int) [EntryTypeMapSize(page_size)]type { + const map_size = EntryTypeMapSize(page_size); + const Result = [map_size]type; + var result: Result = undefined; + switch (Level) { + Level4, Level5 => { + if (@hasField(Level, "pml5")) { + @compileError("TODO: type_map[@enumToInt(Level.PML5)] ="); + } + + result[@intFromEnum(Level.PML4)] = PML4TE; + + if (page_size == lib.arch.valid_page_sizes[2]) { + assert(map_size == 2 + @intFromBool(Level == Level5)); + result[@intFromEnum(Level.PDP)] = PDPTE_1GB; + } else { + result[@intFromEnum(Level.PDP)] = PDPTE; + + if (page_size == lib.arch.valid_page_sizes[1]) { + assert(map_size == @as(usize, 3) + @intFromBool(Level == Level5)); + result[@intFromEnum(Level.PD)] = PDTE_2MB; + } else { + assert(page_size == lib.arch.valid_page_sizes[0]); + + result[@intFromEnum(Level.PD)] = PDTE; + result[@intFromEnum(Level.PT)] = PTE; + } + } + }, + else => @compileError("Unexpected level type"), + } + + return result; +} + +pub inline fn unpackAddress(entry: anytype) u64 { + const T = @TypeOf(entry); + const RealType = switch (@typeInfo(T)) { + .Pointer => |pointer| pointer.child, + else => T, + }; + const address_offset = @bitOffsetOf(RealType, "address"); + return @as(u64, entry.address) << address_offset; +} + +fn AddressType(comptime T: type) type { + var a: T = undefined; + return @TypeOf(@field(a, "address")); +} + +pub fn packAddress(comptime T: type, physical_address: u64) AddressType(T) { + assert(physical_address < lib.config.cpu_driver_higher_half_address); + const address_offset = @bitOffsetOf(T, "address"); + return @as(AddressType(T), @intCast(physical_address >> address_offset)); +} + +pub const PML4TE = packed struct(u64) { + present: bool = false, + write: bool = false, + user: bool = false, + page_level_write_through: bool = false, + page_level_cache_disable: bool = false, + accessed: bool = false, + reserved0: u5 = 0, + hlat_restart: bool = false, + address: u28, + reserved1: u23 = 0, + execute_disable: bool = false, + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u64)); + assert(@bitSizeOf(@This()) == @bitSizeOf(u64)); + } +}; + +pub const PDPTE = packed struct(u64) { + present: bool = false, + write: bool = false, + user: bool = false, + page_level_write_through: bool = false, + page_level_cache_disable: bool = false, + accessed: bool = false, + reserved0: u1 = 0, + page_size: bool = false, + reserved1: u3 = 0, + hlat_restart: bool = false, + address: u28, + reserved2: u23 = 0, + execute_disable: bool = false, + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u64)); + assert(@bitSizeOf(@This()) == @bitSizeOf(u64)); + } +}; + +pub const PDPTE_1GB = packed struct(u64) { + present: bool = false, + write: bool = false, + user: bool = false, + page_level_write_through: bool = false, + page_level_cache_disable: bool = false, + accessed: bool = false, + dirty: bool = false, + page_size: bool = true, + global: bool = false, + reserved: u2 = 0, + hlat_restart: bool = false, + pat: bool = false, + address: u29, + reserved2: u10 = 0, + ignored: u7 = 0, + protection_key: u4 = 0, + execute_disable: bool = false, + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u64)); + assert(@bitSizeOf(@This()) == @bitSizeOf(u64)); + } +}; + +pub const PDTE = packed struct(u64) { + present: bool = false, + write: bool = false, + user: bool = false, + page_level_write_through: bool = false, + page_level_cache_disable: bool = false, + accessed: bool = false, + reserved0: u1 = 0, + page_size: bool = false, + reserved1: u3 = 0, + hlat_restart: bool = false, + address: u28, + reserved2: u23 = 0, + execute_disable: bool = false, + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u64)); + assert(@bitSizeOf(@This()) == @bitSizeOf(u64)); + } +}; + +pub const PDTE_2MB = packed struct(u64) { + present: bool = false, + write: bool = false, + user: bool = false, + page_level_write_through: bool = false, + page_level_cache_disable: bool = false, + accessed: bool = false, + dirty: bool = false, + page_size: bool = true, + global: bool = false, + ignored: u2 = 0, + hlat_restart: bool = false, + pat: bool = false, + reserved: u8 = 0, + address: u21, + reserved2: u10 = 0, + ignored2: u7 = 0, + protection_key: u4 = 0, + execute_disable: bool = false, + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u64)); + assert(@bitSizeOf(@This()) == @bitSizeOf(u64)); + } +}; + +pub const PTE = packed struct(u64) { + present: bool = false, + write: bool = false, + user: bool = false, + page_level_write_through: bool = false, + page_level_cache_disable: bool = false, + accessed: bool = false, + dirty: bool = false, + pat: bool = false, + global: bool = false, + reserved1: u2 = 0, + hlat_restart: bool = false, + address: u28, + reserved2: u19 = 0, + protection_key: u4 = 0, + execute_disable: bool = false, + + comptime { + assert(@sizeOf(@This()) == @sizeOf(u64)); + assert(@bitSizeOf(@This()) == @bitSizeOf(u64)); + } +}; + +pub const PML4Table = [page_table_entry_count]PML4TE; +pub const PDPTable = [page_table_entry_count]PDPTE; +pub const PDTable = [page_table_entry_count]PDTE; +pub const PTable = [page_table_entry_count]PTE; +pub const page_table_entry_size = @sizeOf(u64); +pub const page_table_size = lib.arch.valid_page_sizes[0]; +pub const page_table_entry_count = @divExact(page_table_size, page_table_entry_size); +pub const page_table_alignment = page_table_size; + +comptime { + assert(page_table_alignment == page_table_size); + assert(page_table_size == lib.arch.valid_page_sizes[0]); +} diff --git a/src/privileged/arch/x86/64/registers.zig b/src/privileged/arch/x86/64/registers.zig new file mode 100644 index 0000000..0343113 --- /dev/null +++ b/src/privileged/arch/x86/64/registers.zig @@ -0,0 +1,373 @@ +const lib = @import("lib"); +const assert = lib.assert; +const SimpleR64 = lib.arch.x86_64.registers.SimpleR64; +const RFLAGS = lib.arch.x86_64.registers.RFLAGS; + +const privileged = @import("privileged"); +const PhysicalAddress = lib.PhysicalAddress; +const VirtualAddress = lib.VirtualAddress; +const PhysicalMemoryRegion = lib.PhysicalMemoryRegion; +const VirtualMemoryRegion = lib.VirtualMemoryRegion; +const PhysicalAddressSpace = lib.PhysicalAddressSpace; + +pub const cr3 = packed struct(u64) { + reserved0: u3 = 0, + /// Page-level Write-Through (bit 3 of CR3) — Controls the memory type used to access the first paging + /// structure of the current paging-structure hierarchy. See Section 4.9, “Paging and Memory Typing”. This bit + /// is not used if paging is disabled, with PAE paging, or with 4-level paging or 5-level paging if CR4.PCIDE=1. + PWT: bool = false, + + /// Page-level Cache Disable (bit 4 of CR3) — Controls the memory type used to access the first paging + /// structure of the current paging-structure hierarchy. See Section 4.9, “Paging and Memory Typing”. This bit + /// is not used if paging is disabled, with PAE paging, or with 4-level paging1 or 5-level paging if CR4.PCIDE=1. + PCD: bool = false, + reserved1: u7 = 0, + address: u52 = 0, // get this to be 32-bit compatible + + comptime { + assert(@sizeOf(cr3) == @sizeOf(u64)); + assert(@bitSizeOf(cr3) == @bitSizeOf(u64)); + } + + pub fn fromAddress(physical_address: PhysicalAddress) cr3 { + const PackedAddressType = blk: { + var foo_cr3: cr3 = undefined; + break :blk @TypeOf(@field(foo_cr3, "address")); + }; + + return .{ + .address = @as(PackedAddressType, @intCast(physical_address.value() >> @bitOffsetOf(cr3, "address"))), + }; + } + + pub inline fn read() cr3 { + return asm volatile ("mov %cr3, %[result]" + : [result] "=r" (-> cr3), + ); + } + + pub inline fn write(value: cr3) void { + asm volatile ("mov %[in], %cr3" + : + : [in] "r" (value), + ); + } + + pub inline fn equal(self: cr3, other: cr3) bool { + const self_int = @as(usize, @bitCast(self)); + const other_int = @as(usize, @bitCast(other)); + return self_int == other_int; + } + + pub inline fn getAddress(self: cr3) PhysicalAddress { + return PhysicalAddress.new(@as(u64, self.address) << @bitOffsetOf(cr3, "address")); + } +}; + +/// Contains system control flags that control operating mode and states of the processor. +pub const cr0 = packed struct(u64) { + protected_mode_enable: bool = true, + monitor_coprocessor: bool = false, + emulation: bool = false, + task_switched: bool = false, + extension_type: bool = false, + numeric_error: bool = false, + reserved: u10 = 0, + write_protect: bool = true, + reserved1: u1 = 0, + alignment_mask: bool = false, + reserved2: u10 = 0, + not_write_through: bool = false, + cache_disable: bool = false, + paging: bool = true, + upper_32_bits: u32 = 0, + + pub inline fn read() cr0 { + return asm volatile ("mov %cr0, %[result]" + : [result] "=r" (-> cr0), + ); + } + + pub inline fn write(cr0r: cr0) void { + asm volatile ( + \\mov %[cr0], %cr0 + : + : [cr0] "r" (cr0r), + ); + } +}; + +/// Contains the page-fault linear address (the linear address that caused a page fault). +pub const cr2 = SimpleR64(.cr2); + +/// WARNING: this data structure is only set to be used for 40-bit max physical address bit +/// Contains a group of flags that enable several architectural extensions, and indicate operating system or +/// executive support for specific processor capabilities. Bits CR4[63:32] can only be used for IA-32e mode only +/// features that are enabled after entering 64-bit mode. Bits CR4[63:32] do not have any effect outside of IA-32e +/// mode. +pub const cr4 = packed struct(u64) { + vme: bool = false, + pvi: bool = false, + timestamp_disable: bool = false, + debugging_extensions: bool = false, + page_size_extensions: bool = false, + physical_address_extensions: bool = true, + machine_check_enable: bool = false, + page_global_enable: bool = true, + performance_monitoring_counter_enable: bool = true, + OSFXSR: bool = true, + OSXMMEXCPT: bool = false, + user_mode_instruction: bool = false, + linear_addresses_57_bit: bool = false, + vmx_enable: bool = false, + smx_enable: bool = false, + fs_gs_base_enable: bool = false, + pcid_enable: bool = false, + OSXSAVE: bool = false, + key_locker_enable: bool = false, + supervisor_mode_execution_prevention_enable: bool = false, + supervisor_mode_access_prevention_enable: bool = false, + protection_key_user_mode_enable: bool = false, + control_flow_enforcement_technology: bool = false, + protection_key_supervisor_mode_enable: bool = false, + reserved: u40 = 0, + + pub fn read() cr4 { + return asm volatile ( + \\mov %cr4, %[result] + : [result] "=r" (-> cr4), + ); + } + + pub fn write(cr4_register: cr4) void { + asm volatile ( + \\mov %[cr4], %cr4 + : + : [cr4] "r" (cr4_register), + ); + } +}; + +/// Provides read and write access to the Task Priority Register (TPR). It specifies the priority threshold +/// value that operating systems use to control the priority class of external interrupts allowed to interrupt the +/// processor. This register is available only in 64-bit mode. However, interrupt filtering continues to apply in +/// compatibility mode. +pub const cr8 = SimpleR64(.cr8); + +pub const IA32_LSTAR = SimpleMSR(0xC0000082); +pub const IA32_FMASK = SimpleMSR(0xC0000084); +pub const syscall_mask = (1 << @bitOffsetOf(RFLAGS, "CF")) | + (1 << @bitOffsetOf(RFLAGS, "PF")) | + (1 << @bitOffsetOf(RFLAGS, "AF")) | + (1 << @bitOffsetOf(RFLAGS, "ZF")) | + (1 << @bitOffsetOf(RFLAGS, "SF")) | + (1 << @bitOffsetOf(RFLAGS, "TF")) | + (1 << @bitOffsetOf(RFLAGS, "IF")) | + (1 << @bitOffsetOf(RFLAGS, "DF")) | + (1 << @bitOffsetOf(RFLAGS, "OF")) | + (1 << @bitOffsetOf(RFLAGS, "IOPL")) | + (1 << @bitOffsetOf(RFLAGS, "NT")) | + (1 << @bitOffsetOf(RFLAGS, "RF")) | + (1 << @bitOffsetOf(RFLAGS, "AC")) | + (1 << @bitOffsetOf(RFLAGS, "ID")); + +pub const IA32_FS_BASE = SimpleMSR(0xC0000100); +pub const IA32_GS_BASE = SimpleMSR(0xC0000101); +pub const IA32_KERNEL_GS_BASE = SimpleMSR(0xC0000102); + +pub fn SimpleMSR(comptime msr: u32) type { + return struct { + pub inline fn read() u64 { + var low: u32 = undefined; + var high: u32 = undefined; + + asm volatile ("rdmsr" + : [_] "={eax}" (low), + [_] "={edx}" (high), + : [_] "{ecx}" (msr), + ); + return (@as(u64, high) << 32) | low; + } + + pub inline fn write(value: u64) void { + const low = @as(u32, @truncate(value)); + const high = @as(u32, @truncate(value >> 32)); + + asm volatile ("wrmsr" + : + : [_] "{eax}" (low), + [_] "{edx}" (high), + [_] "{ecx}" (msr), + ); + } + }; +} + +pub const MemoryType = enum(u8) { + uncacheable = 0, + write_combining = 1, + reserved0 = 2, + reserved1 = 3, + write_through = 4, + write_protected = 5, + write_back = 6, + uncached = 7, +}; + +pub const IA32_PAT = extern struct { + page_attributes: [8]MemoryType, + + const MSR = SimpleMSR(0x277); + + pub fn read() IA32_PAT { + return @as(IA32_PAT, @bitCast(MSR.read())); + } + + pub fn write(pat: IA32_PAT) void { + MSR.write(@as(u64, @bitCast(pat))); + } +}; + +pub const IA32_EFER = packed struct(u64) { + /// Syscall Enable - syscall, sysret + SCE: bool = false, + reserved0: u7 = 0, + /// Long Mode Enable + LME: bool = false, + reserved1: bool = false, + /// Long Mode Active + LMA: bool = false, + /// Enables page access restriction by preventing instruction fetches from PAE pages with the XD bit set + NXE: bool = false, + SVME: bool = false, + LMSLE: bool = false, + FFXSR: bool = false, + TCE: bool = false, + reserved2: u48 = 0, + + comptime { + assert(@sizeOf(u64) == @sizeOf(IA32_EFER)); + } + + pub const MSR = SimpleMSR(0xC0000080); + + pub fn read() IA32_EFER { + const result = MSR.read(); + const typed_result = @as(IA32_EFER, @bitCast(result)); + return typed_result; + } + + pub fn write(typed_value: IA32_EFER) void { + const value = @as(u64, @bitCast(typed_value)); + MSR.write(value); + } +}; + +pub const IA32_STAR = packed struct(u64) { + reserved: u32 = 0, + kernel_cs: u16 = 0, + user_cs_anchor: u16 = 0, + + pub const MSR = SimpleMSR(0xC0000081); + + pub fn read() @This() { + const result = MSR.read(); + const typed_result = @as(@This(), @bitCast(result)); + return typed_result; + } + + pub fn write(typed_value: @This()) void { + const value = @as(u64, @bitCast(typed_value)); + MSR.write(value); + } +}; + +pub const IA32_APIC_BASE = packed struct(u64) { + reserved0: u8 = 0, + bsp: bool = false, + reserved1: u1 = 0, + extended: bool = false, + global_enable: bool = false, + address: u24, + reserved2: u28 = 0, + + pub const MSR = SimpleMSR(0x0000001B); + + pub inline fn read() IA32_APIC_BASE { + const result = MSR.read(); + const typed_result = @as(IA32_APIC_BASE, @bitCast(result)); + return typed_result; + } + + pub inline fn write(typed_value: IA32_APIC_BASE) void { + const value = @as(u64, @bitCast(typed_value)); + MSR.write(value); + } + + pub inline fn getAddress(ia32_apic_base: IA32_APIC_BASE) PhysicalAddress { + return PhysicalAddress.new(@as(u64, ia32_apic_base.address) << @bitOffsetOf(IA32_APIC_BASE, "address")); + } +}; + +pub const XCR0 = packed struct(u64) { + X87: bool = true, + SSE: bool = true, + AVX: bool = false, + BNDREG: bool = false, + BNDCSR: bool = false, + opmask: bool = false, + ZMM_hi256: bool = false, + Hi16_ZMM: bool = false, + _: bool = false, + PKRU: bool = false, + reserved: u7 = 0, + AMX_TILECFG: bool = false, + AMX_TILEDATA: bool = false, + reserved1: u45 = 0, + + pub inline fn read() XCR0 { + var eax: u32 = undefined; + var edx: u32 = undefined; + + asm volatile ( + \\xgetbv + : [eax] "={eax}" (eax), + [edx] "={edx}" (edx), + : [ecx] "i" (@as(u32, 0)), + ); + + const xcr0 = @as(XCR0, @bitCast(@as(u64, edx) << 32 | eax)); + return xcr0; + } + + pub inline fn write(xcr0: XCR0) void { + const bitcasted_xcr0 = @as(u64, @bitCast(xcr0)); + const eax = @as(u32, @truncate(bitcasted_xcr0)); + const edx = @as(u32, @truncate(bitcasted_xcr0 >> 32)); + + asm volatile ( + \\xsetbv + : + : [eax] "{eax}" (eax), + [edx] "{edx}" (edx), + [ecx] "{edx}" (@as(u32, 0)), + ); + } +}; + +pub const FSBASE = struct { + pub inline fn write(value: u64) void { + asm volatile ( + \\wrfsbase + : + : [value] "r" (value), + ); + } + + pub inline fn read() u64 { + return asm volatile ( + \\wrfsbase + : [value] "r" (-> u64), + ); + } +}; diff --git a/src/privileged/arch/x86/common.zig b/src/privileged/arch/x86/common.zig new file mode 100644 index 0000000..60c4d51 --- /dev/null +++ b/src/privileged/arch/x86/common.zig @@ -0,0 +1,65 @@ +const lib = @import("lib"); +const assert = lib.assert; + +pub const SegmentDescriptor = extern struct { + limit: u16, + address: u64 align(2), + + comptime { + assert(@sizeOf(@This()) == 10); + } +}; + +pub inline fn stopCPU() noreturn { + while (true) { + asm volatile ( + \\cli + \\hlt + \\pause + ::: "memory"); + } +} + +pub inline fn disableInterrupts() void { + asm volatile ("cli" ::: "memory"); +} + +pub inline fn read(comptime T: type, port: u16) T { + return switch (T) { + u8 => asm volatile ("inb %[port], %[result]" + : [result] "={al}" (-> u8), + : [port] "N{dx}" (port), + ), + u16 => asm volatile ("inw %[port], %[result]" + : [result] "={ax}" (-> u16), + : [port] "N{dx}" (port), + ), + u32 => asm volatile ("inl %[port], %[result]" + : [result] "={eax}" (-> u32), + : [port] "N{dx}" (port), + ), + + else => unreachable, + }; +} + +pub inline fn write(comptime T: type, port: u16, value: T) void { + switch (T) { + u8 => asm volatile ("outb %[value], %[port]" + : + : [value] "{al}" (value), + [port] "N{dx}" (port), + ), + u16 => asm volatile ("outw %[value], %[port]" + : + : [value] "{ax}" (value), + [port] "N{dx}" (port), + ), + u32 => asm volatile ("outl %[value], %[port]" + : + : [value] "{eax}" (value), + [port] "N{dx}" (port), + ), + else => unreachable, + } +} diff --git a/src/privileged/arch/x86_64.zig b/src/privileged/arch/x86_64.zig new file mode 100644 index 0000000..92d24bc --- /dev/null +++ b/src/privileged/arch/x86_64.zig @@ -0,0 +1,227 @@ +const x86 = @import("x86/common.zig"); +pub usingnamespace x86; + +const lib = @import("lib"); +const assert = lib.assert; +const cpuid = lib.arch.x86_64.CPUID; + +const privileged = @import("privileged"); + +pub const APIC = @import("x86/64/apic.zig"); +pub const io = @import("x86/64/io.zig"); +pub const paging = @import("x86/64/paging.zig"); +pub const registers = @import("x86/64/registers.zig"); + +pub const valid_page_sizes = privileged.arch.valid_page_sizes; +pub const page_size = valid_page_sizes[0]; +pub const reasonable_page_size = valid_page_sizes[1]; + +pub fn page_shifter(comptime asked_page_size: comptime_int) comptime_int { + return @ctz(@as(u32, asked_page_size)); +} + +/// Returns the maximum number bits a physical address is allowed to have in this CPU +pub inline fn get_max_physical_address_bit() u6 { + return @as(u6, @truncate(cpuid(0x80000008).eax)); +} + +pub const GDT = extern struct { + pub const Entry = packed struct(u64) { + limit_low: u16, + base_low: u16, + base_mid: u8, + access: packed struct(u8) { + accessed: bool, + read_write: bool, + direction_conforming: bool, + executable: bool, + code_data_segment: bool, + dpl: u2, + present: bool, + }, + limit_high: u4, + reserved: u1 = 0, + long_mode: bool, + size_flag: bool, + granularity: bool, + base_high: u8 = 0, + + pub const null_entry = Entry{ + .limit_low = 0, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = false, + .direction_conforming = false, + .executable = false, + .code_data_segment = false, + .dpl = 0, + .present = false, + }, + .limit_high = 0, + .long_mode = false, + .size_flag = false, + .granularity = false, + }; + + pub const code_16 = Entry{ + .limit_low = 0xffff, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = true, + .direction_conforming = false, + .executable = true, + .code_data_segment = true, + .dpl = 0, + .present = true, + }, + .limit_high = 0, + .long_mode = false, + .size_flag = false, + .granularity = false, + }; + + pub const data_16 = Entry{ + .limit_low = 0xffff, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = true, + .direction_conforming = false, + .executable = false, + .code_data_segment = true, + .dpl = 0, + .present = true, + }, + .limit_high = 0, + .long_mode = false, + .size_flag = false, + .granularity = false, + }; + + pub const code_32 = Entry{ + .limit_low = 0xffff, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = true, + .direction_conforming = false, + .executable = true, + .code_data_segment = true, + .dpl = 0, + .present = true, + }, + .limit_high = 0xf, + .long_mode = false, + .size_flag = true, + .granularity = true, + }; + + pub const data_32 = Entry{ + .limit_low = 0xffff, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = true, + .direction_conforming = false, + .executable = false, + .code_data_segment = true, + .dpl = 0, + .present = true, + }, + .limit_high = 0xf, + .long_mode = false, + .size_flag = true, + .granularity = true, + }; + + pub const code_64 = Entry{ + .limit_low = 0xffff, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = true, + .direction_conforming = false, + .executable = true, + .code_data_segment = true, + .dpl = 0, + .present = true, + }, + .limit_high = 0xf, + .long_mode = true, + .size_flag = false, + .granularity = false, + }; + + pub const data_64 = Entry{ + .limit_low = 0xffff, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = true, + .direction_conforming = false, + .executable = false, + .code_data_segment = true, + .dpl = 0, + .present = true, + }, + .limit_high = 0xf, + .long_mode = false, + .size_flag = false, + .granularity = false, + }; + + pub const user_data_64 = Entry{ + .limit_low = 0xffff, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = true, + .direction_conforming = false, + .executable = false, + .code_data_segment = true, + .dpl = 3, + .present = true, + }, + .limit_high = 0xf, + .long_mode = false, + .size_flag = false, + .granularity = true, + }; + + pub const user_code_64 = Entry{ + .limit_low = 0xffff, + .base_low = 0, + .base_mid = 0, + .access = .{ + .accessed = false, + .read_write = true, + .direction_conforming = false, + .executable = true, + .code_data_segment = true, + .dpl = 3, + .present = true, + }, + .limit_high = 0xf, + .long_mode = true, + .size_flag = false, + .granularity = true, + }; + }; + + pub const Descriptor = x86.SegmentDescriptor; +}; + +pub const TicksPerMS = extern struct { + tsc: u32, + lapic: u32, +}; diff --git a/src/rise.zig b/src/rise.zig new file mode 100644 index 0000000..5ac47dc --- /dev/null +++ b/src/rise.zig @@ -0,0 +1,23 @@ +const lib = @import("lib"); + +pub const arch = @import("rise/arch.zig"); +pub const capabilities = @import("rise/capabilities.zig"); +pub const syscall = @import("rise/syscall.zig"); + +/// This struct is the shared part that the user and the cpu see +pub const UserScheduler = extern struct { + self: *UserScheduler, + disabled: bool, + has_work: bool, + core_id: u32, + setup_stack: [lib.arch.valid_page_sizes[0]]u8 align(lib.arch.stack_alignment), + setup_stack_lock: lib.Atomic(bool), + + pub inline fn architectureSpecific(user_scheduler: *UserScheduler) *arch.UserScheduler { + return @fieldParentPtr(arch.UserScheduler, "generic", user_scheduler); + } +}; + +pub const CommandBuffer = struct { + foo: u32, +}; diff --git a/src/rise/arch.zig b/src/rise/arch.zig new file mode 100644 index 0000000..c82ed6d --- /dev/null +++ b/src/rise/arch.zig @@ -0,0 +1,7 @@ +const lib = @import("lib"); +pub usingnamespace switch (lib.cpu.arch) { + .x86_64 => x86_64, + else => @compileError("Architecture not supported"), +}; + +const x86_64 = @import("arch/x64_64.zig"); diff --git a/src/rise/arch/x64_64.zig b/src/rise/arch/x64_64.zig new file mode 100644 index 0000000..f407251 --- /dev/null +++ b/src/rise/arch/x64_64.zig @@ -0,0 +1,155 @@ +const lib = @import("lib"); +const assert = lib.assert; +const rise = @import("rise"); + +pub const UserScheduler = extern struct { + generic: rise.UserScheduler, + disabled_save_area: RegisterArena, +}; + +pub const RegisterArena = extern struct { + fpu: FPU align(lib.arch.stack_alignment), + registers: rise.arch.Registers, + + pub fn contextSwitch(register_arena: *align(lib.arch.stack_alignment) const RegisterArena) noreturn { + assert(lib.isAligned(@intFromPtr(register_arena), lib.arch.stack_alignment)); + //lib.log.debug("ASDASD: {}", .{register_arena}); + register_arena.fpu.load(); + register_arena.registers.restore(); + } +}; + +pub const Registers = extern struct { + r15: u64, + r14: u64, + r13: u64, + r12: u64, + rbp: u64, + rbx: u64, + r11: u64, + r10: u64, + r9: u64, + r8: u64, + rax: u64, + rcx: u64, + rdx: u64, + rsi: u64, + rdi: u64, + rip: u64, + rflags: lib.arch.x86_64.registers.RFLAGS, + rsp: u64, + + pub fn restore(registers: *const Registers) noreturn { + const fmt = lib.comptimePrint; + asm volatile (fmt( + "mov {}(%[registers]), %r15\n\t" ++ + "mov {}(%[registers]), %r14\n\t" ++ + "mov {}(%[registers]), %r13\n\t" ++ + "mov {}(%[registers]), %r12\n\t" ++ + "mov {}(%[registers]), %rbp\n\t" ++ + "mov {}(%[registers]), %rbx\n\t" ++ + "mov {}(%[registers]), %r11\n\t" ++ + "mov {}(%[registers]), %r10\n\t" ++ + "mov {}(%[registers]), %r9\n\t" ++ + "mov {}(%[registers]), %r8\n\t" ++ + "mov {}(%[registers]), %rax\n\t" ++ + "mov {}(%[registers]), %rcx\n\t" ++ + "mov {}(%[registers]), %rdx\n\t" ++ + "mov {}(%[registers]), %rsi\n\t" ++ + "pushq %[ss]\n\t" ++ + "pushq {}(%[registers])\n\t" ++ + "pushq {}(%[registers])\n\t" ++ + "pushq %[cs]\n\t" ++ + "pushq {}(%[registers])\n\t" ++ + "mov {}(%[registers]), %rdi\n\t" ++ + "iretq\n\t" ++ + "1: jmp 1b", + + .{ + @offsetOf(Registers, "r15"), + @offsetOf(Registers, "r14"), + @offsetOf(Registers, "r13"), + @offsetOf(Registers, "r12"), + @offsetOf(Registers, "rbp"), + @offsetOf(Registers, "rbx"), + @offsetOf(Registers, "r11"), + @offsetOf(Registers, "r10"), + @offsetOf(Registers, "r9"), + @offsetOf(Registers, "r8"), + @offsetOf(Registers, "rax"), + @offsetOf(Registers, "rcx"), + @offsetOf(Registers, "rdx"), + @offsetOf(Registers, "rsi"), + @offsetOf(Registers, "rsp"), + @offsetOf(Registers, "rflags"), + @offsetOf(Registers, "rip"), + @offsetOf(Registers, "rdi"), + }, + ) + : + : [registers] "{rdi}" (registers), + [ss] "i" (rise.arch.user_data_selector), + [cs] "i" (rise.arch.user_code_selector), + : "memory" + ); + + unreachable; + } +}; + +pub const FPU = extern struct { + fcw: u16, + fsw: u16, + ftw: u8, + reserved: u8 = 0, + fop: u16, + fpu_ip1: u32, + fpu_ip2: u16, + reserved0: u16 = 0, + fpu_dp1: u32, + fpu_dp2: u16, + reserved1: u16 = 0, + mxcsr: u32, + mxcsr_mask: u32, + st: [8][2]u64, + xmm: [16][2]u64, + reserved2: [12]u64 = .{0} ** 12, + + pub inline fn load(fpu: *align(lib.arch.stack_alignment) const FPU) void { + assert(@intFromPtr(fpu) % lib.arch.stack_alignment == 0); + asm volatile ( + \\fxrstor %[fpu] + : + : [fpu] "*p" (fpu), + : "memory" + ); + } +}; + +pub const user_code_selector = 0x43; +pub const user_data_selector = 0x3b; + +pub inline fn syscall(options: rise.syscall.Options, arguments: rise.syscall.Arguments) rise.syscall.Result { + var first: rise.syscall.Result.Rise.First = undefined; + var second: rise.syscall.Result.Rise.Second = undefined; + asm volatile ( + \\syscall + : [rax] "={rax}" (first), + [rdx] "={rdx}" (second), + : [options] "{rax}" (options), + [arg0] "{rdi}" (arguments[0]), + [arg1] "{rsi}" (arguments[1]), + [arg2] "{rdx}" (arguments[2]), + [arg3] "{r10}" (arguments[3]), + [arg4] "{r8}" (arguments[4]), + [arg5] "{r9}" (arguments[5]), + : "rcx", "r11", "rsp", "memory" + ); + + return .{ + .rise = .{ + .first = first, + .second = second, + }, + }; +} diff --git a/src/rise/capabilities.zig b/src/rise/capabilities.zig new file mode 100644 index 0000000..8b42ae0 --- /dev/null +++ b/src/rise/capabilities.zig @@ -0,0 +1,381 @@ +const lib = @import("lib"); +const assert = lib.assert; +const PhysicalAddress = lib.PhysicalAddress; + +const rise = @import("rise"); +const syscall = rise.syscall; + +const Capabilities = @This(); + +pub const Type = enum(u8) { + io, // primitive + cpu, // primitive + ram, // primitive + cpu_memory, // non-primitive Barrelfish: frame + boot, + process, // Temporarily available + page_table, // Barrelfish: vnode + // TODO: device_memory, // primitive + // scheduler, + // irq_table, + + // _, + + pub const Type = u8; + + pub const Mappable = enum { + cpu_memory, + page_table, + + pub inline fn toCapability(mappable: Mappable) Capabilities.Type { + return switch (mappable) { + inline else => |mappable_cap| @field(Capabilities.Type, @tagName(mappable_cap)), + }; + } + }; +}; + +pub const Subtype = u16; +pub const AllTypes = Type; + +pub fn CommandBuilder(comptime list: []const []const u8) type { + const capability_base_command_list = .{ + "copy", + "mint", + "retype", + "delete", + "revoke", + "create", + } ++ list; + const enum_fields = lib.enumAddNames(&.{}, capability_base_command_list); + + // TODO: make this non-exhaustive enums + // PROBLEM: https://github.com/ziglang/zig/issues/12250 + // Currently waiting on this since this will enable some comptime magic + const result = @Type(.{ + .Enum = .{ + .tag_type = Subtype, + .fields = enum_fields, + .decls = &.{}, + .is_exhaustive = true, + }, + }); + return result; +} + +/// Takes some names and integers. Then values are added to the Command enum for an specific capability +/// The number is an offset of the fields with respect to the base command enum fields +pub fn Command(comptime capability: Type) type { + const extra_command_list = switch (capability) { + .io => .{ + "log", + }, + .cpu => .{ + "get_core_id", + "shutdown", + "get_command_buffer", + }, + .ram => [_][]const u8{}, + .cpu_memory => .{ + "allocate", + }, + .boot => .{ + "get_bundle_size", + "get_bundle_file_list_size", + }, + .process => .{ + "exit", + }, + .page_table => [_][]const u8{}, + }; + + return CommandBuilder(&extra_command_list); +} + +const success = 0; +const first_valid_error = success + 1; + +pub fn ErrorSet(comptime error_names: []const []const u8) type { + return lib.ErrorSet(error_names, &.{ + .{ + .name = "forbidden", + .value = first_valid_error + 0, + }, + .{ + .name = "corrupted_input", + .value = first_valid_error + 1, + }, + .{ + .name = "invalid_input", + .value = first_valid_error + 2, + }, + }); +} + +const raw_argument_count = @typeInfo(syscall.Arguments).Array.len; + +pub fn Syscall(comptime capability_type: Type, comptime command_type: Command(capability_type)) type { + const Types = switch (capability_type) { + .io => switch (command_type) { + .copy, .mint, .retype, .delete, .revoke, .create => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = void; + pub const Arguments = void; + }, + .log => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = usize; + pub const Arguments = []const u8; + + inline fn toResult(raw_result: syscall.Result.Rise) Result { + return raw_result.second; + } + + inline fn resultToRaw(result: Result) syscall.Result { + return syscall.Result{ + .rise = .{ + .first = .{}, + .second = result, + }, + }; + } + + inline fn argumentsToRaw(arguments: Arguments) syscall.Arguments { + const result = [2]usize{ @intFromPtr(arguments.ptr), arguments.len }; + return result ++ .{0} ** (raw_argument_count - result.len); + } + + inline fn toArguments(raw_arguments: syscall.Arguments) !Arguments { + const message_ptr = @as(?[*]const u8, @ptrFromInt(raw_arguments[0])) orelse return error.invalid_input; + const message_len = raw_arguments[1]; + if (message_len == 0) return error.invalid_input; + const message = message_ptr[0..message_len]; + return message; + } + }, + }, + .cpu => switch (command_type) { + .copy, .mint, .retype, .delete, .revoke, .create => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = void; + pub const Arguments = void; + }, + .get_core_id => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = u32; + pub const Arguments = void; + + inline fn toResult(raw_result: syscall.Result.Rise) Result { + return @as(Result, @intCast(raw_result.second)); + } + + inline fn resultToRaw(result: Result) syscall.Result { + return syscall.Result{ + .rise = .{ + .first = .{}, + .second = result, + }, + }; + } + }, + .shutdown => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = noreturn; + pub const Arguments = void; + + pub const toResult = @compileError("noreturn unexpectedly returned"); + }, + .get_command_buffer => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = noreturn; + pub const Arguments = *rise.CommandBuffer; + + pub const toResult = @compileError("noreturn unexpectedly returned"); + + inline fn toArguments(raw_arguments: syscall.Arguments) !Arguments { + const ptr = @as(?*rise.CommandBuffer, @ptrFromInt(raw_arguments[0])) orelse return error.invalid_input; + return ptr; + } + + inline fn argumentsToRaw(arguments: Arguments) syscall.Arguments { + const result = [1]usize{@intFromPtr(arguments)}; + return result ++ .{0} ** (raw_argument_count - result.len); + } + }, + }, + .ram => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = void; + pub const Arguments = void; + }, + .cpu_memory => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{ + "OutOfMemory", + }); + pub const Result = PhysicalAddress; + pub const Arguments = usize; + + inline fn toResult(raw_result: syscall.Result.Rise) Result { + return PhysicalAddress.new(raw_result.second); + } + + inline fn resultToRaw(result: Result) syscall.Result { + return syscall.Result{ + .rise = .{ + .first = .{}, + .second = result.value(), + }, + }; + } + + inline fn toArguments(raw_arguments: syscall.Arguments) !Arguments { + const size = raw_arguments[0]; + return size; + } + + inline fn argumentsToRaw(arguments: Arguments) syscall.Arguments { + const result = [1]usize{arguments}; + return result ++ .{0} ** (raw_argument_count - result.len); + } + }, + .boot => switch (command_type) { + .get_bundle_file_list_size, .get_bundle_size => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{ + "buffer_too_small", + }); + pub const Result = usize; + pub const Arguments = void; + + inline fn resultToRaw(result: Result) syscall.Result { + return syscall.Result{ + .rise = .{ + .first = .{}, + .second = result, + }, + }; + } + + inline fn toResult(raw_result: syscall.Result.Rise) Result { + return raw_result.second; + } + }, + else => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{ + "buffer_too_small", + }); + pub const Result = void; + pub const Arguments = void; + }, + }, + .process => switch (command_type) { + .exit => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = noreturn; + pub const Arguments = bool; + + inline fn toArguments(raw_arguments: syscall.Arguments) !Arguments { + const result = raw_arguments[0] != 0; + return result; + } + inline fn argumentsToRaw(arguments: Arguments) syscall.Arguments { + const result = [1]usize{@intFromBool(arguments)}; + return result ++ .{0} ** (raw_argument_count - result.len); + } + }, + else => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = void; + pub const Arguments = void; + }, + }, + .page_table => switch (command_type) { + else => struct { + pub const ErrorSet = Capabilities.ErrorSet(&.{}); + pub const Result = void; + pub const Arguments = void; + }, + }, + // else => @compileError("TODO: " ++ @tagName(capability)), + }; + + return struct { + pub const ErrorSet = Types.ErrorSet; + pub const Result = Types.Result; + pub const Arguments = Types.Arguments; + pub const toResult = Types.toResult; + pub const toArguments = if (Arguments != void) + Types.toArguments + else + struct { + fn lambda(raw_arguments: syscall.Arguments) error{}!void { + _ = raw_arguments; + return {}; + } + }.lambda; + pub const capability = capability_type; + pub const command = command_type; + + pub inline fn resultToRaw(result: Result) syscall.Result { + return if (@hasDecl(Types, "resultToRaw")) blk: { + comptime assert(Result != void and Result != noreturn); + break :blk Types.resultToRaw(result); + } else blk: { + if (Result != void) { + @compileError("expected void type, got " ++ @typeName(Result) ++ ". You forgot to implement a resultToRaw function" ++ " for (" ++ @tagName(capability) ++ ", " ++ @tagName(command) ++ ")."); + } + + break :blk syscall.Result{ + .rise = .{ + .first = .{}, + .second = 0, + }, + }; + }; + } + + pub inline fn errorToRaw(err: @This().ErrorSet.Error) syscall.Result { + const error_enum = switch (err) { + inline else => |comptime_error| @field(@This().ErrorSet.Enum, @errorName(comptime_error)), + }; + return syscall.Result{ + .rise = .{ + .first = .{ + .@"error" = @intFromEnum(error_enum), + }, + .second = 0, + }, + }; + } + + /// This is not meant to be called in the CPU driver + pub fn blocking(arguments: Arguments) @This().ErrorSet.Error!Result { + const raw_arguments = if (Arguments != void) Types.argumentsToRaw(arguments) else [1]usize{0} ** raw_argument_count; + // TODO: make this more reliable and robust? + const options = rise.syscall.Options{ + .rise = .{ + .type = capability, + .command = @intFromEnum(command), + }, + }; + + const raw_result = rise.arch.syscall(options, raw_arguments); + + const raw_error_value = raw_result.rise.first.@"error"; + comptime { + assert(!@hasField(@This().ErrorSet.Enum, "ok")); + assert(!@hasField(@This().ErrorSet.Enum, "success")); + assert(lib.enumFields(@This().ErrorSet.Enum)[0].value == first_valid_error); + } + + return switch (raw_error_value) { + success => switch (Result) { + noreturn => unreachable, + else => toResult(raw_result.rise), + }, + else => switch (@as(@This().ErrorSet.Enum, @enumFromInt(raw_error_value))) { + inline else => |comptime_error_enum| @field(@This().ErrorSet.Error, @tagName(comptime_error_enum)), + }, + }; + } + }; +} diff --git a/src/rise/syscall.zig b/src/rise/syscall.zig new file mode 100644 index 0000000..55da669 --- /dev/null +++ b/src/rise/syscall.zig @@ -0,0 +1,117 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.Syscall); + +const rise = @import("rise"); +const capabilities = rise.capabilities; + +pub const argument_count = 6; +pub const Arguments = [argument_count]usize; + +pub const Convention = enum(u1) { + linux = 0, + rise = 1, +}; + +pub const Options = extern union { + general: General, + rise: Rise, + linux: Linux, + + pub const General = packed struct(u64) { + number: Number, + convention: Convention, + + pub const Number = lib.IntType(.unsigned, union_space_bits); + + comptime { + assertSize(@This()); + } + + pub inline fn getNumberInteger(general: General, comptime convention: Convention) NumberIntegerType(convention) { + const options_integer = @as(u64, @bitCast(general)); + return @as(NumberIntegerType(convention), @truncate(options_integer)); + } + + pub fn NumberIntegerType(comptime convention: Convention) type { + return switch (convention) { + .rise => Rise.IDInteger, + .linux => u64, + }; + } + }; + + pub const Rise = packed struct(u64) { + type: capabilities.Type, + command: capabilities.Subtype, + reserved: lib.IntType(.unsigned, @bitSizeOf(u64) - @bitSizeOf(capabilities.Type) - @bitSizeOf(capabilities.Subtype) - @bitSizeOf(Convention)) = 0, + convention: Convention = .rise, + + comptime { + Options.assertSize(@This()); + } + + const IDInteger = u16; + pub const ID = enum(IDInteger) { + qemu_exit = 0, + print = 1, + }; + }; + + pub const Linux = enum(u64) { + _, + comptime { + Options.assertSize(@This()); + } + }; + + pub const union_space_bits = @bitSizeOf(u64) - @bitSizeOf(Convention); + + fn assertSize(comptime T: type) void { + assert(@sizeOf(T) == @sizeOf(u64)); + assert(@bitSizeOf(T) == @bitSizeOf(u64)); + } + + comptime { + assertSize(@This()); + } +}; + +pub const Result = extern union { + general: General, + rise: Rise, + linux: Linux, + + pub const General = extern struct { + first: packed struct(u64) { + argument: u63, + convention: Convention, + }, + second: u64, + }; + + pub const Rise = extern struct { + first: First, + second: Second, + + pub const First = packed struct(u64) { + padding1: u32 = 0, + @"error": u16 = 0, + padding2: u8 = 0, + padding3: u7 = 0, + convention: Convention = .rise, + }; + + pub const Second = u64; + }; + + pub const Linux = extern struct { + result: u64, + reserved: u64 = 0, + }; + + fn assertSize(comptime T: type) void { + assert(@sizeOf(T) == @sizeOf(u64)); + assert(@bitSizeOf(T) == @bitSizeOf(u64)); + } +}; diff --git a/src/user.zig b/src/user.zig new file mode 100644 index 0000000..09f7c6b --- /dev/null +++ b/src/user.zig @@ -0,0 +1,142 @@ +const lib = @import("lib"); +const log = lib.log; +const assert = lib.assert; +const ExecutionMode = lib.Syscall.ExecutionMode; + +const rise = @import("rise"); +const capabilities = rise.capabilities; +pub const Syscall = rise.capabilities.Syscall; + +pub const arch = @import("user/arch.zig"); +const core_state = @import("user/core_state.zig"); +pub const CoreState = core_state.CoreState; +pub const PinnedState = core_state.PinnedState; +pub const libc = @import("user/libc.zig"); +pub const thread = @import("user/thread.zig"); +pub const process = @import("user/process.zig"); +const vas = @import("user/virtual_address_space.zig"); +const VirtualAddress = lib.VirtualAddress; +pub const VirtualAddressSpace = vas.VirtualAddressSpace; +pub const MMUAwareVirtualAddressSpace = vas.MMUAwareVirtualAddressSpace; + +pub const PhysicalMap = @import("user/physical_map.zig").PhysicalMap; +pub const PhysicalMemoryRegion = @import("user/physical_memory_region.zig").PhysicalMemoryRegion; +pub const SlotAllocator = @import("user/slot_allocator.zig").SlotAllocator; + +comptime { + @export(arch._start, .{ .linkage = .Strong, .name = "_start" }); +} + +pub const writer = lib.Writer(void, Writer.Error, Writer.write){ .context = {} }; +const Writer = extern struct { + const syscall = Syscall(.io, .log); + const Error = Writer.syscall.ErrorSet.Error; + + fn write(_: void, bytes: []const u8) Error!usize { + const result = try Writer.syscall.blocking(bytes); + return result; + } +}; + +pub const std_options = struct { + pub fn logFn(comptime level: lib.std.log.Level, comptime scope: @TypeOf(.EnumLiteral), comptime format: []const u8, args: anytype) void { + lib.format(writer, format, args) catch unreachable; + writer.writeByte('\n') catch unreachable; + _ = scope; + _ = level; + } +}; + +pub fn zigPanic(message: []const u8, _: ?*lib.StackTrace, _: ?usize) noreturn { + @call(.always_inline, panic, .{ "{s}", .{message} }); +} + +pub fn panic(comptime format: []const u8, arguments: anytype) noreturn { + lib.log.scoped(.PANIC).err(format, arguments); + while (true) { + Syscall(.process, .exit).blocking(false) catch |err| log.err("Exit failed: {}", .{err}); + } +} + +pub const Scheduler = extern struct { + time_slice: u32, + core_id: u32, + core_state: CoreState, +}; + +pub inline fn currentScheduler() *Scheduler { + return arch.currentScheduler(); +} + +fn schedulerInitDisabled(scheduler: *arch.Scheduler) void { + // Architecture-specific initialization + scheduler.generic.time_slice = 1; + // TODO: capabilities +} + +pub var is_init = false; +pub var command_buffer: rise.CommandBuffer = undefined; + +pub export fn start(scheduler: *arch.Scheduler, arg_init: bool) callconv(.C) noreturn { + assert(arg_init); + is_init = arg_init; + if (is_init) { + assert(scheduler.common.generic.setup_stack_lock.load(.Monotonic)); + } + assert(scheduler.common.generic.disabled); + scheduler.initDisabled(); + @panic("TWTQWD"); + // command_buffer = Syscall(.cpu, .get_command_buffer).blocking(&command_buffer) catch @panic("Unable to get command buffer"); +} + +// export fn riseInitializeDisabled(scheduler: *arch.Scheduler, arg_init: bool) callconv(.C) noreturn { +// // TODO: delete when this code is unnecessary. In the meanwhile it counts as a sanity check +// assert(arg_init); +// is_init = arg_init; +// schedulerInitDisabled(scheduler); +// thread.initDisabled(scheduler); +// } + +// Barrelfish: vregion +pub const VirtualMemoryRegion = extern struct { + virtual_address_space: *VirtualAddressSpace, + physical_region: *PhysicalMemoryRegion, + offset: usize, + size: usize, + address: VirtualAddress, + flags: Flags, + next: ?*VirtualMemoryRegion = null, + + pub const Flags = packed struct(u8) { + read: bool = false, + write: bool = false, + execute: bool = false, + cache_disabled: bool = false, + preferred_page_size: u2 = 0, + write_combining: bool = false, + reserved: u1 = 0, + }; +}; + +pub const MoreCore = extern struct { + const InitializationError = error{ + invalid_page_size, + }; + + pub fn init(page_size: usize) InitializationError!void { + blk: inline for (lib.arch.valid_page_sizes) |valid_page_size| { + if (valid_page_size == page_size) break :blk; + } else { + return InitializationError.invalid_page_size; + } + + const morecore_state = process.getMoreCoreState(); + morecore_state.mmu_state = try MMUAwareVirtualAddressSpace.initAligned(SlotAllocator.getDefault(), lib.arch.valid_page_sizes[1], lib.arch.valid_page_sizes[0], .{ .read = true, .write = true }); + + @panic("TODO: MoreCore.init"); + } + + pub const State = extern struct { + mmu_state: MMUAwareVirtualAddressSpace, + }; +}; diff --git a/src/user/arch.zig b/src/user/arch.zig new file mode 100644 index 0000000..1b9aaec --- /dev/null +++ b/src/user/arch.zig @@ -0,0 +1,16 @@ +const lib = @import("lib"); + +comptime { + if (lib.os != .freestanding) @compileError("OS not supported"); +} + +pub const x86_64 = @import("arch/x86_64.zig"); + +const current = switch (lib.cpu.arch) { + .x86_64 => x86_64, + else => @compileError("Architecture not supported"), +}; + +pub usingnamespace current; + +pub const _start = current._start; diff --git a/src/user/arch/x86_64.zig b/src/user/arch/x86_64.zig new file mode 100644 index 0000000..6ea46cd --- /dev/null +++ b/src/user/arch/x86_64.zig @@ -0,0 +1,116 @@ +const lib = @import("lib"); +const log = lib.log; +const assert = lib.assert; +const rise = @import("rise"); +const user = @import("user"); + +const FPU = rise.arch.FPU; +const Registers = rise.arch.Registers; +const RegisterArena = rise.arch.RegisterArena; + +const VirtualAddress = lib.VirtualAddress; + +const PhysicalMemoryRegion = user.PhysicalMemoryRegion; +const PhysicalMap = user.PhysicalMap; +const SlotAllocator = user.SlotAllocator; +const Thread = user.Thread; +const VirtualAddressSpace = user.VirtualAddressSpace; + +pub const Scheduler = extern struct { + common: rise.arch.UserScheduler, + generic: user.Scheduler, + + pub fn initDisabled(scheduler: *Scheduler) void { + _ = scheduler; + // TODO: + // *set entry points? + // *set tls registers? + } + + pub noinline fn restore(scheduler: *Scheduler, register_arena: *const RegisterArena) noreturn { + assert(scheduler.common.generic.disabled); + assert(scheduler.common.generic.has_work); + + assert(register_arena.registers.rip > lib.arch.valid_page_sizes[0]); + assert(register_arena.registers.rflags.IF and register_arena.registers.rflags.reserved0); + + register_arena.contextSwitch(); + } +}; + +// CRT0 +pub fn _start() callconv(.Naked) noreturn { + asm volatile ( + \\push %rbp + \\jmp *%[startFunction] + : + : [startFunction] "r" (user.start), + ); + + unreachable; +} + +pub inline fn setInitialState(register_arena: *RegisterArena, entry: VirtualAddress, stack_virtual_address: VirtualAddress, arguments: rise.syscall.Arguments) void { + assert(stack_virtual_address.value() > lib.arch.valid_page_sizes[0]); + assert(lib.isAligned(stack_virtual_address.value(), lib.arch.stack_alignment)); + var stack_address = stack_virtual_address; + // x86_64 ABI + stack_address.subOffset(@sizeOf(usize)); + + register_arena.registers.rip = entry.value(); + register_arena.registers.rsp = stack_address.value(); + register_arena.registers.rflags = .{ .IF = true }; + register_arena.registers.rdi = arguments[0]; + register_arena.registers.rsi = arguments[1]; + register_arena.registers.rdx = arguments[2]; + register_arena.registers.rcx = arguments[3]; + register_arena.registers.r8 = arguments[4]; + register_arena.registers.r9 = arguments[5]; + + register_arena.fpu = lib.zeroes(FPU); + // register_arena.fpu.fcw = 0x037f; + register_arena.fpu.fcw = 0x1f80; +} + +pub inline fn maybeCurrentScheduler() ?*user.Scheduler { + return asm volatile ( + \\mov %fs:0, %[user_scheduler] + : [user_scheduler] "=r" (-> ?*user.Scheduler), + : + : "memory" + ); +} + +pub inline fn currentScheduler() *user.Scheduler { + const result = maybeCurrentScheduler().?; + return result; +} + +/// This is an interface to user.PhysicalMap, providing the architecture-specific functionality +pub const PhysicalMapInterface = struct { + pub fn determineAddress(physical_map: *PhysicalMap, physical_memory_region: PhysicalMemoryRegion, alignment: usize) !VirtualAddress { + _ = physical_memory_region; + _ = alignment; + assert(physical_map.virtual_address_space.regions != null); + log.debug("PMap: 0x{x}", .{@intFromPtr(physical_map.virtual_address_space.regions)}); + log.debug("PMap: {?}", .{physical_map.virtual_address_space.regions}); + @panic("TODO: PhysicalMapInterface.determineAddress"); + } + + pub fn initializeCurrent(physical_map: *PhysicalMap) !void { + _ = physical_map; + log.warn("TODO: PhysicalMapInterface.initializeCurrent", .{}); + } + + pub fn init(virtual_address_space: *VirtualAddressSpace, page_level: u3, slot_allocator: *SlotAllocator) !PhysicalMap { + var result = PhysicalMap{ + .virtual_address_space = virtual_address_space, + .slot_allocator = slot_allocator, + }; + _ = page_level; + + try result.initPageTableManagement(); + + @panic("TODO: PhysicalMap.init"); + } +}; diff --git a/src/user/arch/x86_64/linker_script.ld b/src/user/arch/x86_64/linker_script.ld new file mode 100644 index 0000000..e820441 --- /dev/null +++ b/src/user/arch/x86_64/linker_script.ld @@ -0,0 +1,24 @@ +PHDRS { + none PT_NULL FLAGS(0); + text PT_LOAD FLAGS((1 << 2) | (1 << 0) /* Readable | Executable */); + rodata PT_LOAD FLAGS((1 << 2) /* Readable */); + data PT_LOAD FLAGS((1 << 2) | (1 << 1) /* Readable | Writeable */); +} + +SECTIONS { + . = 0x600000; + . = ALIGN(4K); + .text . : { + *(.text*) + }:text + . = ALIGN(4K); + .rodata . : { + *(.rodata*) + }:rodata + . = ALIGN(4K); + .data . : { + *(.data*) + *(.bss*) + }:data + . = ALIGN(4K); +} diff --git a/src/user/capabilities.zig b/src/user/capabilities.zig new file mode 100644 index 0000000..b44f92a --- /dev/null +++ b/src/user/capabilities.zig @@ -0,0 +1,18 @@ +const lib = @import("lib"); +const assert = lib.assert; +const rise = @import("rise"); + +// TODO: ref +pub fn frameCreate(ref: usize, bytes: usize) !usize { + return mappableCapabilityCreate(ref, .cpu_memory, bytes); +} + +fn mappableCapabilityCreate(ref: usize, mappable_capability: rise.capabilities.Type.Mappable, bytes: usize) !usize { + _ = mappable_capability; + _ = ref; + assert(bytes > 0); +} + +fn ramDescendantCreate(ref: usize, ) !usize { + _ = ref; +} diff --git a/src/user/core_state.zig b/src/user/core_state.zig new file mode 100644 index 0000000..705e7df --- /dev/null +++ b/src/user/core_state.zig @@ -0,0 +1,27 @@ +const user = @import("user"); +const MoreCore = user.MoreCore; +const PhysicalMap = user.PhysicalMap; +const PhysicalMemoryRegion = user.PhysicalMemoryRegion; +const SlotAllocator = user.SlotAllocator; +const VirtualAddressSpace = user.VirtualAddressSpace; +const VirtualMemoryRegion = user.VirtualMemoryRegion; + +pub const PagingState = extern struct { + virtual_address_space: VirtualAddressSpace, + physical_map: PhysicalMap, +}; + +pub const PinnedState = extern struct { + physical_memory_region: PhysicalMemoryRegion.Pinned, + virtual_memory_region: VirtualMemoryRegion, + offset: usize, + // TODO: lists +}; + +pub const CoreState = extern struct { + paging: PagingState, + slot_allocator: SlotAllocator.State, + virtual_address_space: VirtualAddressSpace.State, + pinned: PinnedState, + more_core: MoreCore.State, +}; diff --git a/src/user/libc.zig b/src/user/libc.zig new file mode 100644 index 0000000..cf6889f --- /dev/null +++ b/src/user/libc.zig @@ -0,0 +1,7 @@ +const user = @import("user"); + +pub export fn malloc(size: usize) ?*anyopaque { + const morecore_state = user.process.getMoreCoreState(); + const result = morecore_state.mmu_state.map(size) catch return null; + return result.ptr; +} diff --git a/src/user/mmu_aware_virtual_address_space.zig b/src/user/mmu_aware_virtual_address_space.zig new file mode 100644 index 0000000..6ea5e7e --- /dev/null +++ b/src/user/mmu_aware_virtual_address_space.zig @@ -0,0 +1,62 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.MMUAwareVirtualAddressSpace); + +const user = @import("user"); +const PhysicalMemoryRegion = user.PhysicalMemoryRegion; +const SlotAllocator = user.SlotAllocator; +const VirtualMemoryRegion = user.VirtualMemoryRegion; + +pub const MMUAwareVirtualAddressSpace = extern struct { + size: usize, + alignment: usize, + consumed: usize = 0, + /// This is a index into the architecture-specific page sizes + page_size: u8, + slot_allocator: *SlotAllocator, + physical_memory_region: PhysicalMemoryRegion.Anonymous, + virtual_memory_region: VirtualMemoryRegion, + // struct vregion vregion; ///< Needs just one vregion + // struct memobj_anon memobj; ///< Needs just one memobj + // lvaddr_t offset; ///< Offset of free space in anon + // lvaddr_t mapoffset; ///< Offset into the anon that has been mapped in + + pub fn init(size: usize) !MMUAwareVirtualAddressSpace { + const slot_allocator = SlotAllocator.getDefault(); + const alignment = lib.arch.valid_page_sizes[0]; + return initAligned(slot_allocator, size, alignment, .{ .write = true }); + } + + pub fn initAligned(slot_allocator: *SlotAllocator, size: usize, alignment: usize, flags: VirtualMemoryRegion.Flags) !MMUAwareVirtualAddressSpace { + assert(flags.preferred_page_size < lib.arch.valid_page_sizes.len); + var result = MMUAwareVirtualAddressSpace{ + .size = size, + .alignment = alignment, + .page_size = flags.preferred_page_size, + .slot_allocator = slot_allocator, + .physical_memory_region = try PhysicalMemoryRegion.Anonymous.new(size), + .virtual_memory_region = undefined, + }; + // TODO: fix this API + result.virtual_memory_region = try user.process.getVirtualAddressSpace().mapAligned(result.physical_memory_region.getGeneric().*, 0, size, alignment, flags); + + // TODO: create memobj + // TODO: map memobj into vregion + + @panic("TODO: MMUAwareVirtualAddressSpace.initAligned"); + } + + const Error = error{ + alignment, + }; + + pub fn map(virtual_address_space: *MMUAwareVirtualAddressSpace, size: usize) ![]u8 { + if (!lib.isAligned(size, lib.arch.valid_page_sizes[0])) { + return error.alignment; + } + _ = virtual_address_space; + log.warn("[map] TODO: slot allocation", .{}); + //virtual_address_space.slot_allocator.allocate(); + @panic("TODO: MMUAwareVirtualAddressSpace.map"); + } +}; diff --git a/src/user/physical_map.zig b/src/user/physical_map.zig new file mode 100644 index 0000000..516e50d --- /dev/null +++ b/src/user/physical_map.zig @@ -0,0 +1,25 @@ +const lib = @import("lib"); +const log = lib.log.scoped(.PhysicalMap); + +const user = @import("user"); +const SlotAllocator = user.SlotAllocator; +const VirtualAddressSpace = user.VirtualAddressSpace; + +pub const PhysicalMap = extern struct { + virtual_address_space: *VirtualAddressSpace, + slot_allocator: *SlotAllocator, + + pub usingnamespace user.arch.PhysicalMapInterface; + + pub fn initPageTableManagement(physical_map: *PhysicalMap) !void { + const current_physical_map = user.process.getPhysicalMap(); + log.debug("CURR: 0x{x}. PHYS: 0x{x}", .{ @intFromPtr(current_physical_map), @intFromPtr(physical_map) }); + if (current_physical_map == physical_map) { + @panic("TODO: if"); + } else { + log.warn("TODO: slab_init", .{}); + _ = user.libc.malloc(lib.arch.valid_page_sizes[0]); + @panic("TODO: else"); + } + } +}; diff --git a/src/user/physical_memory_region.zig b/src/user/physical_memory_region.zig new file mode 100644 index 0000000..65129aa --- /dev/null +++ b/src/user/physical_memory_region.zig @@ -0,0 +1,81 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log.scoped(.PhysicalMemoryRegion); + +// Barrelfish: memobj +pub const PhysicalMemoryRegion = extern struct { + size: usize, + type: Type, + + pub const Type = enum(u8) { + anonymous = 0, + one_frame = 1, + pinned = 3, + //one_frame_lazy, + //one_frame_one_map, + // vfs, + // fixed, + // numa, + // append, + + fn map(t: Type) type { + return switch (t) { + .anonymous => Anonymous, + .one_frame => OneFrame, + .pinned => Pinned, + }; + } + }; + + pub const Anonymous = extern struct { + region: PhysicalMemoryRegion, + + pub usingnamespace Interface(@This()); + + pub fn new(size: usize) !Anonymous { + const result = Anonymous{ + .region = .{ + .size = size, + .type = .anonymous, + }, + }; + + log.warn("[Anonymous.new] TODO: initialize memory", .{}); + + return result; + } + }; + + pub const OneFrame = extern struct { + pub usingnamespace Interface(@This()); + }; + + pub const Pinned = extern struct { + region: PhysicalMemoryRegion, + pub usingnamespace Interface(@This()); + + pub fn new(size: usize) !Pinned { + const result = Pinned{ + .region = .{ + .size = size, + .type = .pinned, + }, + }; + + log.warn("[Pinned.new] TODO: initialize memory", .{}); + + return result; + } + }; + + fn Interface(comptime PhysicalMemoryRegionType: type) type { + assert(@hasField(PhysicalMemoryRegionType, "region")); + assert(@TypeOf(@field(@as(PhysicalMemoryRegionType, undefined), "region")) == PhysicalMemoryRegion); + + return extern struct { + pub inline fn getGeneric(r: *PhysicalMemoryRegionType) *PhysicalMemoryRegion { + return &r.region; + } + }; + } +}; diff --git a/src/user/process.zig b/src/user/process.zig new file mode 100644 index 0000000..60b9d72 --- /dev/null +++ b/src/user/process.zig @@ -0,0 +1,31 @@ +const user = @import("user"); +const currentScheduler = user.currentScheduler; +const MoreCore = user.MoreCore; +const PhysicalMap = user.PhysicalMap; +const PinnedState = user.PinnedState; +const SlotAllocator = user.SlotAllocator; +const VirtualAddressSpace = user.VirtualAddressSpace; + +pub inline fn getVirtualAddressSpace() *VirtualAddressSpace { + return ¤tScheduler().core_state.paging.virtual_address_space; +} + +pub inline fn getPhysicalMap() *PhysicalMap { + return ¤tScheduler().core_state.paging.physical_map; +} + +pub inline fn getSlotAllocatorState() *SlotAllocator.State { + return ¤tScheduler().core_state.slot_allocator; +} + +pub inline fn getSlotAllocator() *SlotAllocator { + return ¤tScheduler().core_state.slot_allocator.default_allocator.allocator; +} + +pub inline fn getPinnedState() *PinnedState { + return ¤tScheduler().core_state.pinned; +} + +pub inline fn getMoreCoreState() *MoreCore.State { + return ¤tScheduler().core_state.more_core; +} diff --git a/src/user/programs/device_manager/main.zig b/src/user/programs/device_manager/main.zig new file mode 100644 index 0000000..f855590 --- /dev/null +++ b/src/user/programs/device_manager/main.zig @@ -0,0 +1,18 @@ +const lib = @import("lib"); +const log = lib.log; +const user = @import("user"); +const Syscall = user.Syscall; + +pub const panic = user.zigPanic; +pub const std_options = user.std_options; + +export var core_id: u32 = 0; + +pub fn main() !noreturn { + core_id = try Syscall(.cpu, .get_core_id).blocking({}); + user.currentScheduler().core_id = core_id; + log.debug("Hello world! User space initialization from core #{}", .{core_id}); + const allocation = try Syscall(.cpu_memory, .allocate).blocking(0x1000); + log.debug("Look allocation successful at 0x{x}", .{allocation.value()}); + try Syscall(.cpu, .shutdown).blocking({}); +} diff --git a/src/user/programs/device_manager/module.json b/src/user/programs/device_manager/module.json new file mode 100644 index 0000000..2a8c06a --- /dev/null +++ b/src/user/programs/device_manager/module.json @@ -0,0 +1,4 @@ +{ + "kind": "zig_exe", + "dependencies": [] +} diff --git a/src/user/programs/device_manager/test.zig b/src/user/programs/device_manager/test.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/user/programs/init/main.zig b/src/user/programs/init/main.zig new file mode 100644 index 0000000..19e6781 --- /dev/null +++ b/src/user/programs/init/main.zig @@ -0,0 +1,25 @@ +const lib = @import("lib"); +const assert = lib.assert; +const log = lib.log; +const user = @import("user"); +const syscall = user.Syscall; + +pub const panic = user.zigPanic; +pub const std_options = user.std_options; + +export var core_id: u32 = 0; + +pub fn main() !noreturn { + // core_id = try syscall(.cpu, .get_core_id).blocking({}); + // user.currentScheduler().core_id = core_id; + // log.debug("Hello world! User space initialization from core #{}", .{core_id}); + // const bundle_file_list_size = try syscall(.boot, .get_bundle_file_list_size).blocking({}); + // log.debug("Bundle file list size: {}", .{bundle_file_list_size}); + // const bundle_size = try syscall(.boot, .get_bundle_size).blocking({}); + // log.debug("Bundle size: {}", .{bundle_size}); + // assert(bundle_size > 0); + // const aligned_bundle_size = lib.alignForward(usize, bundle_size, lib.arch.valid_page_sizes[0]); + // const bundle_allocation = try syscall(.cpu_memory, .allocate).blocking(aligned_bundle_size); + // log.debug("Look allocation successful at 0x{x}", .{bundle_allocation.value()}); + try syscall(.cpu, .shutdown).blocking({}); +} diff --git a/src/user/programs/init/module.json b/src/user/programs/init/module.json new file mode 100644 index 0000000..2a8c06a --- /dev/null +++ b/src/user/programs/init/module.json @@ -0,0 +1,4 @@ +{ + "kind": "zig_exe", + "dependencies": [] +} diff --git a/src/user/programs/init/test.zig b/src/user/programs/init/test.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/user/slot_allocator.zig b/src/user/slot_allocator.zig new file mode 100644 index 0000000..dcd102f --- /dev/null +++ b/src/user/slot_allocator.zig @@ -0,0 +1,28 @@ +const log = @import("lib").log; +const user = @import("user"); + +pub const SlotAllocator = extern struct { + foo: u32 = 0, + + /// This function is inlined because it's only called once + pub inline fn init() !void { + log.warn("TODO: implement the whole SlotAllocator.init", .{}); + const state = user.process.getSlotAllocatorState(); + const default_allocator = state.default_allocator; + _ = default_allocator; + } + + pub fn getDefault() *SlotAllocator { + const process_slot_allocator_state = user.process.getSlotAllocatorState(); + return &process_slot_allocator_state.default_allocator.allocator; + } + + pub const State = extern struct { + default_allocator: MultiSlotAllocator, + }; +}; + +pub const MultiSlotAllocator = extern struct { + allocator: SlotAllocator, + // TODO: +}; diff --git a/src/user/thread.zig b/src/user/thread.zig new file mode 100644 index 0000000..1da6c4c --- /dev/null +++ b/src/user/thread.zig @@ -0,0 +1,172 @@ +const lib = @import("lib"); +const log = lib.log.scoped(.thread); +const user = @import("user"); +const rise = @import("rise"); + +const MoreCore = user.MoreCore; +const MMUAwareVirtualAddressSpace = user.MMUAwareVirtualAddressSpace; +const SlotAllocator = user.SlotAllocator; +const VirtualAddress = lib.VirtualAddress; +const VirtualAddressSpace = user.VirtualAddressSpace; + +const max_thread_count = 256; + +pub const Thread = extern struct { + self: *Thread, + previous: ?*Thread, + next: ?*Thread, + stack: [*]u8, + stack_top: [*]align(lib.arch.stack_alignment) u8, + register_arena: rise.arch.RegisterArena align(lib.arch.stack_alignment), + core_id: u32, + + pub fn init(thread: *Thread, scheduler: *user.arch.Scheduler) void { + thread.self = thread; + thread.previous = null; + thread.next = null; + thread.core_id = scheduler.generic.core_id; + } +}; + +pub const Mutex = extern struct { + locked: bool = false, + + pub inline fn internalLock(mutex: *volatile Mutex) void { + mutex.locked = true; + } +}; + +var static_stack: [0x10000]u8 align(lib.arch.stack_alignment) = undefined; +var static_thread: Thread = undefined; +var static_thread_lock = Mutex{}; + +pub fn initDisabled(scheduler: *user.arch.Scheduler) noreturn { + const thread = &static_thread; + static_thread_lock.internalLock(); + thread.stack = &static_stack; + thread.stack_top = static_stack[static_stack.len..]; + thread.init(scheduler); + + // TODO: use RAX as parameter? + + user.arch.setInitialState(&thread.register_arena, VirtualAddress.new(bootstrapThread), VirtualAddress.new(thread.stack_top), .{0} ** 6); + + scheduler.common.generic.has_work = true; + + scheduler.restore(&thread.register_arena); +} + +const SpawnDomainParams = extern struct {}; + +// TODO: +const Foo = struct {}; + +pub var slab_allocator: Foo = undefined; +pub var slab_virtual_address_space: user.MMUAwareVirtualAddressSpace = undefined; + +fn initThread(parameters: *SpawnDomainParams) !void { + _ = parameters; + // TODO: + // - waitset + // - ram alloc init + + try VirtualAddressSpace.initializeCurrent(); + + try SlotAllocator.init(); + + if (false) { + // TODO: + log.warn("TODO: handle the case where spawn domain parameters exist", .{}); + } else if (user.is_init) { + log.warn("Known init page table layout. Take advantage of that!", .{}); + } + + if (user.is_init) { + log.debug("More core init start", .{}); + try MoreCore.init(lib.arch.valid_page_sizes[0]); + log.debug("More core init end", .{}); + log.warn("TODO: implement memory initialization -> morecore_init()", .{}); + } else { + @panic("TODO: not init userspace binary"); + } + + log.warn("TODO: Should we do LMP endpoints?", .{}); + + if (!user.is_init) { + @panic("TODO: not init user binaries"); + } +} + +fn bootstrapThread(parameters: *SpawnDomainParams) callconv(.C) noreturn { + + // TODO: Do we have TLS data? + // tls_block_init_base = params->tls_init_base; + // tls_block_init_len = params->tls_init_len; + // tls_block_total_len = params->tls_total_len; + + initThread(parameters) catch |err| user.panic("initThread failed: {}", .{err}); + // // Allocate storage region for real threads + // size_t blocksize = sizeof(struct thread) + tls_block_total_len + THREAD_ALIGNMENT; + // err = vspace_mmu_aware_init(&thread_slabs_vm, MAX_THREADS * blocksize); + + // TODO: make this declaration value assignment complete + const block_size = @sizeOf(Thread); + slab_virtual_address_space = try MMUAwareVirtualAddressSpace.init(max_thread_count * block_size); + // if (err_is_fail(err)) { + // USER_PANIC_ERR(err, "vspace_mmu_aware_init for thread region failed\n"); + // } + // // XXX: do this nicer, but we need struct threads to be in Vspace < 4GB so + // // we can set the thread segment register. -SG, 2017-02-28. + // // We can't use the assertion yet, as the init domain has it's thread + // // slabs above 4G. + // //assert(vregion_get_base_addr(&thread_slabs_vm.vregion) + vregion_get_size(&thread_slabs_vm.vregion) < 1ul << 32); + // slab_init(&thread_slabs, blocksize, refill_thread_slabs); + + if (user.is_init) { + // No allocation path + mainThread(parameters); + } else { + // Do allocations + while (true) {} + } +} + +fn mainThread(parameters: ?*anyopaque) noreturn { + // TODO: parameters + _ = parameters; + const root = @import("root"); + if (@hasDecl(root, "main")) { + const result = switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) { + .NoReturn => root.main(), + .Void => blk: { + root.main(); + break :blk 0; + }, + .Int => root.main(), + .ErrorUnion => blk: { + const result = root.main() catch { + // TODO: log + break :blk 1; + }; + + switch (@typeInfo(@TypeOf(result))) { + .Void => break :blk 0, + .Int => break :blk result, + else => @compileError("Unexpected return type"), + } + }, + else => @compileError("Unexpected return type"), + }; + _ = result; + @panic("ASdasd"); + } else { + const result = _main(); + _ = result; + } +} + +export fn _main() i32 { + // global constructors + // array + return 0; +} diff --git a/src/user/virtual_address_space.zig b/src/user/virtual_address_space.zig new file mode 100644 index 0000000..daea64a --- /dev/null +++ b/src/user/virtual_address_space.zig @@ -0,0 +1,60 @@ +const lib = @import("lib"); +const log = lib.log; + +const user = @import("user"); +const PhysicalMap = user.PhysicalMap; +const PhysicalMemoryRegion = user.PhysicalMemoryRegion; +const VirtualMemoryRegion = user.VirtualMemoryRegion; + +pub const MMUAwareVirtualAddressSpace = @import("mmu_aware_virtual_address_space.zig").MMUAwareVirtualAddressSpace; + +pub const VirtualAddressSpace = extern struct { + physical_map: *PhysicalMap, + // TODO: layout + regions: ?*VirtualMemoryRegion = null, + + /// The function is inlined because it's only called once + pub inline fn initializeCurrent() !void { + log.debug("VirtualAddressSpace.initializeCurrent", .{}); + const virtual_address_space = user.process.getVirtualAddressSpace(); + const physical_map = user.process.getPhysicalMap(); + virtual_address_space.physical_map = physical_map; + + const root_page_level = 0; + physical_map.* = try PhysicalMap.init(virtual_address_space, root_page_level, user.process.getSlotAllocator()); + // This should be an inline call as this the only time this function is called + try physical_map.initializeCurrent(); + + try virtual_address_space.pinnedInit(); + + log.warn("TODO: VirtualAddressSpace.initializeCurrent is incomplete!", .{}); + } + + pub inline fn pinnedInit(virtual_address_space: *VirtualAddressSpace) !void { + const pinned_state = user.process.getPinnedState(); + const pinned_size = 128 * lib.mb; + pinned_state.physical_memory_region = try PhysicalMemoryRegion.Pinned.new(pinned_size); + + pinned_state.virtual_memory_region = try virtual_address_space.map(pinned_state.physical_memory_region.getGeneric().*, 0, pinned_size, .{ .write = true }); + log.warn("TODO: VirtualAddressSpace.pinnedInit", .{}); + } + + pub inline fn map(virtual_address_space: *VirtualAddressSpace, physical_memory_region: PhysicalMemoryRegion, offset: usize, size: usize, flags: VirtualMemoryRegion.Flags) !VirtualMemoryRegion { + const alignment = lib.arch.valid_page_sizes[0]; + return virtual_address_space.mapAligned(physical_memory_region, offset, size, alignment, flags); + } + + pub fn mapAligned(virtual_address_space: *VirtualAddressSpace, physical_memory_region: PhysicalMemoryRegion, offset: usize, size: usize, alignment: usize, flags: VirtualMemoryRegion.Flags) !VirtualMemoryRegion { + const virtual_address = try virtual_address_space.physical_map.determineAddress(physical_memory_region, alignment); + _ = virtual_address; + _ = offset; + _ = size; + _ = flags; + @panic("TODO: VirtualAddressSpace.mapAligned"); + } + + pub const State = extern struct { + virtual_address_space: VirtualAddressSpace, + physical_map: PhysicalMap, + }; +}; diff --git a/tools/format_loopback_fat32.sh b/tools/format_loopback_fat32.sh new file mode 100755 index 0000000..461dff7 --- /dev/null +++ b/tools/format_loopback_fat32.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# $1 Loopback device +set -e +sudo mkfs.fat -F 32 `cat $1`p1 1>2 diff --git a/tools/loopback_end.sh b/tools/loopback_end.sh new file mode 100755 index 0000000..a729ed9 --- /dev/null +++ b/tools/loopback_end.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# $1 = Loopback device +set -e +sudo losetup -d `cat $1` diff --git a/tools/loopback_mount.sh b/tools/loopback_mount.sh new file mode 100755 index 0000000..4060318 --- /dev/null +++ b/tools/loopback_mount.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# $1 = Loopback device +# $2 = Mount directory +set -e +#echo "Mounting loopback device $1 in directory $2..." +sudo mount `cat $1`p1 $2 diff --git a/tools/loopback_start.sh b/tools/loopback_start.sh new file mode 100755 index 0000000..efc2327 --- /dev/null +++ b/tools/loopback_start.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# $1 = Disk image +# $2 = Loopback device +set -e +#echo "Starting loopback device $2 with image $1..." +sudo losetup -Pf --show $1 > $2