first commit

This commit is contained in:
David Gonzalez Martin 2023-07-09 11:24:30 -06:00
commit 0709f980af
126 changed files with 21766 additions and 0 deletions

9
.gitattributes vendored Normal file
View File

@ -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

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [davidgm94]

44
.github/workflows/lightning.yml vendored Normal file
View File

@ -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

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
zig-cache/*
zig-out/*
*.png
logfile
debug_disk
.gdb_history
bochsout.txt
*.hdd
loopback_device

119
README.md Normal file
View File

@ -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

17
bochsrc Normal file
View File

@ -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

743
build.zig Normal file
View File

@ -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,
};
}

9
config/default.json Normal file
View File

@ -0,0 +1,9 @@
{
"architecture": "x86_64",
"bootloader": "rise",
"boot_protocol": "bios",
"execution_environment": "qemu",
"optimize_mode": "Debug",
"execution_type": "emulated",
"executable_kind": "exe"
}

1
config/gdb_script Normal file
View File

@ -0,0 +1 @@
set can-use-hw-watchpoints 1

11
config/image_config.json Normal file
View File

@ -0,0 +1,11 @@
{
"sector_count": 131072,
"sector_size": 512,
"image_name": "rise",
"partition_table": "gpt",
"partition": {
"name": "ESP",
"filesystem": "fat32",
"first_lba": 2048
}
}

31
config/qemu.json Normal file
View File

@ -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"
]
}

Binary file not shown.

BIN
resources/zap-light16.psf Normal file

Binary file not shown.

796
src/bootloader.zig Normal file
View File

@ -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,
};

2
src/bootloader/arch.zig Normal file
View File

@ -0,0 +1,2 @@
pub const x86 = @import("arch/x86.zig");
pub const x86_64 = @import("arch/x86_64.zig");

View File

View File

@ -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:

View File

@ -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;

649
src/bootloader/bios.zig Normal file
View File

@ -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, &registers, &registers);
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, &registers) 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, &registers);
}
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, &registers);
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, &registers);
}
};

369
src/bootloader/limine.zig Normal file
View File

@ -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,
};
};

View File

@ -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.

View File

@ -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 = .);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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.

View File

@ -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 $@

View File

@ -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:

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,920 @@
#undef IS_WINDOWS
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__)
#define IS_WINDOWS 1
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#include <limits.h>
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 <device> [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=<filename>\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) { // <device>
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;
}

Binary file not shown.

View File

@ -0,0 +1,7 @@
#include <stdio.h>
#define LIMINE_VERSION "4.20230120.0"
int main(void) {
puts(LIMINE_VERSION);
}

View File

@ -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

View File

@ -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 <stdint.h>
/* 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

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -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),
}
}

View File

View File

@ -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 = .;
}

View File

@ -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));
};
}

View File

View File

@ -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

View File

@ -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));
};
}

View File

View File

@ -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;
}
}
}
};

View File

@ -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;
}
}
};

136
src/bootloader/todo/smp.zig Normal file
View File

@ -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"),
// }
// }

88
src/bootloader/uefi.zig Normal file
View File

@ -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;

502
src/common.zig Normal file
View File

@ -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;

435
src/cpu.zig Normal file
View File

@ -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 = {} };

13
src/cpu/arch.zig Normal file
View File

@ -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;

1492
src/cpu/arch/x86/64/init.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 = .);
}

View File

@ -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,
};

265
src/cpu/arch/x86_64.zig Normal file
View File

@ -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,
};

419
src/cpu/capabilities.zig Normal file
View File

@ -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,
};

37
src/cpu/main.zig Normal file
View File

@ -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 });
}

21
src/cpu/test.zig Normal file
View File

@ -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;
}
};

38
src/cpu/test_runner.zig Normal file
View File

@ -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);
}

74
src/host.zig Normal file
View File

@ -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;

View File

@ -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;
}

View File

@ -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);
}
};

View File

@ -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');
}
};

View File

@ -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 => {},
}
}

350
src/host/runner/main.zig Normal file
View File

@ -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,
};

6
src/host/test.zig Normal file
View File

@ -0,0 +1,6 @@
const lib = @import("lib");
const host = @import("host");
test "Host tests" {
_ = lib;
_ = host;
}

1080
src/lib.zig Normal file

File diff suppressed because it is too large Load Diff

34
src/lib/arch.zig Normal file
View File

@ -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;

6
src/lib/arch/x86.zig Normal file
View File

@ -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 };

View File

@ -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);

View File

@ -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);
}
};

34
src/lib/arch/x86_64.zig Normal file
View File

@ -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;

6
src/lib/config.zig Normal file
View File

@ -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;

13
src/lib/crc32.zig Normal file
View File

@ -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 };

123
src/lib/disk.zig Normal file
View File

@ -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,
};
};

View File

@ -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;
}
}

18
src/lib/filesystem.zig Normal file
View File

@ -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;
}

1297
src/lib/filesystem/fat32.zig Normal file

File diff suppressed because it is too large Load Diff

184
src/lib/graphics.zig Normal file
View File

@ -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,
_,
};

21
src/lib/nls.zig Normal file
View File

@ -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");

125
src/lib/nls/ascii.zig Normal file
View File

@ -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,
};

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
};

13
src/lib/psf1.zig Normal file
View File

@ -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,
};

114
src/privileged.zig Normal file
View File

@ -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,
};
};

247
src/privileged/acpi.zig Normal file
View File

@ -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,
};
};

22
src/privileged/arch.zig Normal file
View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,
};
}

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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),
);
}
};

View File

@ -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,
}
}

View File

@ -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,
};

23
src/rise.zig Normal file
View File

@ -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,
};

7
src/rise/arch.zig Normal file
View File

@ -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");

155
src/rise/arch/x64_64.zig Normal file
View File

@ -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,
},
};
}

Some files were not shown because too many files have changed in this diff Show More