first commit
This commit is contained in:
commit
0709f980af
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal 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
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [davidgm94]
|
||||
44
.github/workflows/lightning.yml
vendored
Normal file
44
.github/workflows/lightning.yml
vendored
Normal 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
9
.gitignore
vendored
Normal 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
119
README.md
Normal file
@ -0,0 +1,119 @@
|
||||
# Rise: an attempt to write a better operating system
|
||||
|
||||

|
||||
|
||||
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
17
bochsrc
Normal 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
743
build.zig
Normal 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
9
config/default.json
Normal 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
1
config/gdb_script
Normal file
@ -0,0 +1 @@
|
||||
set can-use-hw-watchpoints 1
|
||||
11
config/image_config.json
Normal file
11
config/image_config.json
Normal 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
31
config/qemu.json
Normal 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"
|
||||
]
|
||||
}
|
||||
BIN
resources/FiraSans-Regular.otf
Normal file
BIN
resources/FiraSans-Regular.otf
Normal file
Binary file not shown.
BIN
resources/zap-light16.psf
Normal file
BIN
resources/zap-light16.psf
Normal file
Binary file not shown.
796
src/bootloader.zig
Normal file
796
src/bootloader.zig
Normal 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
2
src/bootloader/arch.zig
Normal file
@ -0,0 +1,2 @@
|
||||
pub const x86 = @import("arch/x86.zig");
|
||||
pub const x86_64 = @import("arch/x86_64.zig");
|
||||
0
src/bootloader/arch/x86.zig
Normal file
0
src/bootloader/arch/x86.zig
Normal file
135
src/bootloader/arch/x86/64/smp_trampoline.S
Normal file
135
src/bootloader/arch/x86/64/smp_trampoline.S
Normal 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:
|
||||
150
src/bootloader/arch/x86_64.zig
Normal file
150
src/bootloader/arch/x86_64.zig
Normal 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
649
src/bootloader/bios.zig
Normal 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, ®isters, ®isters);
|
||||
lib.log.debug("End int", .{});
|
||||
|
||||
if (registers.eflags.flags.carry_flag) return error.read_error;
|
||||
|
||||
const provided_buffer_offset = lba_offset * disk.sector_size;
|
||||
const bytes_to_copy = sectors_to_read * disk.sector_size;
|
||||
const src_slice = buffer[0..bytes_to_copy];
|
||||
|
||||
if (maybe_provided_buffer) |provided_buffer| {
|
||||
lib.log.debug("A", .{});
|
||||
const dst_slice = provided_buffer[@as(usize, @intCast(provided_buffer_offset))..][0..bytes_to_copy];
|
||||
|
||||
// TODO: report Zig that this codegen is so bad that we have to use rep movsb instead to make it go fast
|
||||
// Tasks:
|
||||
// - Find out the root issue: is it only soft float? is it 32-bit soft_float? is it 32-bit soft_float ReleaseSmall?
|
||||
// - Report the issue with data to back the facts
|
||||
const use_rep_movsb = true;
|
||||
if (use_rep_movsb) {
|
||||
lib.memcpy(dst_slice, src_slice);
|
||||
const bytes_left = asm volatile (
|
||||
\\rep movsb
|
||||
: [ret] "={ecx}" (-> usize),
|
||||
: [dest] "{edi}" (dst_slice.ptr),
|
||||
[src] "{esi}" (src_slice.ptr),
|
||||
[len] "{ecx}" (src_slice.len),
|
||||
);
|
||||
assert(bytes_left == 0);
|
||||
} else {
|
||||
@memcpy(dst_slice, src_slice);
|
||||
}
|
||||
} else {
|
||||
lib.log.debug("B", .{});
|
||||
}
|
||||
}
|
||||
|
||||
const result = lib.Disk.ReadResult{
|
||||
.sector_count = sector_count,
|
||||
.buffer = (maybe_provided_buffer orelse &buffer).ptr,
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn readCache(disk: *lib.Disk, asked_sector_count: u64, sector_offset: u64) lib.Disk.ReadError!lib.Disk.ReadResult {
|
||||
const max_sector_count = @divExact(disk.cache_size, disk.sector_size);
|
||||
const sector_count = if (asked_sector_count > max_sector_count) max_sector_count else asked_sector_count;
|
||||
return try read(disk, sector_count, sector_offset, null);
|
||||
}
|
||||
|
||||
pub fn write(disk: *lib.Disk, bytes: []const u8, sector_offset: u64, commit_memory_to_disk: bool) lib.Disk.WriteError!void {
|
||||
_ = disk;
|
||||
_ = bytes;
|
||||
_ = sector_offset;
|
||||
_ = commit_memory_to_disk;
|
||||
return lib.Disk.WriteError.not_supported;
|
||||
}
|
||||
};
|
||||
|
||||
extern fn interrupt(number: u8, out_regs: *Registers, in_regs: *const Registers) linksection(".realmode") callconv(.C) void;
|
||||
|
||||
const DAP = lib.PartitionTable.MBR.DAP;
|
||||
|
||||
const Registers = extern struct {
|
||||
gs: u16 = 0,
|
||||
fs: u16 = 0,
|
||||
es: u16 = 0,
|
||||
ds: u16 = 0,
|
||||
eflags: packed struct(u32) {
|
||||
flags: packed struct(u16) {
|
||||
carry_flag: bool = false,
|
||||
reserved: u1 = 1,
|
||||
parity_flag: bool = false,
|
||||
reserved1: u1 = 0,
|
||||
adjust_flag: bool = false,
|
||||
reserved2: u1 = 0,
|
||||
zero_flag: bool = false,
|
||||
sign_flag: bool = false,
|
||||
trap_flag: bool = false,
|
||||
interrupt_enabled_flag: bool = false,
|
||||
direction_flag: bool = false,
|
||||
overflow_flag: bool = false,
|
||||
io_privilege_level: u2 = 0,
|
||||
nested_task_flag: bool = false,
|
||||
mode_flag: bool = false,
|
||||
} = .{},
|
||||
extended: packed struct(u16) {
|
||||
resume_flag: bool = false,
|
||||
virtual_8086_mode: bool = false,
|
||||
alignment_smap_check: bool = false,
|
||||
virtual_interrupt_flag: bool = false,
|
||||
virtual_interrupt_pending: bool = false,
|
||||
cpuid: bool = false,
|
||||
reserved: u8 = 0,
|
||||
aes_key_schedule: bool = false,
|
||||
reserved1: bool = false,
|
||||
} = .{},
|
||||
} = .{},
|
||||
ebp: u32 = 0,
|
||||
edi: u32 = 0,
|
||||
esi: u32 = 0,
|
||||
edx: u32 = 0,
|
||||
ecx: u32 = 0,
|
||||
ebx: u32 = 0,
|
||||
eax: u32 = 0,
|
||||
};
|
||||
|
||||
fn A20IsEnabled() bool {
|
||||
const address = 0x7dfe;
|
||||
const address_with_offset = address + 0x100000;
|
||||
if (@as(*volatile u16, @ptrFromInt(address)).* != @as(*volatile u16, @ptrFromInt(address_with_offset)).*) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@as(*volatile u16, @ptrFromInt(address)).* = ~(@as(*volatile u16, @ptrFromInt(address)).*);
|
||||
|
||||
if (@as(*volatile u16, @ptrFromInt(address)).* != @as(*volatile u16, @ptrFromInt(address_with_offset)).*) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const A20Error = error{a20_not_enabled};
|
||||
|
||||
pub fn A20Enable() A20Error!void {
|
||||
if (!A20IsEnabled()) {
|
||||
return A20Error.a20_not_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
pub const MemoryMapEntry = extern struct {
|
||||
address: PhysicalAddress,
|
||||
size: u64,
|
||||
type: Type,
|
||||
unused: u32 = 0,
|
||||
|
||||
pub inline fn isUsable(entry: MemoryMapEntry) bool {
|
||||
return entry.type == .usable and entry.address.value() >= lib.mb;
|
||||
}
|
||||
|
||||
pub inline fn toPhysicalMemoryRegion(entry: MemoryMapEntry) PhysicalMemoryRegion {
|
||||
return PhysicalMemoryRegion.new(.{
|
||||
.address = entry.address,
|
||||
.size = entry.size,
|
||||
});
|
||||
}
|
||||
|
||||
const Type = enum(u32) {
|
||||
usable = 1,
|
||||
reserved = 2,
|
||||
acpi_reclaimable = 3,
|
||||
acpi_nvs = 4,
|
||||
bad_memory = 5,
|
||||
};
|
||||
};
|
||||
|
||||
var memory_map_entries: [max_memory_entry_count]MemoryMapEntry = undefined;
|
||||
const max_memory_entry_count = 32;
|
||||
|
||||
pub const E820Iterator = extern struct {
|
||||
registers: Registers = Registers{},
|
||||
index: usize = 0,
|
||||
|
||||
pub fn next(iterator: *E820Iterator) ?MemoryMapEntry {
|
||||
var memory_map_entry: MemoryMapEntry = undefined;
|
||||
|
||||
comptime assert(@sizeOf(MemoryMapEntry) == 24);
|
||||
iterator.registers.eax = 0xe820;
|
||||
iterator.registers.ecx = @sizeOf(MemoryMapEntry);
|
||||
iterator.registers.edx = 0x534d4150;
|
||||
iterator.registers.edi = @intFromPtr(&memory_map_entry);
|
||||
|
||||
interrupt(0x15, &iterator.registers, &iterator.registers);
|
||||
|
||||
if (!iterator.registers.eflags.flags.carry_flag and iterator.registers.ebx != 0) {
|
||||
iterator.index += 1;
|
||||
return memory_map_entry;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn getMemoryMapEntryCount() u32 {
|
||||
var entry_count: u32 = 0;
|
||||
var iterator = E820Iterator{};
|
||||
|
||||
while (iterator.next()) |_| {
|
||||
entry_count += 1;
|
||||
}
|
||||
|
||||
return entry_count;
|
||||
}
|
||||
|
||||
const SuitableEntry = extern struct {
|
||||
region: PhysicalMemoryRegion(.local),
|
||||
index: u32,
|
||||
};
|
||||
|
||||
pub fn fetchMemoryEntries(memory_map: []bootloader.MemoryMapEntry) void {
|
||||
var iterator = E820Iterator{};
|
||||
while (iterator.next()) |entry| {
|
||||
memory_map[entry.index] = .{
|
||||
.region = entry.descriptor.region,
|
||||
.type = switch (entry.descriptor.type) {
|
||||
.usable => if (entry.descriptor.isUsable()) .usable else .reserved,
|
||||
.bad_memory => .bad_memory,
|
||||
else => .reserved,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (iterator.index != memory_map.len) {
|
||||
@panic("memory map entry mismatch");
|
||||
//privileged.panic("Memory map entries don't match. Got {}. Expected: {}", .{ iterator.index, memory_map.len });
|
||||
}
|
||||
}
|
||||
|
||||
const FindRSDPResult = union(enum) {
|
||||
descriptor1: *ACPI.RSDP.Descriptor1,
|
||||
descriptor2: *ACPI.RSDP.Descriptor2,
|
||||
};
|
||||
|
||||
fn wrapSumBytes(bytes: []const u8) u8 {
|
||||
var result: u8 = 0;
|
||||
for (bytes) |byte| {
|
||||
result +%= byte;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn getEBDAAddress() u32 {
|
||||
const expected_EBDA_base = 0x80000;
|
||||
const expected_EBDA_top = 0xa0000;
|
||||
|
||||
const base = @as(u32, @as(*u16, @ptrFromInt(0x40e)).*) << 4;
|
||||
|
||||
if (base < expected_EBDA_base or base > expected_EBDA_top) {
|
||||
return expected_EBDA_base;
|
||||
} else {
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
const FindRSDP = error{
|
||||
not_found,
|
||||
checksum_failed,
|
||||
};
|
||||
|
||||
pub fn findRSDP() FindRSDP!*ACPI.RSDP.Descriptor1 {
|
||||
const ebda_address = getEBDAAddress();
|
||||
const main_bios_area_base_address = 0xe0000;
|
||||
const RSDP_PTR = "RSD PTR ".*;
|
||||
|
||||
const pointers = [2]u32{ ebda_address, main_bios_area_base_address };
|
||||
const limits = [2]u32{ ebda_address + @as(u32, @intCast(@intFromEnum(lib.SizeUnit.kilobyte))), @as(u32, @intCast(@intFromEnum(lib.SizeUnit.megabyte))) };
|
||||
|
||||
for (pointers, 0..) |pointer, index| {
|
||||
var ptr = pointer;
|
||||
const limit = limits[index];
|
||||
|
||||
while (ptr < limit) : (ptr += 16) {
|
||||
const rsdp_descriptor = @as(*ACPI.RSDP.Descriptor1, @ptrFromInt(ptr));
|
||||
|
||||
if (lib.equal(u8, &rsdp_descriptor.signature, &RSDP_PTR)) {
|
||||
switch (rsdp_descriptor.revision) {
|
||||
0 => {
|
||||
if (wrapSumBytes(lib.asBytes(rsdp_descriptor)) == 0) {
|
||||
return rsdp_descriptor;
|
||||
} else {
|
||||
return FindRSDP.checksum_failed;
|
||||
}
|
||||
},
|
||||
2 => {
|
||||
const rsdp_descriptor2 = @fieldParentPtr(ACPI.RSDP.Descriptor2, "descriptor1", rsdp_descriptor);
|
||||
if (wrapSumBytes(lib.asBytes(rsdp_descriptor2)) == 0) {
|
||||
return &rsdp_descriptor2.descriptor1;
|
||||
} else {
|
||||
return FindRSDP.checksum_failed;
|
||||
}
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FindRSDP.not_found;
|
||||
}
|
||||
|
||||
pub const RealModePointer = extern struct {
|
||||
offset: u16,
|
||||
segment: u16,
|
||||
|
||||
pub inline fn desegment(real_mode_pointer: RealModePointer, comptime Ptr: type) Ptr {
|
||||
return @as(Ptr, @ptrFromInt((@as(u32, real_mode_pointer.segment) << 4) + real_mode_pointer.offset));
|
||||
}
|
||||
};
|
||||
|
||||
pub const VBE = extern struct {
|
||||
pub const Information = extern struct {
|
||||
signature: [4]u8,
|
||||
version_minor: u8,
|
||||
version_major: u8,
|
||||
OEM: RealModePointer,
|
||||
capabitilies: [4]u8,
|
||||
video_modes: RealModePointer,
|
||||
video_memory_blocks: u16,
|
||||
OEM_software_revision: u16,
|
||||
OEM_vendor: RealModePointer,
|
||||
OEM_product_name: RealModePointer,
|
||||
OEM_product_revision: RealModePointer,
|
||||
reserved: [222]u8,
|
||||
OEM_data: [256]u8,
|
||||
|
||||
pub const Capabilities = packed struct(u32) {
|
||||
dac_switchable: bool,
|
||||
controller_not_vga_compatible: bool,
|
||||
ramdac_blank: bool,
|
||||
hardware_stereoscopic_signaling: bool,
|
||||
VESA_EVC_stereo_signaling: bool,
|
||||
reserved: u27 = 0,
|
||||
};
|
||||
|
||||
comptime {
|
||||
assert(@sizeOf(Information) == lib.default_sector_size);
|
||||
}
|
||||
|
||||
pub fn getVideoMode(vbe_info: *const VBE.Information, comptime isValidVideoMode: fn (mode: *const Mode) bool, desired_width: u16, desired_height: u16, edid_bpp: u8) ?Mode {
|
||||
const video_modes = vbe_info.video_modes.desegment([*]const u16);
|
||||
for (video_modes[0..lib.maxInt(usize)]) |video_mode_number| {
|
||||
if (video_mode_number == 0xffff) break;
|
||||
var registers = Registers{};
|
||||
var mode: VBE.Mode = undefined;
|
||||
|
||||
registers.ecx = video_mode_number;
|
||||
registers.edi = @intFromPtr(&mode);
|
||||
|
||||
VBEinterrupt(.get_mode_information, ®isters) catch continue;
|
||||
|
||||
if (isValidVideoMode(&mode) and mode.resolution_x == desired_width and mode.resolution_y == desired_height and mode.bpp == edid_bpp) {
|
||||
// lib.log.debug("Video mode setting", .{});
|
||||
setVideoMode(video_mode_number) catch continue;
|
||||
// lib.log.debug("Video mode set", .{});
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Mode = extern struct {
|
||||
mode_attributes: Attributes,
|
||||
wina_attributes: u8,
|
||||
winb_attributes: u8,
|
||||
win_granularity: u16,
|
||||
win_size: u16,
|
||||
wina_segment: u16,
|
||||
winb_segment: u16,
|
||||
win_far_pointer: u32 align(2),
|
||||
bytes_per_scanline: u16,
|
||||
|
||||
resolution_x: u16,
|
||||
resolution_y: u16,
|
||||
character_size_x: u8,
|
||||
character_size_y: u8,
|
||||
plane_count: u8,
|
||||
bpp: u8,
|
||||
bank_count: u8,
|
||||
memory_model: MemoryModel,
|
||||
bank_size: u8,
|
||||
image_count: u8,
|
||||
reserved: u8 = 0,
|
||||
|
||||
red_mask_size: u8,
|
||||
red_mask_shift: u8,
|
||||
green_mask_size: u8,
|
||||
green_mask_shift: u8,
|
||||
blue_mask_size: u8,
|
||||
blue_mask_shift: u8,
|
||||
reserved_mask_size: u8,
|
||||
reserved_mask_shift: u8,
|
||||
direct_color_info: u8,
|
||||
|
||||
framebuffer_address: u32 align(2),
|
||||
reserved_arr: [6]u8,
|
||||
|
||||
linear_bytes_per_scanline: u16,
|
||||
banked_image_count: u8,
|
||||
linear_image_count: u8,
|
||||
linear_red_mask_size: u8,
|
||||
linear_red_mask_shift: u8,
|
||||
linear_green_mask_size: u8,
|
||||
linear_green_mask_shift: u8,
|
||||
linear_blue_mask_size: u8,
|
||||
linear_blue_mask_shift: u8,
|
||||
linear_reserved_mask_size: u8,
|
||||
linear_reserved_mask_shift: u8,
|
||||
max_pixel_clock: u32 align(2),
|
||||
|
||||
reserved0: [189]u8,
|
||||
|
||||
comptime {
|
||||
assert(@sizeOf(Mode) == 0x100);
|
||||
}
|
||||
|
||||
pub const MemoryModel = enum(u8) {
|
||||
text_mode = 0x00,
|
||||
cga_graphics = 0x01,
|
||||
hercules_graphics = 0x02,
|
||||
planar = 0x03,
|
||||
packed_pixel = 0x04,
|
||||
non_chain_4_256_color = 0x05,
|
||||
direct_color = 0x06,
|
||||
yuv = 0x07,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const Attributes = packed struct(u16) {
|
||||
mode_supported_by_hardware: bool,
|
||||
reserved: u1 = 0,
|
||||
TTY_output_function_supported_by_BIOS: bool,
|
||||
color: bool,
|
||||
graphics: bool,
|
||||
vga_incompatible: bool,
|
||||
vga_incompatible_window_mode: bool,
|
||||
linear_framebuffer: bool,
|
||||
double_scan_mode: bool,
|
||||
interlaced_mode: bool,
|
||||
hardware_triple_buffering: bool,
|
||||
hardware_stereoscopic_display: bool,
|
||||
dual_display_start_address: bool,
|
||||
reserved0: u3 = 0,
|
||||
};
|
||||
|
||||
pub const Number = packed struct(u16) {
|
||||
number: u8,
|
||||
is_VESA: bool,
|
||||
reserved: u2 = 0,
|
||||
refresh_rate_control_select: bool,
|
||||
reserved0: u2 = 0,
|
||||
linear_flat_frame_buffer_select: bool,
|
||||
preserve_display_memory_select: bool,
|
||||
};
|
||||
|
||||
pub fn defaultIsValid(mode: *const VBE.Mode) bool {
|
||||
return mode.memory_model == .direct_color and mode.mode_attributes.linear_framebuffer;
|
||||
}
|
||||
};
|
||||
|
||||
const ReturnValue = enum(u8) {
|
||||
successful = 0,
|
||||
failure = 1,
|
||||
not_supported_in_hardware = 2,
|
||||
invalid_in_current_video_mode = 3,
|
||||
};
|
||||
|
||||
const Call = enum(u8) {
|
||||
get_controller_information = 0x00,
|
||||
get_mode_information = 0x01,
|
||||
set_mode_information = 0x02,
|
||||
get_edid_information = 0x15,
|
||||
};
|
||||
|
||||
const interrupt_number = 0x10;
|
||||
const vbe_code = 0x4f;
|
||||
|
||||
pub fn VBEinterrupt(call: Call, registers: *Registers) !void {
|
||||
const source_ax = @as(u16, vbe_code << 8) | @intFromEnum(call);
|
||||
registers.eax = source_ax;
|
||||
interrupt(interrupt_number, registers, registers);
|
||||
|
||||
const ax = @as(u16, @truncate(registers.eax));
|
||||
const al = @as(u8, @truncate(ax));
|
||||
const is_supported = al == vbe_code;
|
||||
if (!is_supported) return Error.not_supported;
|
||||
|
||||
const ah = @as(u8, @truncate(ax >> 8));
|
||||
if (ah > 3) @panic("Return value too high");
|
||||
const return_value = @as(ReturnValue, @enumFromInt(ah));
|
||||
return switch (return_value) {
|
||||
.failure => Error.failure,
|
||||
.not_supported_in_hardware => Error.not_supported_in_hardware,
|
||||
.invalid_in_current_video_mode => Error.invalid_in_current_video_mode,
|
||||
.successful => {},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getControllerInformation(vbe_info: *VBE.Information) VBE.Error!void {
|
||||
var registers = Registers{};
|
||||
|
||||
registers.edi = @intFromPtr(vbe_info);
|
||||
try VBEinterrupt(.get_controller_information, ®isters);
|
||||
}
|
||||
|
||||
pub const Error = error{
|
||||
bad_signature,
|
||||
unsupported_version,
|
||||
not_supported,
|
||||
failure,
|
||||
not_supported_in_hardware,
|
||||
invalid_in_current_video_mode,
|
||||
};
|
||||
|
||||
const EDID = extern struct {
|
||||
padding: [8]u8,
|
||||
manufacturer_id_be: u16 align(1),
|
||||
edid_id_code: u16 align(1),
|
||||
serial_number: u32 align(1),
|
||||
man_week: u8,
|
||||
man_year: u8,
|
||||
edid_version: u8,
|
||||
edid_revision: u8,
|
||||
video_input_type: u8,
|
||||
max_horizontal_size: u8,
|
||||
max_vertical_size: u8,
|
||||
gamma_factor: u8,
|
||||
dpms_flags: u8,
|
||||
chroma_info: [10]u8,
|
||||
est_timings1: u8,
|
||||
est_timings2: u8,
|
||||
man_res_timing: u8,
|
||||
std_timing_id: [8]u16 align(1),
|
||||
det_timing_desc1: [18]u8,
|
||||
det_timing_desc2: [18]u8,
|
||||
det_timing_desc3: [18]u8,
|
||||
det_timing_desc4: [18]u8,
|
||||
unused: u8,
|
||||
checksum: u8,
|
||||
|
||||
comptime {
|
||||
assert(@sizeOf(EDID) == 0x80);
|
||||
}
|
||||
|
||||
pub fn getWidth(edid: *const EDID) u16 {
|
||||
return edid.det_timing_desc1[2] + (@as(u16, edid.det_timing_desc1[4] & 0xf0) << 4);
|
||||
}
|
||||
|
||||
pub fn getHeight(edid: *const EDID) u16 {
|
||||
return edid.det_timing_desc1[5] + (@as(u16, edid.det_timing_desc1[7] & 0xf0) << 4);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn getEDIDInfo() VBE.Error!EDID {
|
||||
var edid_info: EDID = undefined;
|
||||
|
||||
var registers = Registers{};
|
||||
registers.ds = segment(@intFromPtr(&edid_info));
|
||||
registers.es = registers.ds;
|
||||
registers.edi = offset(@intFromPtr(&edid_info));
|
||||
registers.ebx = 1;
|
||||
|
||||
try VBEinterrupt(.get_edid_information, ®isters);
|
||||
|
||||
return edid_info;
|
||||
}
|
||||
|
||||
pub fn setVideoMode(video_mode_number: u16) VBE.Error!void {
|
||||
var registers = Registers{};
|
||||
registers.ebx = @as(u32, video_mode_number) | (1 << 14);
|
||||
try VBEinterrupt(.set_mode_information, ®isters);
|
||||
}
|
||||
};
|
||||
369
src/bootloader/limine.zig
Normal file
369
src/bootloader/limine.zig
Normal 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,
|
||||
};
|
||||
};
|
||||
9
src/bootloader/limine/LICENSE.md
Normal file
9
src/bootloader/limine/LICENSE.md
Normal 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.
|
||||
37
src/bootloader/limine/arch/x86_64/linker_script.ld
Normal file
37
src/bootloader/limine/arch/x86_64/linker_script.ld
Normal 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 = .);
|
||||
}
|
||||
BIN
src/bootloader/limine/installables/BOOTAA64.EFI
Executable file
BIN
src/bootloader/limine/installables/BOOTAA64.EFI
Executable file
Binary file not shown.
BIN
src/bootloader/limine/installables/BOOTIA32.EFI
Executable file
BIN
src/bootloader/limine/installables/BOOTIA32.EFI
Executable file
Binary file not shown.
BIN
src/bootloader/limine/installables/BOOTX64.EFI
Executable file
BIN
src/bootloader/limine/installables/BOOTX64.EFI
Executable file
Binary file not shown.
9
src/bootloader/limine/installables/LICENSE.md
Normal file
9
src/bootloader/limine/installables/LICENSE.md
Normal 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.
|
||||
45
src/bootloader/limine/installables/Makefile
Normal file
45
src/bootloader/limine/installables/Makefile
Normal 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 $@
|
||||
541
src/bootloader/limine/installables/install-sh
Executable file
541
src/bootloader/limine/installables/install-sh
Executable 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:
|
||||
BIN
src/bootloader/limine/installables/limine-cd-efi.bin
Normal file
BIN
src/bootloader/limine/installables/limine-cd-efi.bin
Normal file
Binary file not shown.
BIN
src/bootloader/limine/installables/limine-cd.bin
Normal file
BIN
src/bootloader/limine/installables/limine-cd.bin
Normal file
Binary file not shown.
920
src/bootloader/limine/installables/limine-deploy.c
Normal file
920
src/bootloader/limine/installables/limine-deploy.c
Normal 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;
|
||||
}
|
||||
BIN
src/bootloader/limine/installables/limine-pxe.bin
Normal file
BIN
src/bootloader/limine/installables/limine-pxe.bin
Normal file
Binary file not shown.
7
src/bootloader/limine/installables/limine-version.c
Normal file
7
src/bootloader/limine/installables/limine-version.c
Normal file
@ -0,0 +1,7 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#define LIMINE_VERSION "4.20230120.0"
|
||||
|
||||
int main(void) {
|
||||
puts(LIMINE_VERSION);
|
||||
}
|
||||
13
src/bootloader/limine/installables/limine.cfg
Normal file
13
src/bootloader/limine/installables/limine.cfg
Normal 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
|
||||
466
src/bootloader/limine/installables/limine.h
Normal file
466
src/bootloader/limine/installables/limine.h
Normal 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
|
||||
BIN
src/bootloader/limine/installables/limine.sys
Executable file
BIN
src/bootloader/limine/installables/limine.sys
Executable file
Binary file not shown.
1676
src/bootloader/limine/installer.zig
Normal file
1676
src/bootloader/limine/installer.zig
Normal file
File diff suppressed because it is too large
Load Diff
332
src/bootloader/limine/main.zig
Normal file
332
src/bootloader/limine/main.zig
Normal 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),
|
||||
}
|
||||
}
|
||||
0
src/bootloader/limine/test.zig
Normal file
0
src/bootloader/limine/test.zig
Normal file
27
src/bootloader/rise/bios/linker_script.ld
Normal file
27
src/bootloader/rise/bios/linker_script.ld
Normal 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 = .;
|
||||
}
|
||||
275
src/bootloader/rise/bios/main.zig
Normal file
275
src/bootloader/rise/bios/main.zig
Normal 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));
|
||||
};
|
||||
}
|
||||
0
src/bootloader/rise/bios/test.zig
Normal file
0
src/bootloader/rise/bios/test.zig
Normal file
113
src/bootloader/rise/bios/unreal_mode.S
Normal file
113
src/bootloader/rise/bios/unreal_mode.S
Normal 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
|
||||
436
src/bootloader/rise/uefi/main.zig
Normal file
436
src/bootloader/rise/uefi/main.zig
Normal 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));
|
||||
};
|
||||
}
|
||||
0
src/bootloader/rise/uefi/test.zig
Normal file
0
src/bootloader/rise/uefi/test.zig
Normal file
48
src/bootloader/todo/draw_context.zig
Normal file
48
src/bootloader/todo/draw_context.zig
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
51
src/bootloader/todo/font.zig
Normal file
51
src/bootloader/todo/font.zig
Normal 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
136
src/bootloader/todo/smp.zig
Normal 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
88
src/bootloader/uefi.zig
Normal 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
502
src/common.zig
Normal 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
435
src/cpu.zig
Normal 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
13
src/cpu/arch.zig
Normal 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
1492
src/cpu/arch/x86/64/init.zig
Normal file
File diff suppressed because it is too large
Load Diff
39
src/cpu/arch/x86/64/linker_script.ld
Normal file
39
src/cpu/arch/x86/64/linker_script.ld
Normal 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 = .);
|
||||
}
|
||||
281
src/cpu/arch/x86/64/syscall.zig
Normal file
281
src/cpu/arch/x86/64/syscall.zig
Normal 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
265
src/cpu/arch/x86_64.zig
Normal 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
419
src/cpu/capabilities.zig
Normal 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
37
src/cpu/main.zig
Normal 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
21
src/cpu/test.zig
Normal 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
38
src/cpu/test_runner.zig
Normal 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
74
src/host.zig
Normal 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;
|
||||
250
src/host/disk_image_builder.zig
Normal file
250
src/host/disk_image_builder.zig
Normal 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;
|
||||
}
|
||||
405
src/host/disk_image_builder/boot_disk.zig
Normal file
405
src/host/disk_image_builder/boot_disk.zig
Normal 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);
|
||||
}
|
||||
};
|
||||
302
src/host/disk_image_builder/main.zig
Normal file
302
src/host/disk_image_builder/main.zig
Normal 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');
|
||||
}
|
||||
};
|
||||
224
src/host/disk_image_builder/test.zig
Normal file
224
src/host/disk_image_builder/test.zig
Normal 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
350
src/host/runner/main.zig
Normal 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
6
src/host/test.zig
Normal file
@ -0,0 +1,6 @@
|
||||
const lib = @import("lib");
|
||||
const host = @import("host");
|
||||
test "Host tests" {
|
||||
_ = lib;
|
||||
_ = host;
|
||||
}
|
||||
1080
src/lib.zig
Normal file
1080
src/lib.zig
Normal file
File diff suppressed because it is too large
Load Diff
34
src/lib/arch.zig
Normal file
34
src/lib/arch.zig
Normal 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
6
src/lib/arch/x86.zig
Normal 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 };
|
||||
343
src/lib/arch/x86/64/registers.zig
Normal file
343
src/lib/arch/x86/64/registers.zig
Normal 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);
|
||||
59
src/lib/arch/x86/common.zig
Normal file
59
src/lib/arch/x86/common.zig
Normal 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
34
src/lib/arch/x86_64.zig
Normal 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
6
src/lib/config.zig
Normal 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
13
src/lib/crc32.zig
Normal 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
123
src/lib/disk.zig
Normal 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,
|
||||
};
|
||||
};
|
||||
231
src/lib/extern_enum_array.zig
Normal file
231
src/lib/extern_enum_array.zig
Normal 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
18
src/lib/filesystem.zig
Normal 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
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
184
src/lib/graphics.zig
Normal 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
21
src/lib/nls.zig
Normal 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
125
src/lib/nls/ascii.zig
Normal 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,
|
||||
};
|
||||
11
src/lib/partition_table.zig
Normal file
11
src/lib/partition_table.zig
Normal 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;
|
||||
416
src/lib/partition_table/gpt.zig
Normal file
416
src/lib/partition_table/gpt.zig
Normal 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);
|
||||
}
|
||||
}
|
||||
357
src/lib/partition_table/mbr.zig
Normal file
357
src/lib/partition_table/mbr.zig
Normal 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
13
src/lib/psf1.zig
Normal 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
114
src/privileged.zig
Normal 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
247
src/privileged/acpi.zig
Normal 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
22
src/privileged/arch.zig
Normal 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;
|
||||
11
src/privileged/arch/x86.zig
Normal file
11
src/privileged/arch/x86.zig
Normal 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;
|
||||
14
src/privileged/arch/x86/32/io.zig
Normal file
14
src/privileged/arch/x86/32/io.zig
Normal 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;
|
||||
148
src/privileged/arch/x86/64/apic.zig
Normal file
148
src/privileged/arch/x86/64/apic.zig
Normal 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,
|
||||
};
|
||||
}
|
||||
44
src/privileged/arch/x86/64/io.zig
Normal file
44
src/privileged/arch/x86/64/io.zig
Normal 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;
|
||||
1088
src/privileged/arch/x86/64/paging.zig
Normal file
1088
src/privileged/arch/x86/64/paging.zig
Normal file
File diff suppressed because it is too large
Load Diff
373
src/privileged/arch/x86/64/registers.zig
Normal file
373
src/privileged/arch/x86/64/registers.zig
Normal 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),
|
||||
);
|
||||
}
|
||||
};
|
||||
65
src/privileged/arch/x86/common.zig
Normal file
65
src/privileged/arch/x86/common.zig
Normal 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,
|
||||
}
|
||||
}
|
||||
227
src/privileged/arch/x86_64.zig
Normal file
227
src/privileged/arch/x86_64.zig
Normal 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
23
src/rise.zig
Normal 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
7
src/rise/arch.zig
Normal 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
155
src/rise/arch/x64_64.zig
Normal 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
Loading…
x
Reference in New Issue
Block a user