initial commit

This commit is contained in:
Break27 2025-05-09 23:16:25 +08:00
commit 207c775cf5
59 changed files with 4113 additions and 0 deletions

3
.gitignore vendored Executable file
View File

@ -0,0 +1,3 @@
/.vscode
/.zig-cache
/zig-out

1
.zigversion Normal file
View File

@ -0,0 +1 @@
0.14.0

500
build.zig Executable file
View File

@ -0,0 +1,500 @@
const std = @import("std");
const builtin = @import("builtin");
pub fn build(b: *std.Build) !void {
const arch =
b.option(std.Target.Cpu.Arch, "arch", "The target kernel architecture")
orelse builtin.cpu.arch;
var query: std.Target.Query = .{
.cpu_arch = arch,
.os_tag = .freestanding,
.abi = .none,
};
var code_model: std.builtin.CodeModel = .default;
switch (arch) {
.aarch64 => {
const Feature = std.Target.aarch64.Feature;
query.cpu_features_sub.addFeature(@intFromEnum(Feature.fp_armv8));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.crypto));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.neon));
},
.loongarch64 => {
const Feature = std.Target.loongarch.Feature;
query.cpu_features_add.addFeature(@intFromEnum(Feature.f));
query.cpu_features_add.addFeature(@intFromEnum(Feature.d));
},
.riscv64 => {
const Feature = std.Target.riscv.Feature;
query.cpu_features_add.addFeature(@intFromEnum(Feature.f));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.d));
},
.x86_64 => {
const Feature = std.Target.x86.Feature;
query.cpu_features_add.addFeature(@intFromEnum(Feature.soft_float));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.mmx));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.sse));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.sse2));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.avx));
query.cpu_features_sub.addFeature(@intFromEnum(Feature.avx2));
code_model = .kernel;
},
else => std.debug.panic("Unsupported architecture: {s}", .{@tagName(arch)}),
}
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.resolveTargetQuery(query);
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
const kernel_mod = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
.code_model = code_model,
.red_zone = false,
});
const kernel = b.addExecutable(.{
.name = "kernel",
// We will also create a module for our other entry point, 'main.zig'.
.root_module = kernel_mod,
});
// Disable LTO. This prevents Limine requests from being optimized away.
kernel.want_lto = false;
// Add kernel dependencies.
try resolveDependencies(b, kernel_mod);
// Set the linker script.
const linker_script = try generateLinkerScript(b, arch);
kernel.setLinkerScript(linker_script);
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(kernel);
const img = buildBootableImage(b, kernel.getEmittedBin(), arch);
const run_cmd = generateRunCommand(b, img, arch);
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());
// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the kernel");
run_step.dependOn(&run_cmd.step);
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const exe_unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_exe_unit_tests.step);
}
fn buildBootableImage(b: *std.Build, kernel: std.Build.LazyPath, arch: std.Target.Cpu.Arch) std.Build.LazyPath {
// Add Limine to the dependency tree.
const limine = b.dependency("limine", .{});
const rootfs = b.addNamedWriteFiles("rootfs");
// Build the bootloader from source.
const exe = b.addExecutable(.{
.name = "limine",
.target = b.resolveTargetQuery(.{}),
.optimize = .ReleaseSafe,
});
exe.addCSourceFile(.{
.file = limine.path("limine.c"),
.flags = &.{"-std=c99"},
});
exe.linkLibC();
_ = rootfs.addCopyFile(b.path("lib/limine/limine.conf"), "boot/limine/limine.conf");
_ = rootfs.addCopyFile(kernel, "boot/kernel");
const run_cmd = b.addSystemCommand(switch (arch) {
.aarch64 => block: {
_ = rootfs.addCopyFile(limine.path("limine-uefi-cd.bin"), "boot/limine/limine-uefi-cd.bin");
_ = rootfs.addCopyFile(limine.path("BOOTAA64.EFI"), "EFI/BOOT/BOOTAA64.EFI");
break :block &.{
"xorriso", "-as", "mkisofs",
"-R", "-r", "-J",
"-hfsplus",
"-apm-block-size", "2048",
"--efi-boot", "boot/limine/limine-uefi-cd.bin",
"-efi-boot-part",
"--efi-boot-image",
"--protective-msdos-label",
};
},
.loongarch64 => block: {
_ = rootfs.addCopyFile(limine.path("limine-uefi-cd.bin"), "boot/limine/limine-uefi-cd.bin");
_ = rootfs.addCopyFile(limine.path("BOOTLOONGARCH64.EFI"), "EFI/BOOT/BOOTLOONGARCH64.EFI");
break :block &.{
"xorriso", "-as", "mkisofs",
"-R", "-r", "-J",
"-hfsplus",
"-apm-block-size", "2048",
"--efi-boot", "boot/limine/limine-uefi-cd.bin",
"-efi-boot-part",
"--efi-boot-image",
"--protective-msdos-label",
};
},
.riscv64 => block: {
_ = rootfs.addCopyFile(limine.path("limine-uefi-cd.bin"), "boot/limine/limine-uefi-cd.bin");
_ = rootfs.addCopyFile(limine.path("BOOTRISCV64.EFI"), "EFI/BOOT/BOOTRISCV64.EFI");
break :block &.{
"xorriso", "-as", "mkisofs",
"-R", "-r", "-J",
"-hfsplus",
"-apm-block-size", "2048",
"--efi-boot", "boot/limine/limine-uefi-cd.bin",
"-efi-boot-part",
"--efi-boot-image",
"--protective-msdos-label",
};
},
.x86_64 => block: {
_ = rootfs.addCopyFile(limine.path("limine-bios.sys"), "boot/limine/limine-bios.sys");
_ = rootfs.addCopyFile(limine.path("limine-bios-cd.bin"), "boot/limine/limine-bios-cd.bin");
_ = rootfs.addCopyFile(limine.path("limine-uefi-cd.bin"), "boot/limine/limine-uefi-cd.bin");
_ = rootfs.addCopyFile(limine.path("BOOTX64.EFI"), "EFI/BOOT/BOOTX64.EFI");
_ = rootfs.addCopyFile(limine.path("BOOTIA32.EFI"), "EFI/BOOT/BOOTIA32.EFI");
break :block &.{
"xorriso", "-as", "mkisofs",
"-R", "-r", "-J",
"-b", "boot/limine/limine-bios-cd.bin",
"-no-emul-boot",
"-boot-load-size", "4",
"-boot-info-table",
"-hfsplus",
"-apm-block-size", "2048",
"--efi-boot", "boot/limine/limine-uefi-cd.bin",
"-efi-boot-part",
"--efi-boot-image",
"--protective-msdos-label",
};
},
else => unreachable,
});
run_cmd.step.dependOn(&rootfs.step);
run_cmd.addDirectoryArg(rootfs.getDirectory());
run_cmd.addArg("-o");
// Retrieve the bootable image from command.
const image = run_cmd.addOutputFileArg("bootable.iso");
// Install Limine stage 1 and 2 for legacy BIOS boot.
// This is only required on x86_64, but we are deploying for all anyway.
const deploy = b.addRunArtifact(exe);
deploy.step.dependOn(&run_cmd.step);
deploy.addArg("bios-install");
deploy.addFileArg(image);
const step = b.step("image", "Generate a bootable ISO image");
step.dependOn(&deploy.step);
return image;
}
fn generateLinkerScript(b: *std.Build, arch: std.Target.Cpu.Arch) !std.Build.LazyPath {
const path = "linker.ld";
const data = try std.mem.join(b.allocator, "\n", &.{
switch (arch) {
.aarch64 => "OUTPUT_FORMAT(elf64-littleaarch64)",
.loongarch64 => "OUTPUT_FORMAT(elf64-loongarch)",
.riscv64 => "OUTPUT_FORMAT(elf64-littleriscv)",
.x86_64 => "OUTPUT_FORMAT(elf64-x86-64)",
else => unreachable,
},
\\ENTRY(_start)
\\
\\/* Define the program headers we want so the bootloader gives us the right */
\\/* MMU permissions; this also allows us to exert more control over the linking */
\\/* process. */
\\PHDRS
\\{
\\ text PT_LOAD;
\\ rodata PT_LOAD;
\\ data PT_LOAD;
\\}
\\
\\SECTIONS
\\{
\\ /* We want to be placed in the topmost 2GiB of the address space, for optimisations */
\\ /* and because that is what the Limine spec mandates. */
\\ /* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
\\ /* that is the beginning of the region. */
\\ . = 0xffffffff80000000;
\\
\\ .text : {
\\ *(.text .text.*)
\\ } :text
\\
\\ /* Move to the next memory page for .rodata */
\\ . = ALIGN(CONSTANT(MAXPAGESIZE));
\\
\\ .rodata : {
\\ *(.rodata .rodata.*)
\\ } :rodata
\\
\\ /* Move to the next memory page for .data */
\\ . = ALIGN(CONSTANT(MAXPAGESIZE));
\\
\\ .data : {
\\ *(.data .data.*)
\\
\\ /* Place the sections that contain the Limine requests as part of the .data */
\\ /* output section. */
\\ KEEP(*(.requests_start_marker))
\\ KEEP(*(.requests))
\\ KEEP(*(.requests_end_marker))
\\ } :data
\\
\\ /* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
\\ /* unnecessary zeros will be written to the binary. */
\\ /* If you need, for example, .init_array and .fini_array, those should be placed */
\\ /* above this. */
\\ .bss : {
\\ *(.bss .bss.*)
\\ *(COMMON)
\\ } :data
\\
\\ /* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
\\ /DISCARD/ : {
\\ *(.eh_frame*)
\\ *(.note .note.*)
\\ }
\\}
});
const script = b.addWriteFile(path, data);
b.getInstallStep().dependOn(&script.step);
return script.getDirectory().path(b, path);
}
fn generateRunCommand(b: *std.Build, image: std.Build.LazyPath, arch: std.Target.Cpu.Arch) *std.Build.Step.Run {
// Add edk2_ovmf to the dependency tree
const edk2 = b.dependency("edk2", .{});
const fs = b.addWriteFiles();
var code: std.Build.LazyPath = undefined;
var vars: std.Build.LazyPath = undefined;
// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd: *std.Build.Step.Run = switch (arch) {
.aarch64 => block: {
code = fs.addCopyFile(edk2.path("bin/RELEASEAARCH64_QEMU_EFI.fd"), "code.fd");
vars = fs.addCopyFile(edk2.path("bin/RELEASEAARCH64_QEMU_VARS.fd"), "vars.fd");
const dd1_cmd = b.addSystemCommand(&.{"dd", "if=/dev/zero", "bs=1", "count=0", "seek=67108864"});
dd1_cmd.addPrefixedFileArg("of=", code);
dd1_cmd.step.dependOn(&fs.step);
const dd2_cmd = b.addSystemCommand(&.{"dd", "if=/dev/zero", "bs=1", "count=0", "seek=67108864"});
dd2_cmd.addPrefixedFileArg("of=", vars);
dd2_cmd.step.dependOn(&fs.step);
const run_cmd = b.addSystemCommand(&.{
"qemu-system-aarch64",
"-M", "virt",
"-cpu", "cortex-a72",
"-device", "ramfb",
"-device", "qemu-xhci",
"-device", "usb-kbd",
"-device", "usb-mouse",
"-serial", "stdio",
});
run_cmd.step.dependOn(&dd1_cmd.step);
run_cmd.step.dependOn(&dd2_cmd.step);
break :block run_cmd;
},
.loongarch64 => block: {
code = fs.addCopyFile(edk2.path("bin/RELEASELOONGARCH64_QEMU_EFI.fd"), "code.fd");
vars = fs.addCopyFile(edk2.path("bin/RELEASELOONGARCH64_QEMU_VARS.fd"), "vars.fd");
const dd1_cmd = b.addSystemCommand(&.{"dd", "if=/dev/zero", "bs=1", "count=0", "seek=5242880"});
dd1_cmd.addPrefixedFileArg("of=", code);
dd1_cmd.step.dependOn(&fs.step);
const dd2_cmd = b.addSystemCommand(&.{"dd", "if=/dev/zero", "bs=1", "count=0", "seek=5242880"});
dd2_cmd.addPrefixedFileArg("of=", vars);
dd2_cmd.step.dependOn(&fs.step);
const run_cmd = b.addSystemCommand(&.{
"qemu-system-loongarch64",
"-M", "virt",
"-cpu", "la464",
"-device", "ramfb",
"-device", "qemu-xhci",
"-device", "usb-kbd",
"-device", "usb-mouse",
"-serial", "stdio",
});
run_cmd.step.dependOn(&dd1_cmd.step);
run_cmd.step.dependOn(&dd2_cmd.step);
break :block run_cmd;
},
.riscv64 => block: {
code = fs.addCopyFile(edk2.path("bin/RELEASERISCV64_VIRT_CODE.fd"), "code.fd");
vars = fs.addCopyFile(edk2.path("bin/RELEASERISCV64_VIRT_VARS.fd"), "vars.fd");
const dd1_cmd = b.addSystemCommand(&.{"dd", "if=/dev/zero", "bs=1", "count=0", "seek=33554432"});
dd1_cmd.addPrefixedFileArg("of=", code);
dd1_cmd.step.dependOn(&fs.step);
const dd2_cmd = b.addSystemCommand(&.{"dd", "if=/dev/zero", "bs=1", "count=0", "seek=33554432"});
dd2_cmd.addPrefixedFileArg("of=", vars);
dd2_cmd.step.dependOn(&fs.step);
const run_cmd = b.addSystemCommand(&.{
"qemu-system-riscv64",
"-M", "virt",
"-cpu", "rv64",
"-device", "ramfb",
"-device", "qemu-xhci",
"-device", "usb-kbd",
"-device", "usb-mouse",
"-serial", "stdio",
});
run_cmd.step.dependOn(&dd1_cmd.step);
run_cmd.step.dependOn(&dd2_cmd.step);
break :block run_cmd;
},
.x86_64 => block: {
code = edk2.path("bin/RELEASEX64_OVMF_CODE.fd");
vars = edk2.path("bin/RELEASEX64_OVMF_VARS.fd");
break :block b.addSystemCommand(&.{
"qemu-system-x86_64",
"-M", "q35",
"-device", "isa-debug-exit,iobase=0xf4,iosize=0x04",
"-serial", "stdio",
"-rtc", "clock=vm",
});
},
else => unreachable,
};
run_cmd.addArg("-drive");
run_cmd.addPrefixedFileArg("if=pflash,unit=0,format=raw,readonly=on,file=", code);
run_cmd.addArg("-drive");
run_cmd.addPrefixedFileArg("if=pflash,unit=1,format=raw,file=", vars);
run_cmd.addArg("-cdrom");
run_cmd.addFileArg(image);
return run_cmd;
}
fn resolveDependencies(b: *std.Build, kernel: *std.Build.Module) !void {
const libk = block: {
const file = b.path("lib/kernel/kernel.zig");
const mod = b.addModule("kernel", .{
.root_source_file = file,
.optimize = kernel.optimize,
.target = kernel.resolved_target
});
break :block mod;
};
const limine = block: {
const file = b.path("lib/limine/limine.zig");
const mod = b.createModule(.{ .root_source_file = file });
break :block mod;
};
const uacpi = block: {
const dependency = b.dependency("uacpi", .{});
const file = b.path("lib/uacpi/uacpi.zig");
const mod = b.createModule(.{ .root_source_file = file });
var flags: std.ArrayList([]const u8) = .init(b.allocator);
for (b.debug_log_scopes) |scope| {
if (std.mem.eql(u8, scope, "uacpi")) {
try flags.append("-DUACPI_DEFAULT_LOG_LEVEL=UACPI_LOG_TRACE");
break;
}
}
mod.addIncludePath(dependency.path("include"));
mod.addCSourceFiles(.{
.root = dependency.path("source"),
.files = &.{
"default_handlers.c",
"event.c",
"interpreter.c",
"io.c",
"mutex.c",
"namespace.c",
"notify.c",
"opcodes.c",
"opregion.c",
"osi.c",
"registers.c",
"resources.c",
"shareable.c",
"sleep.c",
"stdlib.c",
"tables.c",
"types.c",
"uacpi.c",
"utilities.c",
},
.flags = try flags.toOwnedSlice(),
});
break :block mod;
};
kernel.addImport("libk", libk);
kernel.addImport("uacpi", uacpi);
kernel.addImport("limine", limine);
}

61
build.zig.zon Executable file
View File

@ -0,0 +1,61 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = .kernel,
// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",
// Together with name, this represents a globally unique package
// identifier. This field is generated by the Zig toolchain when the
// package is first created, and then *never changes*. This allows
// unambiguous detection of one package being an updated version of
// another.
//
// When forking a Zig project, this id should be regenerated (delete the
// field and run `zig build`) if the upstream project is still maintained.
// Otherwise, the fork is *hostile*, attempting to take control over the
// original project's identity. Thus it is recommended to leave the comment
// on the following line intact, so that it shows up in code reviews that
// modify the field.
.fingerprint = 0x5dd29aabeb5697e0,
// This field is optional.
// This is currently advisory only; Zig does not yet do anything
// with this value.
.minimum_zig_version = "0.14.0",
// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
.edk2 = .{
.url = "https://github.com/retrage/edk2-nightly/archive/9eaa0a48ce51a4bd98941c6cf6d3dec0ffebc2a9.tar.gz",
.hash = "1220c7bb96f5e6d0b1f54e26fa5b053e41ef570184f55b81e9fda795fe5a8463083c",
},
.uacpi = .{
.url = "https://github.com/uACPI/uACPI/archive/refs/tags/1.0.1.tar.gz",
.hash = "12200468fb671c04225f04d87e0fe1131e49ccd153e870be6e858dc80e39cb4e18df",
},
.limine = .{
.url = "https://github.com/limine-bootloader/limine/archive/refs/heads/v8.x-binary.tar.gz",
.hash = "1220412bffeb555ae2aa1a6b18b204adf3cabbcf6c94505d513d851d9884c3a4a7e0",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
// For example...
//"LICENSE",
//"README.md",
},
}

59
lib/kernel/io/IoMap.zig Normal file
View File

@ -0,0 +1,59 @@
pub fn IoMap(comptime T: type) type {
return struct {
pub const Permission = enum {
readonly,
writeonly,
all,
};
pub const Error = error {
NotAvailable,
};
pub const Pin = struct {
kind: type = T,
perm: Permission,
base: usize,
};
pub fn Schematics(comptime U: type) type {
return struct {
impl: T,
init: bool = false,
pub fn open(self: *@This(), address: anytype) void {
self.impl = T.open(address);
self.init = true;
}
pub fn read(self: *@This(), comptime name: []const u8) Error!@field(U{}, name).kind {
if (@field(U{}, name).perm == .writeonly) {
@compileError("attempted to perform a read operation on an unreadable field '" ++ name ++ "'");
}
if (self.init == false) {
return error.NotAvailable;
}
const offset = @field(U{}, name).base;
const address = self.impl.address + offset;
var io = T.open(@intCast(address));
return io.read();
}
pub fn write(self: *@This(), comptime name: []const u8, value: @field(U{}, name).kind) Error!void {
if (@field(U{}, name).perm == .readonly) {
@compileError("attempted to peform a write operation on an unwritable field '" ++ name ++ "'");
}
if (self.init == false) {
return error.NotAvailable;
}
const offset = @field(U{}, name).base;
const address = self.impl.address + offset;
var io = T.open(@intCast(address));
io.write(value);
}
};
}
};
}

47
lib/kernel/io/io.zig Normal file
View File

@ -0,0 +1,47 @@
pub const IoMap = @import("IoMap.zig").IoMap;
pub fn Mmio(comptime T: type) type {
return struct {
address: usize,
pub fn open(address: usize) @This() {
return .{ .address = address };
}
pub fn read(self: *@This()) T {
return @as(*T, @ptrFromInt(self.address)).*;
}
pub fn write(self: *@This(), data: T) void {
@as(*T, @ptrFromInt(self.address)).* = data;
}
};
}
pub fn ReadOnly(comptime T: type) type {
return struct {
inner: T,
pub inline fn open(address: anytype) @This() {
return .{ .inner = .open(address) };
}
pub inline fn read(self: *@This()) void {
return self.inner.read();
}
};
}
pub fn WriteOnly(comptime T: type) type {
return struct {
inner: T,
pub inline fn open(address: anytype) @This() {
return .{ .inner = .open(address) };
}
pub inline fn write(self: *@This(), value: anytype) void {
self.inner.write(value);
}
};
}

5
lib/kernel/kernel.zig Normal file
View File

@ -0,0 +1,5 @@
pub const io = @import("io/io.zig");
pub const utils = @import("utils/utils.zig");
pub const object = @import("object.zig");
pub const machine = @import("machine/machine.zig");
pub const syscall = @import("syscall.zig");

View File

@ -0,0 +1 @@
pub const serial = @import("serial/serial.zig");

View File

@ -0,0 +1,104 @@
const std = @import("std");
const libk = @import("../../kernel.zig");
const IoMap = libk.io.IoMap;
const BitFields = libk.utils.BitFields;
const IntEnFlags = BitFields(packed struct(u4) {
received : bool = false,
sent : bool = false,
errored : bool = false,
status_change : bool = false,
});
const LineStsFlags = BitFields(packed struct(u8) {
input_full : bool = false,
padding_0 : u4 = 0,
output_empty : bool = false,
padding_1 : u2 = 0,
});
pub fn SerialPort(comptime Io: type) type {
return struct {
io: IoMap(Io).Schematics(struct {
/// Data port
data : IoMap(u8).Pin = .{ .perm = .all, .base = 0 },
/// Interrupt enable port
int_en : IoMap(u8).Pin = .{ .perm = .writeonly, .base = 1 },
/// Fifo control port
fifo_ctrl : IoMap(u8).Pin = .{ .perm = .writeonly, .base = 2 },
/// Line control port
line_ctrl : IoMap(u8).Pin = .{ .perm = .writeonly, .base = 3 },
/// Modem control port
modem_ctrl : IoMap(u8).Pin = .{ .perm = .writeonly, .base = 4 },
/// Line status port
line_sts : IoMap(u8).Pin = .{ .perm = .readonly, .base = 5 },
}),
pub fn open(self: *@This(), address: anytype) void {
self.io.open(address);
}
pub fn init(self: *@This()) !void {
// Disable interrupts
try self.io.write("int_en", IntEnFlags.init(.{}).bits());
// Enable DLAB
try self.io.write("line_ctrl", 0x80);
// Set maximum speed to 38400 bps by configuring DLL and DLM
try self.io.write("data", 0x03);
try self.io.write("int_en", IntEnFlags.init(.{}).bits());
// Disable DLAB and set data word length to 8 bits
try self.io.write("line_ctrl", 0x03);
// Enable FIFO, clear TX/RX queues and
// set interrupt watermark at 14 bytes
try self.io.write("fifo_ctrl", 0xC7);
// Mark data terminal ready, signal request to send
// and enable auxilliary output #2 (used as interrupt line for CPU)
try self.io.write("modem_ctrl", 0x0B);
// Enable interrupts
try self.io.write("int_en", IntEnFlags.init(.{ .received = true }).bits());
}
pub fn write(self: *@This(), data: []const u8) void {
for (data) |byte| {
switch (byte) {
8, 0x7f => {
self.send( 8 ) catch return;
self.send(' ') catch return;
self.send( 8 ) catch return;
},
else => {
self.send(byte) catch return;
},
}
}
}
pub fn send(self: *@This(), data: u8) !void {
while (!(try self.lineSts()).fields.output_empty) {
std.atomic.spinLoopHint();
}
try self.io.write("data", data);
}
pub fn receive(self: *@This()) !u8 {
while (!(try self.lineSts()).fields.input_full) {
std.atomic.spinLoopHint();
}
return try self.io.read("data");
}
inline fn lineSts(self: *@This()) !LineStsFlags {
const data = try self.io.read("line_sts");
const result = LineStsFlags.fromBitsTruncate(data);
return result;
}
};
}

View File

@ -0,0 +1,168 @@
const std = @import("std");
const libk = @import("../../kernel.zig");
const Mmio = libk.io.Mmio;
const IoMap = libk.io.IoMap;
const BitFields = libk.utils.BitFields;
const Flags = BitFields(packed struct(u9) {
/// Clear to send
cts : bool = false,
/// Data set ready
dsr : bool = false,
/// UART busy transmitting data
dcd : bool = false,
/// Receive FIFO is empty
busy : bool = false,
/// Transmit FIFO is empty
rxfe : bool = false,
/// Trasnmit FIFO is full
txff : bool = false,
/// Receive FIFO is full
rxff : bool = false,
/// Transmit FIFO is empty
txfe : bool = false,
/// Ring indicator
ri : bool = false,
});
const ReceiveStatus = BitFields(packed struct(u4) {
/// Framing error
fe: bool = false,
/// Parity error
pe: bool = false,
/// Break error
be: bool = false,
/// Overrun error
oe: bool = false,
});
const Control = BitFields(packed struct(u16) {
/// UART Enable
uarten : bool = false,
/// Serial InfraRed (SIR) Enable
siren : bool = false,
/// Serial InfraRed (SIR) Low-power
sirlp : bool = false,
/// Loopback Enable
lbe : bool = false,
/// Bits 6:3 reserved
reserved : u4 = 0,
/// Transmit Enable
txe : bool = false,
/// Receive Enable
rxe : bool = false,
/// Data Trasmit Ready
dtr : bool = false,
/// Request to Send
rts : bool = false,
/// Complement of nUARTOut1
out_1 : bool = false,
/// Complement of nUARTOut2
out_2 : bool = false,
/// Request To Send (RTS) Hardware Flow Control Enable
rtsen : bool = false,
/// Clear To Send (CTS) Hardware Flow Control Enable
ctsen : bool = false,
});
pub const SerialPort = struct {
io: IoMap(Mmio(u32)).Schematics(struct {
/// Data Register
uartdr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x00 },
/// Receive status / error clear
uartrsr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x04 },
/// Flag register
uartfr : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x18 },
/// IrDA Low power counter register
uartilpr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x20 },
/// Integer baud rate
uartibrd : IoMap(u32).Pin = .{ .perm = .all, .base = 0x24 },
/// Fractional baud rate
uartfbrd : IoMap(u32).Pin = .{ .perm = .all, .base = 0x28 },
/// Line control
uartlcr_h : IoMap(u32).Pin = .{ .perm = .all, .base = 0x2C },
/// Control
uartcr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x30 },
/// Interrupt fifo level select
uartifls : IoMap(u32).Pin = .{ .perm = .all, .base = 0x34 },
/// Interrupt mask set/clear
uartimsc : IoMap(u32).Pin = .{ .perm = .all, .base = 0x38 },
/// Raw interrupt status
uartris : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x3C },
/// Masked interrupt status
uartmis : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x40 },
/// Interrupt clear
uarticr : IoMap(u32).Pin = .{ .perm = .writeonly, .base = 0x44 },
/// Dma control
uartdmacr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x48 },
}),
pub fn open(self: *@This(), address: usize) void {
self.io.open(address);
}
pub fn init(self: *@This(), clock: u32, baud_rate: u32) !void {
const bits = try self.io.read("uartcr");
var ctrl = Control.fromBitsTruncate(bits);
// Disable UART
ctrl.fields.uarten = false;
try self.io.write("uartcr", ctrl.bits());
// Program integer baud rate
const divisor = (clock << 2) / baud_rate;
try self.io.write("uartibrd", divisor >> 6);
// Program fractional baud rate
try self.io.write("uartfbrd", divisor & 0x3F);
// Clear errors
const status = ReceiveStatus.init(.{});
try self.io.write("uartrsr", status.bits());
// Enable UART
const data = Control.init(.{
.rxe = true,
.txe = true,
.uarten = true,
});
try self.io.write("uartcr", data.bits());
}
pub fn write(self: *@This(), data: []const u8) void {
for (data) |byte| {
switch (byte) {
8, 0x7f => {
self.send( 8 ) catch return;
self.send(' ') catch return;
self.send( 8 ) catch return;
},
else => {
self.send(byte) catch return;
},
}
}
}
pub fn send(self: *@This(), data: u32) !void {
while ((try self.flags()).fields.txff) {
std.atomic.spinLoopHint();
}
try self.io.write("uartdr", data);
}
pub fn receive(self: *@This()) !u32 {
while ((try self.flags()).fields.rxff) {
std.atomic.spinLoopHint();
}
return try self.io.read("uartdr");
}
inline fn flags(self: *@This()) !Flags {
const data = try self.io.read("uartfr");
const result = Flags.fromBitsTruncate(data);
return result;
}
};

View File

@ -0,0 +1,2 @@
pub const uart_16550 = @import("16550.zig");
pub const uart_pl011 = @import("pl011.zig");

11
lib/kernel/object.zig Normal file
View File

@ -0,0 +1,11 @@
pub const ObjectType = enum {
untyped,
endpoint,
notification,
cnode,
tcb,
schedctx,
reply,
interrupt,
vspace,
};

0
lib/kernel/syscall.zig Normal file
View File

View File

@ -0,0 +1,30 @@
pub fn BitFields(comptime T: type) type {
return packed struct {
pub const Self = T;
pub const Bits =
if (@typeInfo(T).@"struct".backing_integer) |U| U
else @compileError("T must be a packed struct");
fields: Self,
pub inline fn init(x: Self) @This() {
return .{ .fields = x };
}
pub inline fn fromBits(value: Bits) @This() {
return .init(@bitCast(value));
}
pub inline fn fromBitsTruncate(value: anytype) @This() {
return fromBits(@truncate(value));
}
pub inline fn bits(self: @This()) Bits {
return @bitCast(self);
}
pub inline fn cast(self: @This(), comptime Int: type) Int {
return @intCast(bits(self));
}
};
}

View File

@ -0,0 +1,12 @@
pub const BitFields = @import("BitFields.zig").BitFields;
pub inline fn alignDown(address: usize, alignment: usize) usize {
return address & ~(alignment - 1);
}
pub inline fn alignUp(address: usize, alignment: usize) usize {
const mask = alignment - 1;
if (address & mask == 0) return address;
return (address | mask) + 1;
}

10
lib/limine/limine.conf Executable file
View File

@ -0,0 +1,10 @@
# Timeout in seconds that Limine will use before automatically booting.
timeout: 0
# The entry name that will be displayed in the boot menu.
/Tangerine
# We use the Limine boot protocol.
protocol: limine
# Path to the kernel to boot. boot():/ represents the partition on which limine.conf is located.
kernel_path: boot():/boot/kernel

281
lib/limine/limine.zig Executable file
View File

@ -0,0 +1,281 @@
const builtin = @import("builtin");
pub fn magic(a: u64, b: u64) [4]u64 {
return .{ 0xc7b1dd30df4c8b88, 0x0a82e883a194f07b, a, b };
}
pub const BaseRevision = extern struct {
id: [2]u64 = .{ 0xf9562b2d5c95a6c8, 0x6a7b384944536bdc },
revision: u64,
pub fn is_supported(self: *const volatile @This()) bool {
return self.revision == 0;
}
};
pub const BootloaderInfo = struct {
pub const Request = extern struct {
id: [4]u64 = magic(0xf55038d8e2a1202f, 0x279426fcf5f59740),
revision: u64 = 0,
response: ?*Response = null,
};
pub const Response = extern struct {
revision: u64,
name: [*:0]u8,
version: [*:0]u8,
};
};
pub const BootTime = struct {
pub const Request = extern struct {
id: [4]u64 = magic(0x502746e184c088aa, 0xfbc5ec83e6327893),
revision: u64 = 0,
response: ?*Response = null,
};
pub const Response = extern struct {
revision: u64,
boot_time: i64,
};
};
pub const File = extern struct {
revision: u64,
address: [*]u8,
size: u64,
path: [*:0]u8,
cmdline: [*:0]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 Uuid = extern struct {
a: u32,
b: u16,
c: u16,
d: [8]u8,
};
pub const MediaType = enum(u32) {
generic = 0,
optical = 1,
tftp = 2,
};
};
pub const Framebuffer = extern struct {
address: [*]u8,
width: u64,
height: u64,
pitch: u64,
bpp: u16,
memory_model: MemoryModel,
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: ?[*]u8,
// Response revision 1
mode_count: u64,
modes: [*]*VideoMode,
pub const MemoryModel = enum(u8) {
rgb = 1,
};
pub const VideoMode = extern struct {
pitch: u64,
width: u64,
height: u64,
bpp: u16,
memory_model: MemoryModel,
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 Request = extern struct {
id: [4]u64 = magic(0x9d5827dcd881dd75, 0xa3148604f6fab11b),
revision: u64 = 1,
response: ?*Response = null
};
pub const Response = extern struct {
revision: u64,
framebuffer_count: u64,
framebuffers: [*]*Framebuffer,
pub inline fn getFramebuffers(self: *@This()) []*Framebuffer {
return self.framebuffers[0..self.framebuffer_count];
}
};
pub inline fn getEdid(self: *@This()) ?[]u8 {
if (self.edid) |edid| {
return edid[0..self.edid_size];
}
}
pub inline fn getModes(self: *@This()) []*VideoMode {
return self.modes[0..self.mode_count];
}
};
pub const Hhdm = struct {
pub const Request = extern struct {
id: [4]u64 = magic(0x48dcf1cb8ad2b852, 0x63984e959a98244b),
revision: u64 = 0,
response: ?*Response = null,
};
pub const Response = extern struct {
revision: u64,
offset: u64,
};
};
pub const Rsdp = struct {
pub const Request = extern struct {
id: [4]u64 = magic(0xc5e77b6b397e7b43, 0x27637845accdcf3c),
revision: u64 = 0,
response: ?*Response = null,
};
pub const Response = extern struct {
revision: u64,
address: u64,
};
};
pub const MemoryMap = struct {
pub const Request = extern struct {
id: [4]u64 = magic(0x67cf3d9d378a806f, 0xe304acdfc50c3c62),
revision: u64 = 0,
response: ?*Response = null,
};
pub const Response = extern struct {
revision: u64,
entry_count: u64,
entries: [*]*Entry,
pub inline fn getEntries(self: *@This()) []*Entry {
return self.entries[0..self.entry_count];
}
};
pub const Entry = extern struct {
base: u64,
length: u64,
type: EntryType,
};
pub const EntryType = 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 Paging = struct {
pub const Mode = switch (builtin.cpu.arch) {
.aarch64, .x86_64 => enum(u64) {
four_level,
five_level,
pub inline fn default() Mode {
return Mode.four_level;
}
pub inline fn max() Mode {
return Mode.five_level;
}
pub inline fn min() Mode {
return Mode.four_level;
}
},
.loongarch64 => enum(u64) {
four_level,
pub inline fn default() Mode {
return Mode.four_level;
}
pub inline fn max() Mode {
return Mode.four_level;
}
pub inline fn min() Mode {
return Mode.four_level;
}
},
.riscv64 => enum(u64) {
sv39,
sv48,
sv57,
pub inline fn default() Mode {
return Mode.sv48;
}
pub inline fn max() Mode {
return Mode.sv57;
}
pub inline fn min() Mode {
return Mode.sv39;
}
},
else => |x| @compileError("Unsupported architecture: " ++ @tagName(x)),
};
pub const Request = extern struct {
id: [4]u64 = magic(0x95c1a0edab0944cb, 0xa4e5cb3842f7488a),
revision: u64 = 0,
response: ?*Response = null,
mode: Mode = .default(),
// Revision 1+
max_mode: Mode = .max(),
min_mode: Mode = .min(),
};
pub const Response = extern struct {
revision: u64,
mode: Mode
};
};
pub const DeviceTreeBlob = struct {
pub const Request = extern struct {
id: [4]u64 = magic(0xb40ddb48fb54bac7, 0x545081493f81ffb7),
revision: u64 = 0,
response: ?*Response = null,
};
pub const Response = extern struct {
revision: u64,
pointer: ?*anyopaque,
};
};

75
lib/uacpi/uacpi.zig Normal file
View File

@ -0,0 +1,75 @@
const C = @cImport({
@cInclude("uacpi/event.h");
@cInclude("uacpi/io.h");
@cInclude("uacpi/namespace.h");
@cInclude("uacpi/notify.h");
@cInclude("uacpi/opregion.h");
@cInclude("uacpi/osi.h");
@cInclude("uacpi/resources.h");
@cInclude("uacpi/sleep.h");
@cInclude("uacpi/status.h");
@cInclude("uacpi/tables.h");
@cInclude("uacpi/types.h");
@cInclude("uacpi/uacpi.h");
@cInclude("uacpi/utilities.h");
});
pub const Status = enum(C.uacpi_status) {
ok = C.UACPI_STATUS_OK,
mapping_failed = C.UACPI_STATUS_MAPPING_FAILED,
out_of_memory = C.UACPI_STATUS_OUT_OF_MEMORY,
bad_checksum = C.UACPI_STATUS_BAD_CHECKSUM,
invalid_signature = C.UACPI_STATUS_INVALID_SIGNATURE,
invalid_table_length = C.UACPI_STATUS_INVALID_TABLE_LENGTH,
not_found = C.UACPI_STATUS_NOT_FOUND,
invalid_argument = C.UACPI_STATUS_INVALID_ARGUMENT,
unimplemented = C.UACPI_STATUS_UNIMPLEMENTED,
already_exists = C.UACPI_STATUS_ALREADY_EXISTS,
internal_error = C.UACPI_STATUS_INTERNAL_ERROR,
type_mismatch = C.UACPI_STATUS_TYPE_MISMATCH,
init_level_mismatch = C.UACPI_STATUS_INIT_LEVEL_MISMATCH,
namespace_node_dangling = C.UACPI_STATUS_NAMESPACE_NODE_DANGLING,
no_handler = C.UACPI_STATUS_NO_HANDLER,
no_resource_end_tag = C.UACPI_STATUS_NO_RESOURCE_END_TAG,
compiled_out = C.UACPI_STATUS_COMPILED_OUT,
hardware_timeout = C.UACPI_STATUS_HARDWARE_TIMEOUT,
timeout = C.UACPI_STATUS_TIMEOUT,
overridden = C.UACPI_STATUS_OVERRIDDEN,
denied = C.UACPI_STATUS_DENIED,
// All errors that have bytecode-related origin should go here
aml_undefined_reference = C.UACPI_STATUS_AML_UNDEFINED_REFERENCE,
aml_invalid_namestring = C.UACPI_STATUS_AML_INVALID_NAMESTRING,
aml_object_already_exists = C.UACPI_STATUS_AML_OBJECT_ALREADY_EXISTS,
aml_invalid_opcode = C.UACPI_STATUS_AML_INVALID_OPCODE,
aml_incompatible_object_type = C.UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE,
aml_bad_encoding = C.UACPI_STATUS_AML_BAD_ENCODING,
aml_out_of_bounds_index = C.UACPI_STATUS_AML_OUT_OF_BOUNDS_INDEX,
aml_sync_level_too_high = C.UACPI_STATUS_AML_SYNC_LEVEL_TOO_HIGH,
aml_invalid_resource = C.UACPI_STATUS_AML_INVALID_RESOURCE,
aml_loop_timeout = C.UACPI_STATUS_AML_LOOP_TIMEOUT,
aml_call_stack_depth_limit = C.UACPI_STATUS_AML_CALL_STACK_DEPTH_LIMIT,
};
pub const InterruptResult = enum(C.uacpi_interrupt_ret) {
not_handled = C.UACPI_INTERRUPT_NOT_HANDLED,
handled = C.UACPI_INTERRUPT_HANDLED,
};
pub const LogLevel = enum(C.uacpi_log_level) {
debug = C.UACPI_LOG_DEBUG,
err = C.UACPI_LOG_ERROR,
info = C.UACPI_LOG_INFO,
trace = C.UACPI_LOG_TRACE,
warn = C.UACPI_LOG_WARN,
};
pub const InterruptHandler = *const fn(Handle) callconv(.C) InterruptResult;
pub const Handle = C.uacpi_handle;
pub const CpuFlags = C.uacpi_cpu_flags;
pub const PhysAddr = C.uacpi_phys_addr;
pub const ThreadId = C.uacpi_thread_id;
pub const WorkType = C.uacpi_work_type;
pub const PciAddress = C.uacpi_pci_address;
pub const WorkHandler = C.uacpi_work_handler;

6
src/arch/aarch64/aarch64.zig Executable file
View File

@ -0,0 +1,6 @@
pub const debug = @import("debug.zig");
pub const machine = @import("machine/machine.zig");
pub inline fn halt() void {
asm volatile ("wfi");
}

View File

@ -0,0 +1,16 @@
const std = @import("std");
const root = @import("root");
pub const Error = error {};
pub const Writer = std.io.GenericWriter(void, Error, write);
pub fn writer() Writer {
return .{ .context = {} };
}
fn write(_: void, data: []const u8) Error!usize {
root.arch.machine.serial.COM1.write(data);
root.machine.Console.Instance.write(data);
return data.len;
}

View File

@ -0,0 +1,17 @@
const root = @import("root");
const limine = @import("limine");
pub const serial = @import("serial.zig");
export var device_tree_blob_request: limine.DeviceTreeBlob.Request = .{};
pub fn init() void {
serial.init();
if (device_tree_blob_request.response) |res| {
const pointer = res.pointer;
root.debug.print("{*}", .{pointer});
}
//root.main();
}

View File

@ -0,0 +1,12 @@
const root = @import("root");
const libk = @import("libk");
const PhysicalAddress = root.machine.memory.PhysicalAddress;
const SerialPort = libk.machine.serial.uart_pl011.SerialPort;
pub var COM1: SerialPort = undefined;
pub fn init() void {
COM1.open(PhysicalAddress.init(0x0900_0000).toAddr());
COM1.init(24000000, 115200) catch {};
}

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

@ -0,0 +1,7 @@
pub usingnamespace switch (@import("builtin").cpu.arch) {
.aarch64 => @import("aarch64/aarch64.zig"),
.loongarch64 => @import("loong64/loong64.zig"),
.riscv64 => @import("riscv64/riscv64.zig"),
.x86_64 => @import("x86_64/x86_64.zig"),
else => |arch| @compileError("unsupported architecture: " ++ arch),
};

View File

@ -0,0 +1,16 @@
const std = @import("std");
const root = @import("root");
pub const Error = error {};
pub const Writer = std.io.GenericWriter(void, Error, write);
pub fn writer() Writer {
return .{ .context = {} };
}
fn write(_: void, data: []const u8) Error!usize {
root.arch.machine.serial.COM1.write(data);
root.machine.Console.Instance.write(data);
return data.len;
}

6
src/arch/loong64/loong64.zig Executable file
View File

@ -0,0 +1,6 @@
pub const debug = @import("debug.zig");
pub const machine = @import("machine/machine.zig");
pub inline fn halt() void {
asm volatile ("idle 0");
}

View File

@ -0,0 +1,9 @@
const root = @import("root");
const limine = @import("limine");
pub const serial = @import("serial.zig");
pub fn init() void {
serial.init();
//root.main();
}

View File

@ -0,0 +1,13 @@
const root = @import("root");
const libk = @import("libk");
const Mmio = libk.io.Mmio;
const PhysicalAddress = root.machine.memory.PhysicalAddress;
const SerialPort = libk.machine.serial.uart_16550.SerialPort;
pub var COM1: SerialPort(Mmio(u8)) = undefined;
pub fn init() void {
COM1.open(PhysicalAddress.init(0x1fe0_01e0).toAddr());
COM1.init() catch {};
}

View File

@ -0,0 +1,16 @@
const std = @import("std");
const root = @import("root");
pub const Error = error {};
pub const Writer = std.io.GenericWriter(void, Error, write);
pub fn writer() Writer {
return .{ .context = {} };
}
fn write(_: void, data: []const u8) Error!usize {
root.arch.machine.serial.COM1.write(data);
root.machine.Console.Instance.write(data);
return data.len;
}

View File

@ -0,0 +1,11 @@
const root = @import("root");
const limine = @import("limine");
pub const serial = @import("serial.zig");
export var device_tree_blob_request: limine.DeviceTreeBlob.Request = .{};
pub fn init() void {
serial.init();
//root.main();
}

View File

@ -0,0 +1,13 @@
const root = @import("root");
const libk = @import("libk");
const Mmio = libk.io.Mmio;
const PhysicalAddress = root.machine.memory.PhysicalAddress;
const SerialPort = libk.machine.serial.uart_16550.SerialPort;
pub var COM1: SerialPort(Mmio(u8)) = undefined;
pub fn init() void {
COM1.open(PhysicalAddress.init(0x1000_0000).toAddr());
COM1.init() catch {};
}

6
src/arch/riscv64/riscv64.zig Executable file
View File

@ -0,0 +1,6 @@
pub const debug = @import("debug.zig");
pub const machine = @import("machine/machine.zig");
pub inline fn halt() void {
asm volatile ("wfi");
}

162
src/arch/x86_64/cpuid.zig Normal file
View File

@ -0,0 +1,162 @@
pub const Vendor = struct {
id: [12]u8,
pub inline fn get() @This() {
var ebx: u32 = 0;
var ecx: u32 = 0;
var edx: u32 = 0;
asm volatile (
\\cpuid
: [ebx] "={ebx}" (ebx),
[ecx] "={ecx}" (ecx),
[edx] "={edx}" (edx),
: [code] "{eax}" ( 0),
);
const triplet: [3]u32 = .{ebx, edx, ecx};
const string: [12]u8 = @bitCast(triplet);
return .{ .id = string };
}
};
pub const Features = packed struct(u64) {
sse3 : bool = false,
pclmul : bool = false,
dtes64 : bool = false,
monitor : bool = false,
ds_cpl : bool = false,
vmx : bool = false,
smx : bool = false,
est : bool = false,
tm2 : bool = false,
ssse3 : bool = false,
cid : bool = false,
sdbg : bool = false,
fma : bool = false,
cx16 : bool = false,
xtpr : bool = false,
pdcm : bool = false,
reserved_0 : u1 = 0,
pcid : bool = false,
dca : bool = false,
sse4_1 : bool = false,
sse4_2 : bool = false,
x2apic : bool = false,
movbe : bool = false,
popcnt : bool = false,
tsc_1 : bool = false,
aes : bool = false,
xsave : bool = false,
osxsave : bool = false,
avx : bool = false,
f16c : bool = false,
rdrand : bool = false,
hypervisor : bool = false,
fpu : bool = false,
vme : bool = false,
de : bool = false,
pse : bool = false,
tsc_2 : bool = false,
msr : bool = false,
pae : bool = false,
mce : bool = false,
cx8 : bool = false,
apic : bool = false,
reserved_1 : u1 = 0,
sep : bool = false,
mtrr : bool = false,
pge : bool = false,
mca : bool = false,
cmov : bool = false,
pat : bool = false,
pse36 : bool = false,
psn : bool = false,
clflush : bool = false,
reserved_2 : u1 = 0,
ds : bool = false,
acpi : bool = false,
mmx : bool = false,
fxsr : bool = false,
sse : bool = false,
sse2 : bool = false,
ss : bool = false,
htt : bool = false,
tm : bool = false,
ia64 : bool = false,
pbe : bool = false,
pub inline fn get() @This() {
var ecx: u32 = 0;
var edx: u32 = 0;
asm volatile (
\\cpuid
: [ecx] "={ecx}" (ecx),
[edx] "={edx}" (edx),
: [code] "{eax}" ( 1),
);
const array: [2]u32 = .{ecx, edx};
const result: Features = @bitCast(array);
return result;
}
};
pub const BusFrequency = struct {
denominator: u32,
numerator: u32,
/// Core crystal clock frequency in Hz
clock: u32,
pub inline fn get() @This() {
var eax: u32 = 0;
var ebx: u32 = 0;
var ecx: u32 = 0;
asm volatile (
\\cpuid
: [eax] "={eax}" (eax),
[ebx] "={ebx}" (ebx),
[ecx] "={ecx}" (ecx),
: [code] "{eax}" ( 21),
);
return .{
.denominator = eax,
.numerator = ebx,
.clock = ecx,
};
}
};
pub const CoreFrequency = struct {
/// Core base frequency in MHz
base: u16,
/// Core maximum frequency in MHz
maximum: u16,
/// bus (reference) frequency in MHz
bus: u16,
pub inline fn get() @This() {
var eax: u32 = 0;
var ebx: u32 = 0;
var ecx: u32 = 0;
asm volatile (
\\cpuid
: [eax] "={eax}" (eax),
[ebx] "={ebx}" (ebx),
[ecx] "={ecx}" (ecx),
: [code] "{eax}" ( 22),
);
return .{
.base = @truncate(eax),
.maximum = @truncate(ebx),
.bus = @truncate(ecx),
};
}
};

27
src/arch/x86_64/debug.zig Normal file
View File

@ -0,0 +1,27 @@
const std = @import("std");
const root = @import("root");
const libk = @import("libk");
pub const QemuExitCode = enum(u16) {
success = 0x10,
failed = 0x11,
};
pub fn exit(code: QemuExitCode) noreturn {
root.arch.out(0x16, @intFromEnum(code));
while (true) root.arch.halt();
}
pub const Error = error{};
pub const Writer = std.io.GenericWriter(void, Error, write);
pub fn writer() Writer {
return .{ .context = {} };
}
fn write(_: void, data: []const u8) Error!usize {
root.arch.machine.serial.COM1.write(data);
root.machine.Console.Instance.write(data);
return data.len;
}

View File

@ -0,0 +1,267 @@
const root = @import("root");
const Interrupt = root.arch.interrupt.Interrupt;
const StackFrame = root.arch.interrupt.InterruptStackFrame;
const print = root.debug.print;
const abort = root.debug.abort;
pub const DivisionError = Interrupt(struct {
pub const Vector = 0x00;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: division error\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const Debug = Interrupt(struct {
pub const Vector = 0x01;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: debug\n", .{});
print("Stack frame: {}\n", .{stack});
}
});
pub const NonMaskable = Interrupt(struct {
pub const Vector = 0x02;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: non-maskable\n", .{});
print("Stack frame: {}", .{stack});
abort();
}
});
pub const Breakpoint = Interrupt(struct {
pub const Vector = 0x03;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: breakpoint\n", .{});
print("Stack frame: {}\n", .{stack});
}
});
pub const Overflow = Interrupt(struct {
pub const Vector = 0x04;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: overflow\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const BoundRangeExceeded = Interrupt(struct {
pub const Vector = 0x05;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: bound range exceeded\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const InvalidOpcode = Interrupt(struct {
pub const Vector = 0x06;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: invalid opcode\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const DeviceNotAvailable = Interrupt(struct {
pub const Vector = 0x07;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: device not availble\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const DoubleFault = Interrupt(struct {
pub const Vector = 0x08;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: double fault\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const InvalidTss = Interrupt(struct {
pub const Vector = 0x0A;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: invalid TSS\n", .{});
print("Error code: {}\n", .{stack.error_code});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const SegmentNotPresent = Interrupt(struct {
pub const Vector = 0x0B;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: segment not present\n", .{});
print("Error code: {}\n", .{stack.error_code});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const StackSegmentFault = Interrupt(struct {
pub const Vector = 0x0C;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: stack segment fault\n", .{});
print("Error code: {}\n", .{stack.error_code});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const GeneralProtectionFault = Interrupt(struct {
pub const Vector = 0x0D;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: general protection fault\n", .{});
print("Error code: {}\n", .{stack.error_code});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const PageFault = Interrupt(struct {
pub const Vector = 0x0E;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: page fault\n", .{});
print("Error code: 0x{x}\n", .{stack.error_code});
print("Address: 0x{x}\n", .{root.arch.cr2()});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const x87FloatingPoint = Interrupt(struct {
pub const Vector = 0x10;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: x86 floating point\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const AlignmentCheck = Interrupt(struct {
pub const Vector = 0x11;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: alignment check\n", .{});
print("Error code: {}\n", .{stack.error_code});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const MachineCheck = Interrupt(struct {
pub const Vector = 0x12;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: machine check\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const SimdFloatingPoint = Interrupt(struct {
pub const Vector = 0x13;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: SIMD floating point\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const Virtualization = Interrupt(struct {
pub const Vector = 0x14;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: virtualization\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const ControlProtection = Interrupt(struct {
pub const Vector = 0x15;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: control protection\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const HypervisorInjection = Interrupt(struct {
pub const Vector = 0x1C;
pub const HasErrorCode = false;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: hypervisor injection\n", .{});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const VmmCommunication = Interrupt(struct {
pub const Vector = 0x1D;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: VMM communication\n", .{});
print("Error code: {}\n", .{stack.error_code});
print("Stack frame: {}\n", .{stack});
abort();
}
});
pub const Security = Interrupt(struct {
pub const Vector = 0x1E;
pub const HasErrorCode = true;
pub fn handler(stack: *StackFrame) callconv(.C) void {
print("Exception: security\n", .{});
print("Error code: {}\n", .{stack.error_code});
print("Stack frame: {}\n", .{stack});
abort();
}
});

View File

@ -0,0 +1,7 @@
pub inline fn enable() void {
asm volatile ("sti");
}
pub inline fn disable() void {
asm volatile ("cli");
}

View File

@ -0,0 +1,229 @@
const std = @import("std");
const arch = @import("../x86_64.zig");
const libk = @import("libk");
pub const exception = @import("exception.zig");
pub const hardware = @import("hardware.zig");
const BitFields = libk.utils.BitFields;
const PrivilegeLevel = arch.PrivilegeLevel;
const CodeSegment = arch.segmentation.CodeSegment;
const SegmentSelector = arch.segmentation.SegmentSelector;
pub const InterruptStackFrame = extern struct {
r15: u64,
r14: u64,
r13: u64,
r12: u64,
r11: u64,
r10: u64,
r9: u64,
r8: u64,
rdi: u64,
rsi: u64,
rbp: u64,
rdx: u64,
rcx: u64,
rbx: u64,
rax: u64,
vector_number: u64,
error_code: u64,
instruction_pointer: u64,
code_segment: u64,
cpu_flags: u64,
stack_pointer: u64,
stack_segment: u64,
};
pub fn Interrupt(comptime T: type) type {
return struct {
pub fn entry() callconv(.naked) noreturn {
asm volatile (
\\.align 16
);
// push a dummy error code if neccessary
if (comptime !T.HasErrorCode) {
asm volatile ("pushq $0");
}
// store the vector number for interrupt dispatcher
asm volatile (
"pushq " ++ std.fmt.comptimePrint("{}", .{T.Vector})
);
// jump to the stub
asm volatile (
\\jmp interruptCommonStub
);
}
pub fn register(selector: SegmentSelector) void {
InterruptDescriptorTable.entries[T.Vector] = .init(selector, entry);
if (@hasDecl(T, "handler")) attach();
}
pub fn attach() void {
Dispatcher.handlers[T.Vector] = T.handler;
}
pub fn detach() void {
Dispatcher.handlers[T.Vector] = null;
}
};
}
pub const Dispatcher = struct {
var handlers: [Limit]?*const Handler = undefined;
export fn dispatchInterruptHandler(isf: *InterruptStackFrame) callconv(.C) void {
const target = handlers[isf.vector_number];
const handler = target orelse default;
hardware.disable();
handler(isf);
hardware.enable();
}
export fn interruptCommonStub() callconv(.naked) noreturn {
asm volatile (
\\push %rax
\\push %rbx
\\push %rcx
\\push %rdx
\\push %rbp
\\push %rsi
\\push %rdi
\\push %r8
\\push %r9
\\push %r10
\\push %r11
\\push %r12
\\push %r13
\\push %r14
\\push %r15
\\mov %rsp, %rdi
\\call dispatchInterruptHandler
\\pop %r15
\\pop %r14
\\pop %r13
\\pop %r12
\\pop %r11
\\pop %r10
\\pop %r9
\\pop %r8
\\pop %rdi
\\pop %rsi
\\pop %rbp
\\pop %rdx
\\pop %rcx
\\pop %rbx
\\pop %rax
\\add $16, %rsp
\\iretq
);
}
fn default(isf: *InterruptStackFrame) callconv(.C) void {
std.debug.panic("Unhandled Interrupt\n{}", .{isf});
}
};
pub const InterruptDescriptorTable = extern struct {
var entries: [Limit]Entry = undefined;
pub const Entry = packed struct {
offset_0: u16,
selector: SegmentSelector,
option: Option,
offset_1: u16,
offset_2: u32,
reserved: u32,
pub const Option = BitFields(packed struct(u16) {
stack_index : u3 = 0,
reserved_0 : u5 = 0,
trap_gate : bool = false,
reserved_1 : u3 = 0b111,
reserved_2 : u1 = 0,
privilege_level : PrivilegeLevel,
present : bool = false,
});
pub fn init(selector: SegmentSelector, stub: *const Stub) @This() {
const offset = @intFromPtr(stub);
const option = Option.init(.{
.privilege_level = .ring0,
.present = true
});
return .{
.offset_0 = @truncate(offset),
.selector = selector,
.option = option,
.offset_1 = @truncate(offset >> 16),
.offset_2 = @truncate(offset >> 32),
.reserved = 0,
};
}
};
pub fn read() ?*@TypeOf(entries) {
const pointer = arch.sidt();
const table: ?*@TypeOf(entries) = @ptrFromInt(pointer.base);
return table;
}
pub fn load() void {
arch.lidt(.{
.limit = @sizeOf(@TypeOf(entries)) - 1,
.base = @intFromPtr(&entries)
});
}
};
pub const Stub = fn() callconv(.naked) noreturn;
pub const Handler = fn(*InterruptStackFrame) callconv(.C) void;
pub const Limit: usize = 256;
pub const RequestBase: usize = 32;
pub fn init() void {
const selector = CodeSegment.get();
defer InterruptDescriptorTable.load();
inline for (RequestBase..Limit) |i| {
const Request = Interrupt(struct {
pub const Vector = i;
pub const HasErrorCode = false;
});
Request.register(selector);
}
exception.DivisionError.register(selector);
exception.Debug.register(selector);
exception.NonMaskable.register(selector);
exception.Breakpoint.register(selector);
exception.Overflow.register(selector);
exception.BoundRangeExceeded.register(selector);
exception.InvalidOpcode.register(selector);
exception.DeviceNotAvailable.register(selector);
exception.DoubleFault.register(selector);
exception.InvalidTss.register(selector);
exception.SegmentNotPresent.register(selector);
exception.StackSegmentFault.register(selector);
exception.GeneralProtectionFault.register(selector);
exception.PageFault.register(selector);
exception.x87FloatingPoint.register(selector);
exception.AlignmentCheck.register(selector);
exception.MachineCheck.register(selector);
exception.SimdFloatingPoint.register(selector);
exception.Virtualization.register(selector);
exception.ControlProtection.register(selector);
exception.HypervisorInjection.register(selector);
exception.VmmCommunication.register(selector);
exception.Security.register(selector);
}

View File

@ -0,0 +1,4 @@
pub const ioApic = @import("ioApic.zig");
pub const x2Apic = @import("x2Apic.zig");
pub const xApic = @import("xApic.zig");

View File

View File

View File

@ -0,0 +1,165 @@
const Self = @This();
const libk = @import("libk");
const root = @import("root");
const Mmio = libk.io.Mmio;
const IoMap = libk.io.IoMap;
const ModelSpecificRegister = root.arch.ModelSpecificRegister;
const PhysicalAddress = root.machine.memory.PhysicalAddress;
pub const Register = packed struct(u64) {
reserved_0 : u32,
base : u19,
enable : bool,
reserved_1 : u2,
bsp : bool,
reserved_2 : u8,
pub fn read() Register {
const register = ModelSpecificRegister.apic_base.read();
const result = Register.fromBits(register);
return result;
}
pub fn write(self: *Register) void {
ModelSpecificRegister.apic_base.write(self.bits());
}
};
io: IoMap(Mmio(u32)).Schematics(struct {
/// Local APIC ID Register
id : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x020 },
/// Local APIC Version Register
version : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x030 },
/// Task Priority Register
tpr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x080 },
/// EOI register
eoi : IoMap(u32).Pin = .{ .perm = .writeonly, .base = 0x0B0 },
/// Logical Destination Register
ldr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x0D0 },
/// Spurious Interrupt Vector Register
svr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x0F0 },
/// In-Service Register
isr0 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x100 },
/// ISR bits 63:32
isr1 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x110 },
/// ISR bits 95:64
isr2 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x120 },
/// ISR bits 127:96
isr3 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x130 },
/// ISR bits 159:128
isr4 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x140 },
/// ISR bits 191:160
isr5 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x150 },
/// ISR bits 223:192
isr6 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x160 },
/// ISR bits 255:224
isr7 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x170 },
/// Trigger Mode Register
tmr0 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x180 },
/// TMR bits 63:32
tmr1 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x190 },
/// TMR bits 95:64
tmr2 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x1A0 },
/// TMR bits 127:96
tmr3 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x1B0 },
/// TMR bits 159:128
tmr4 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x1C0 },
/// TMR bits 191:160
tmr5 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x1D0 },
/// TMR bits 223:192
tmr6 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x1E0 },
/// TMR bits 255:224
tmr7 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x1F0 },
/// Interrupt Request Register
irr0 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x200 },
/// IRR bits 63:32
irr1 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x210 },
/// IRR bits 95:64
irr2 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x220 },
/// IRR bits 127:96
irr3 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x230 },
/// IRR bits 159:128
irr4 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x240 },
/// IRR bits 191:160
irr5 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x250 },
/// IRR bits 223:192
irr6 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x260 },
/// IRR bits 255:224
irr7 : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x270 },
/// Error Status Register
esr : IoMap(u32).Pin = .{ .perm = .all, .base = 0x280 },
/// LVT CMCI Register
lvt_cmci : IoMap(u32).Pin = .{ .perm = .all, .base = 0x2F0 },
/// Interrupt Command Register
icr0 : IoMap(u32).Pin = .{ .perm = .all, .base = 0x300 },
/// Interrupt Command Register
icr1 : IoMap(u32).Pin = .{ .perm = .all, .base = 0x310 },
/// LVT Timer Register
lvt_timer : IoMap(u32).Pin = .{ .perm = .all, .base = 0x320 },
/// LVT Thermal Sensor Register
lvt_thermal : IoMap(u32).Pin = .{ .perm = .all, .base = 0x330 },
/// LVT Performance Monitoring Register
lvt_pmi : IoMap(u32).Pin = .{ .perm = .all, .base = 0x340 },
/// LVT LINT0 Register
lvt_lint0 : IoMap(u32).Pin = .{ .perm = .all, .base = 0x350 },
/// LVT LINT1 Register
lvt_lint1 : IoMap(u32).Pin = .{ .perm = .all, .base = 0x360 },
/// LVT Error Register
lvt_error : IoMap(u32).Pin = .{ .perm = .all, .base = 0x370 },
/// Initial Count Register
timer_init_count : IoMap(u32).Pin = .{ .perm = .all, .base = 0x380 },
/// Current Count Register
timer_current_count : IoMap(u32).Pin = .{ .perm = .readonly, .base = 0x390 },
/// Divide Configuration Register
timer_div_conf : IoMap(u32).Pin = .{ .perm = .all, .base = 0x3E0 },
}),
pub fn attach(self: *@This()) void {
var register: Register = .read();
const address = PhysicalAddress.init(register.base).toVirtual();
register.enable = true;
register.write();
self.io.open(address.base);
self.io.write("svr", 1 << 8 | 15) catch unreachable;
}
pub fn detach(_: *@This()) void {
var register: Register = .read();
register.enable = false;
register.write();
}
pub fn isBootstrapCore(self: *Self) bool {
return self.register.bsp;
}
pub fn getId(self: *Self) u32 {
return self.io.read("id") catch unreachable;
}
pub fn getLogicalId(self: *Self) u32 {
return self.io.read("ldr") catch unreachable;
}
pub fn getVersion(self: *Self) u32 {
return self.io.read("version") catch unreachable;
}
pub fn notify(self: *Self) void {
self.io.write("eoi", 0);
}
pub fn enableTsc(self: *Self, vector: u8) void {
_ = self;
_ = vector;
// TODO
}
pub fn setTsc(_: *Self, value: u64) void {
ModelSpecificRegister.tsc_dead_line.write(value);
}

View File

@ -0,0 +1,24 @@
const arch = @import("../x86_64.zig");
const libk = @import("libk");
const Pio = arch.Pio;
const WriteOnly = libk.io.WriteOnly;
var command: WriteOnly(Pio(u8)) = .open(0x70);
var data: Pio(u8) = .open(0x71);
pub const Register = enum(u8) {
status_a = 0x0A,
status_b = 0x0B,
_,
};
pub fn read(register: Register) u8 {
command.write(0x80 | @intFromEnum(register));
return data.read();
}
pub fn write(register: Register, value: u8) void {
command.write(0x80 | @intFromEnum(register));
data.write(value);
}

View File

@ -0,0 +1,36 @@
const arch = @import("../x86_64.zig");
const root = @import("root");
const limine = @import("limine");
pub const pic = @import("pic.zig");
pub const ps2 = @import("ps2.zig");
pub const cmos = @import("cmos.zig");
pub const time = @import("time.zig");
pub const serial = @import("serial.zig");
pub const Apic = @import("apic/Apic.zig");
export var bootloader_info_request: limine.BootloaderInfo.Request = .{};
export var paging_request: limine.Paging.Request = .{};
pub fn init() void {
serial.init();
arch.segmentation.init();
arch.interrupt.init();
if (paging_request.response) |res| {
arch.paging.mode = res.mode;
}
if (bootloader_info_request.response) |res| {
root.debug.print("name: {s}\n", .{res.name});
root.debug.print("version: {s}\n", .{res.version});
}
arch.interrupt.hardware.enable();
pic.ChainedPics.enable();
time.init();
root.main();
}

View File

@ -0,0 +1,113 @@
const arch = @import("../x86_64.zig");
const libk = @import("libk");
const Pio = arch.Pio;
const Interrupt = arch.interrupt.Interrupt;
const StackFrame = arch.interrupt.InterruptStackFrame;
const WriteOnly = libk.io.WriteOnly;
pub const Command = enum(u8) {
init = 0x11,
end_of_interrupt = 0x20,
};
pub const Mode = enum(u8) {
@"8086" = 0x01,
};
pub fn Pic(comptime Offset: u8) type {
return struct {
command: WriteOnly(Pio(u8)),
data: Pio(u8),
pub inline fn init(self: *@This()) void {
self.data.write(Offset);
}
pub inline fn notify(self: *@This()) void {
self.command.write(@intFromEnum(Command.end_of_interrupt));
}
pub inline fn contains(_: *@This(), id: u8) bool {
return Offset <= id and id < Offset + 8;
}
};
}
pub const ChainedPics = struct {
var pic1: Pic(0x20) = .{
.command = .open(0x20),
.data = .open(0x21)
};
var pic2: Pic(0x28) = .{
.command = .open(0xA0),
.data = .open(0xA1)
};
pub fn enable() void {
Timer.attach();
Keyboard.attach();
// Initiate initialization sequence
pic1.command.write(@intFromEnum(Command.init));
pic2.command.write(@intFromEnum(Command.init));
// Setup base offsets
pic1.init();
pic2.init();
// Configure PIC chaining
pic1.data.write(4);
pic2.data.write(2);
// Set mode
pic1.data.write(@intFromEnum(Mode.@"8086"));
pic2.data.write(@intFromEnum(Mode.@"8086"));
// Unmask interrupts
pic1.data.write(0);
pic2.data.write(0);
// Ack remaining interrupts
pic1.notify();
pic2.notify();
}
pub fn disable() void {
pic1.data.write(0xff);
pic2.data.write(0xff);
}
pub inline fn contains(id: u8) bool {
return pic1.contains(id) or pic2.contains(id);
}
pub inline fn notify(id: u8) void {
if (contains(id)) {
if (pic2.contains(id)) {
pic2.notify();
}
pic1.notify();
}
}
pub const Timer = Interrupt(struct {
pub const Vector = 0x20;
pub const HasErrorCode = false;
pub fn handler(_: *StackFrame) callconv(.C) void {
arch.machine.time.Pit.tick();
notify(Vector);
}
});
pub const Keyboard = Interrupt(struct {
pub const Vector = 0x21;
pub const HasErrorCode = false;
pub fn handler(_: *StackFrame) callconv(.C) void {
arch.machine.ps2.Keyboard.read();
notify(Vector);
}
});
};

View File

@ -0,0 +1,9 @@
const arch = @import("../x86_64.zig");
pub const Keyboard = struct {
var data: arch.Pio(u8) = .open(0x60);
pub fn read() void {
}
};

View File

@ -0,0 +1,20 @@
const arch = @import("../x86_64.zig");
const libk = @import("libk");
const Pio = arch.Pio;
const SerialPort = libk.machine.serial.uart_16550.SerialPort;
pub var COM1: SerialPort(Pio(u8)) = undefined;
pub var COM2: SerialPort(Pio(u8)) = undefined;
pub var COM3: SerialPort(Pio(u8)) = undefined;
pub fn init() void {
COM1.open(0x3f8);
COM1.init() catch {};
COM2.open(0x2f8);
COM2.init() catch {};
COM3.open(0x3e8);
COM3.init() catch {};
}

View File

@ -0,0 +1,171 @@
const std = @import("std");
const arch = @import("../x86_64.zig");
const libk = @import("libk");
const Pio = arch.Pio;
const WriteOnly = libk.io.WriteOnly;
const BitFields = libk.utils.BitFields;
pub const Pit = struct {
var command: WriteOnly(Pio(u8)) = .open(0x43);
var data: Pio(u8) = .open(0x40);
const Frequency: f32 = 1.193182;
const Command = BitFields(packed struct(u8) {
bcd: bool,
mode: Mode,
access: AccessMode,
channel: Channel,
});
const Mode = enum(u3) {
default = 2,
};
const Channel = enum(u2) {
chan0 = 0,
chan1 = 1,
chan2 = 2,
chan3 = 3
};
const AccessMode = enum(u2) {
default = 3,
};
pub fn init() void {
const byte = Command.init(.{
.bcd = false,
.mode = .default,
.access = .default,
.channel = .chan0,
});
//
const count: u16 = @round(Frequency * 1000);
// send command
command.write(byte.bits());
data.write(@truncate(count));
data.write(@truncate(count >> 8));
}
pub fn tick() void {
timestamp += 1;
}
};
pub const Rtc = struct {
var is_24hour_format: bool = false;
var is_binary_format: bool = false;
pub const Register = enum(u8) {
second = 0x00,
minute = 0x02,
hour = 0x04,
day = 0x07,
month = 0x08,
year = 0x09,
century = 0x20,
};
pub fn init() void {
const status = arch.machine.cmos.read(.status_b);
is_24hour_format = status & 0x02 > 0;
is_binary_format = status & 0x04 > 0;
}
pub fn read(register: Register) u8 {
const value = arch.machine.cmos.read(@enumFromInt(@intFromEnum(register)));
return if (is_binary_format) value else convertBcdValue(value);
}
pub fn instant() u64 {
const century = read(.century);
const day = read(.day);
const minute = read(.minute);
const second = read(.second);
var hour = read(.hour);
var is_pm = false;
if (!is_24hour_format) {
is_pm = hour & 0x80 > 0;
hour &= 0x7F;
}
if (!is_binary_format) {
hour = convertBcdValue(hour);
}
if (!is_24hour_format) {
if (hour == 12) hour = 0;
if (is_pm) hour += 12;
}
var year: usize = read(.year);
var month: usize = read(.month);
if (century > 0) { year += century * 100; }
else { year += 2000; }
if (month > 2) { month -= 2; }
else { month += 10; year -= 1; }
const days_since_epoch = (year / 4 - year / 100 + year / 400 + 367 * month / 12 + day) + year * 365 - 719_499;
const hours_since_epoch = days_since_epoch * 24 + hour;
const minutes_since_epoch = hours_since_epoch * 60 + minute;
const seconds_since_epoch = minutes_since_epoch * 60 + second;
return seconds_since_epoch;
}
pub fn getUnixTimestamp() usize {
while (true) {
while (isUpdateInProgress()) std.atomic.spinLoopHint();
const t1 = instant();
if (isUpdateInProgress()) continue;
const t2 = instant();
if (t1 == t2) return t1;
}
}
pub fn setUnixTimestamp(value: usize) void {
_ = value; // TODO
}
fn isUpdateInProgress() bool {
return arch.machine.cmos.read(.status_a) & 0x80 > 0;
}
fn convertBcdValue(value: u8) u8 {
return ((value & 0xF0) >> 1) + ((value & 0xF0) >> 3) + (value & 0x0F);
}
};
/// Unix Timestamp in milliseconds
var timestamp: usize = 0;
pub fn init() void {
Pit.init();
Rtc.init();
timestamp += Rtc.getUnixTimestamp() * 1000;
}
pub fn wait(millis: usize) void {
const future = timestamp + millis;
while (timestamp < future) std.atomic.spinLoopHint();
}
pub fn getUnixTimestampMs() usize {
return timestamp;
}
pub fn setUnixTimestampMs(value: usize) void {
timestamp = value;
}

156
src/arch/x86_64/paging.zig Normal file
View File

@ -0,0 +1,156 @@
const std = @import("std");
const libk = @import("libk");
const root = @import("root");
const limine = @import("limine");
const alignUp = libk.utils.alignUp;
const alignDown = libk.utils.alignDown;
const BitFields = libk.utils.BitFields;
const PhysicalAddress = root.machine.memory.PhysicalAddress;
const VirtualAddress = root.machine.memory.VirtualAddress;
pub var mode: ?limine.Paging.Mode = null;
pub var frame_size: Size = .@"4KiB";
pub const Size = enum(usize) {
@"1GiB" = 0x40000000,
@"2MiB" = 0x200000,
@"4KiB" = 0x1000,
};
pub const PageTable = extern struct {
entries: [512]EntryValue,
pub const EntryValue = BitFields(packed struct(u64) {
present : bool = false,
writable : bool = false,
user_accessible : bool = false,
write_through : bool = false,
no_cache : bool = false,
accessed : bool = false,
dirty : bool = false,
huge_page : bool = false,
global : bool = false,
bits : u50 = 0,
protection_key : u4 = 0,
no_execute : bool = false,
});
pub fn Entry(comptime size: Size) type {
return extern struct {
value: EntryValue,
pub fn init(value: *EntryValue) *@This() {
return @ptrCast(value);
}
pub fn frame(self: *@This()) PhysicalAddress {
const base = switch (size) {
.@"1GiB" => self.value.bits() & 0x000f_ffff_ffff_e000,
.@"2MiB" => self.value.bits() & 0x000f_ffff_ffff_e000,
.@"4KiB" => self.value.bits() & 0x000f_ffff_ffff_f000,
};
return .init(alignDown(base, @intFromEnum(size)));
}
};
}
pub fn get(self: *@This(), index: usize) *EntryValue {
return &self.entries[index];
}
};
pub const Allocator = struct {
var fallback = root.machine.memory.Allocator.allocator();
var head4KiB: Node = undefined;
var head2MiB: Node = undefined;
var head1GiB: Node = undefined;
const Node = struct {
next: ?*Node,
};
pub fn allocator() std.mem.Allocator {
return .{
.ptr = undefined,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
},
};
}
fn alloc(_: *anyopaque, len: usize, alignment: std.mem.Alignment, ret_addr: usize) ?[*]u8 {
_ = len;
_ = alignment;
_ = ret_addr;
return null;
}
fn resize(_: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
_ = memory;
_ = alignment;
_ = new_len;
_ = ret_addr;
return false;
}
fn remap(_: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
_ = memory;
_ = alignment;
_ = new_len;
_ = ret_addr;
return null;
}
fn free(_: *anyopaque, memory: []u8, alignment: std.mem.Alignment, ret_addr: usize) void {
_ = memory;
_ = alignment;
_ = ret_addr;
}
};
pub fn translate(virt: VirtualAddress) error{HugePage, NotPresent}!PhysicalAddress {
const bits = [_]u6{0, 12, 21, 30, 39, 48};
const page_table_address = root.arch.cr3();
var level: usize = if (mode == .five_level) 5 else 4;
var address: PhysicalAddress = .init(page_table_address);
while (level > 0) : (level -= 1) {
const table = address.cast(*PageTable);
const index: u9 = @truncate(virt.base >> bits[level]);
const entry = table.get(index);
if (!entry.fields.present) {
return error.NotPresent;
}
if (entry.fields.huge_page) switch (level) {
3 => {
const phys = PageTable.Entry(Size.@"1GiB").init(entry).frame();
const offset: u30 = @truncate(virt.base);
return .{ .base = phys.base + offset };
},
2 => {
const phys = PageTable.Entry(Size.@"2MiB").init(entry).frame();
const offset: u21 = @truncate(virt.base);
return .{ .base = phys.base + offset };
},
else => return error.HugePage,
};
address = PageTable.Entry(Size.@"4KiB").init(entry).frame();
}
const phys = address;
const offset: u12 = @truncate(virt.base);
return .{ .base = phys.base + offset };
}

View File

@ -0,0 +1,164 @@
const std = @import("std");
const libk = @import("libk");
const root = @import("root");
const BitFields = libk.utils.BitFields;
const PrivilegeLevel = root.arch.PrivilegeLevel;
pub const SegmentSelector = BitFields(packed struct(u16) {
privilege_level : PrivilegeLevel,
table_indicator : u2,
index : u12
});
pub const SegmentDescriptor = BitFields(packed struct(u64) {
limit_0 : u16,
base_0 : u24,
access : u8,
limit_1 : u4,
flags : u4,
base_1 : u8,
});
pub const SystemSegmentDescriptor = extern struct {
lower: SegmentDescriptor,
upper: u64,
pub const Access = BitFields(packed struct(u8) {
type : SegmentType,
reserved : u1 = 0,
privilege_level : PrivilegeLevel,
present : bool = false,
});
pub const Flags = BitFields(packed struct(u4) {
granularity : bool = false,
default_size : bool = false,
long_mode : bool = false,
reserved : u1 = 0,
});
pub const SegmentType = enum(u4) {
local_descriptor_table = 0x2,
tss_available = 0x9,
tss_busy = 0xb,
};
pub fn init(limit: u32, base: u64, access: Access, flags: Flags) @This() {
const lower: SegmentDescriptor = .init(.{
.base_0 = @truncate(base),
.base_1 = @truncate(base >> 24),
.limit_0 = @truncate(limit),
.limit_1 = @truncate(limit >> 16),
.access = access.bits(),
.flags = flags.bits(),
});
return .{
.lower = lower,
.upper = base >> 32,
};
}
};
pub fn GlobalDescriptorTable(comptime M: usize) type {
return extern struct {
entries: ?*Entries = null,
len: usize = 1,
pub const Max = M;
pub const Entries = [Max]u64;
pub fn read(len: usize) @This() {
const pointer = root.arch.sgdt();
const entries: *Entries = @ptrFromInt(pointer.base);
return .{ .entries = entries, .len = len };
}
pub fn append(self: *@This(), value: SystemSegmentDescriptor) void {
self.push(value.lower.bits());
self.push(value.upper);
}
pub fn push(self: *@This(), entry: u64) void {
if (self.len >= Max) {
std.debug.panic("exceeded maximum GDT entry limit ({})", .{Max});
}
self.entries.?[self.len] = entry;
self.len += 1;
}
pub fn load(self: *@This()) void {
root.arch.lgdt(.{
.limit = @sizeOf(Entries) - 1,
.base = @intFromPtr(self.entries.?)
});
}
};
}
pub const TaskStateSegment = extern struct {
reserved_0: u32 = 0,
privilege_stack_table: [3]u64 = undefined,
reserved_1: u64 = 0,
interrupt_stack_table: [7]u64 = undefined,
reserved_2: u64 = 0,
reserved_3: u16 = 0,
iomap_base: u16 = @sizeOf(@This()),
pub fn getDescriptor(self: *@This()) SystemSegmentDescriptor {
const limit = @sizeOf(@This()) - 1;
const base = @intFromPtr(self);
const access: SystemSegmentDescriptor.Access = .init(.{
.type = .tss_available,
.privilege_level = .ring0,
.present = true
});
return .init(limit, base, access, .init(.{}));
}
};
pub const CodeSegment = struct {
pub inline fn set(selector: SegmentSelector) void {
asm volatile (
\\pushq %[a]
\\leaq 1f(%rip), %rax
\\pushq %rax
\\lretq
\\1:
:
: [a] "r" (selector.bits())
);
}
pub inline fn get() SegmentSelector {
return asm volatile (
\\mov %%cs, %[ret]
: [ret] "=&r" (-> SegmentSelector),
:
: "memory"
);
}
};
pub var gdt: GlobalDescriptorTable(9) = .{};
pub var tss: TaskStateSegment = .{};
pub fn init() void {
// at this point, the GDT has been loaded with 7 entries;
// see <https://github.com/limine-bootloader/limine/blob/v8.x/PROTOCOL.md#x86-64-1>
gdt = .read(7);
// setup double fault IST
tss.interrupt_stack_table[0] = block: {
const size = 4096 * 5; // 20 kiB
const stack = [_]u8{0} ** size;
break :block @intFromPtr(&stack);
};
gdt.append(tss.getDescriptor());
gdt.load();
}

181
src/arch/x86_64/x86_64.zig Executable file
View File

@ -0,0 +1,181 @@
const root = @import("root");
pub const cpuid = @import("cpuid.zig");
pub const debug = @import("debug.zig");
pub const paging = @import("paging.zig");
pub const machine = @import("machine/machine.zig");
pub const interrupt = @import("interrupt/interrupt.zig");
pub const segmentation = @import("segmentation.zig");
pub const DescriptorTablePointer = packed struct {
limit: u16,
base: u64,
};
pub const PrivilegeLevel = enum(u2) {
ring0 = 0,
ring1 = 1,
ring2 = 2,
ring3 = 3,
};
pub const ModelSpecificRegister = enum(u32) {
apic_base = 0x01B,
tsc_dead_line = 0x6E0,
pub inline fn read(self: @This()) u64 {
var lo: u32 = 0;
var hi: u32 = 0;
asm volatile (
\\rdmsr
: [lo] "={eax}" (lo),
[hi] "={edx}" (hi),
: [msr] "{ecx}" (@intFromEnum(self))
);
const array: [2]u32 = .{lo, hi};
const result: u64 = @bitCast(array);
return result;
}
pub inline fn write(self: @This(), value: u64) void {
const lo: u32 = @truncate(value);
const hi: u32 = @truncate(value >> 32);
asm volatile (
\\wrmsr
:
: [lo] "{eax}" (lo),
[hi] "{edx}" (hi),
[msr] "{ecx}" (@intFromEnum(self)),
);
}
};
pub fn Pio(comptime T: type) type {
return struct {
address: u16,
pub fn open(port: u16) @This() {
return .{ .address = port };
}
pub fn read(self: *@This()) T {
return in(T, self.address);
}
pub fn write(self: *@This(), data: T) void {
out(self.address, data);
}
};
}
pub inline fn halt() void {
asm volatile ("hlt");
}
pub inline fn int3() void {
asm volatile ("int3");
}
pub inline fn lidt(pointer: DescriptorTablePointer) void {
asm volatile (
\\lidt %[ptr]
:
: [ptr] "*p" (&pointer),
);
}
pub inline fn sidt() DescriptorTablePointer {
var pointer
: DescriptorTablePointer
= undefined;
asm volatile (
\\sidt %[ptr]
: [ptr] "=m" (pointer)
);
return pointer;
}
pub inline fn lgdt(pointer: DescriptorTablePointer) void {
asm volatile (
\\lgdt %[ptr]
:
: [ptr] "*p" (&pointer),
);
}
pub inline fn sgdt() DescriptorTablePointer {
var pointer
: DescriptorTablePointer
= undefined;
asm volatile (
\\sgdt %[ptr]
: [ptr] "=m" (pointer)
);
return pointer;
}
pub inline fn cr2() u64 {
return asm volatile (
\\mov %%cr2, %[ret]
: [ret] "=&r" (-> u64)
);
}
pub inline fn cr3() u64 {
return asm volatile (
\\mov %%cr3, %[ret]
: [ret] "=&r" (-> u64)
);
}
pub inline fn in(comptime T: type, port: u16) T {
return switch (T) {
u8 => asm volatile (
\\inb %[port], %[ret]
: [ret] "={al}" (-> T),
: [port] "{dx}" (port),
),
u16 => asm volatile (
\\inw %[port], %[ret]
: [ret] "={ax}" (-> T),
: [port] "{dx}" (port),
),
u32 => asm volatile (
\\inl %[port], %[ret]
: [ret] "={eax}" (-> T),
: [port] "{dx}" (port),
),
else => @compileError("Invalid data type: " ++ @typeName(T)),
};
}
pub inline fn out(port: u16, data: anytype) void {
switch (@TypeOf(data)) {
u8 => asm volatile (
\\outb %[data], %[port]
:
: [port] "{dx}" (port),
[data] "{al}" (data),
),
u16 => asm volatile (
\\outw %[data], %[port]
:
: [port] "{dx}" (port),
[data] "{ax}" (data),
),
u32 => asm volatile (
\\outl %[data], %[port]
:
: [port] "{dx}" (port),
[data] "{eax}" (data),
),
else => |T| @compileError("Invalid data type: " ++ @typeName(T)),
}
}

10
src/debug.zig Executable file
View File

@ -0,0 +1,10 @@
const std = @import("std");
const root = @import("root");
pub fn abort() noreturn {
while(true) root.arch.halt();
}
pub fn print(comptime fmt: []const u8, args: anytype) void {
std.fmt.format(root.arch.debug.writer(), fmt, args) catch unreachable;
}

267
src/machine/acpi/acpi.zig Normal file
View File

@ -0,0 +1,267 @@
const std = @import("std");
const root = @import("root");
const uacpi = @import("uacpi");
const builtin = @import("builtin");
pub var rsdp: ?usize = null;
pub var boot_time: ?isize = null;
export fn uacpi_kernel_get_rsdp(out_rsdp_address: *uacpi.PhysAddr) uacpi.Status {
if (rsdp) |address| {
out_rsdp_address.* = address;
return .ok;
}
return .internal_error;
}
export fn uacpi_kernel_pci_device_open(address: uacpi.PciAddress, out_handle: *anyopaque) uacpi.Status {
_ = address;
_ = out_handle;
return .unimplemented;
}
export fn uacpi_kernel_pci_device_close(out_handle: *anyopaque) void {
_ = out_handle;
}
export fn uacpi_kernel_pci_read8(device: *anyopaque, offset: usize, value: *u8) uacpi.Status {
_ = device;
_ = offset;
_ = value;
return .unimplemented;
}
export fn uacpi_kernel_pci_read16(device: *anyopaque, offset: usize, value: *u16) uacpi.Status {
_ = device;
_ = offset;
_ = value;
return .unimplemented;
}
export fn uacpi_kernel_pci_read32(device: *anyopaque, offset: usize, value: *u32) uacpi.Status {
_ = device;
_ = offset;
_ = value;
return .unimplemented;
}
export fn uacpi_kernel_pci_write8(device: *anyopaque, offset: usize, value: u8) uacpi.Status {
_ = device;
_ = offset;
_ = value;
return .unimplemented;
}
export fn uacpi_kernel_pci_write16(device: *anyopaque, offset: usize, value: u16) uacpi.Status {
_ = device;
_ = offset;
_ = value;
return .unimplemented;
}
export fn uacpi_kernel_pci_write32(device: *anyopaque, offset: usize, value: u32) uacpi.Status {
_ = device;
_ = offset;
_ = value;
return .unimplemented;
}
export fn uacpi_kernel_io_map(base: u64, _: usize, out_handle: **anyopaque) uacpi.Status {
if (builtin.cpu.arch == .x86_64) {
out_handle.* = @ptrFromInt(base);
return .ok;
}
return .unimplemented;
}
export fn uacpi_kernel_io_unmap(handle: *anyopaque) void {
_ = handle;
}
export fn uacpi_kernel_io_read8(handle: *anyopaque, offset: usize, value: *u8) uacpi.Status {
if (builtin.cpu.arch == .x86_64) {
const port: u16 = @truncate(@intFromPtr(handle) + offset);
const output = root.arch.in(u8, port);
value.* = output;
return .ok;
}
return .unimplemented;
}
export fn uacpi_kernel_io_read16(handle: *anyopaque, offset: usize, value: *u16) uacpi.Status {
if (builtin.cpu.arch == .x86_64) {
const port: u16 = @truncate(@intFromPtr(handle) + offset);
const output = root.arch.in(u16, port);
value.* = output;
return .ok;
}
return .unimplemented;
}
export fn uacpi_kernel_io_read32(handle: *anyopaque, offset: usize, value: *u32) uacpi.Status {
if (builtin.cpu.arch == .x86_64) {
const port: u16 = @truncate(@intFromPtr(handle) + offset);
const output = root.arch.in(u32, port);
value.* = output;
return .ok;
}
return .unimplemented;
}
export fn uacpi_kernel_io_write8(handle: *anyopaque, offset: usize, value: u8) uacpi.Status {
if (builtin.cpu.arch == .x86_64) {
root.arch.out(@truncate(@intFromPtr(handle) + offset), value);
return .ok;
}
return .unimplemented;
}
export fn uacpi_kernel_io_write16(handle: *anyopaque, offset: usize, value: u16) uacpi.Status {
if (builtin.cpu.arch == .x86_64) {
root.arch.out(@truncate(@intFromPtr(handle) + offset), value);
return .ok;
}
return .unimplemented;
}
export fn uacpi_kernel_io_write32(handle: *anyopaque, offset: usize, value: u32) uacpi.Status {
if (builtin.cpu.arch == .x86_64) {
root.arch.out(@truncate(@intFromPtr(handle) + offset), value);
return .ok;
}
return .unimplemented;
}
export fn uacpi_kernel_map(addr: uacpi.PhysAddr, len: usize) [*]u8 {
_ = addr;
_ = len;
return undefined;
}
export fn uacpi_kernel_unmap(addr: [*]u8, len: usize) void {
_ = addr;
_ = len;
}
export fn uacpi_kernel_alloc(size: usize) ?[*]u8 {
_ = size;
return null;
}
export fn uacpi_kernel_free(opt_mem: ?[*]u8) void {
_ = opt_mem;
}
export fn uacpi_kernel_log(uacpi_log_level: uacpi.LogLevel, msg: [*:0]const u8) void {
_ = uacpi_log_level;
_ = msg;
}
export fn uacpi_kernel_get_nanoseconds_since_boot() u64 {
const then: usize = if (boot_time != null and boot_time.? > 0) @intCast(boot_time.?) else 0;
const now = root.arch.machine.time.Rtc.getUnixTimestamp();
return (now - then) * 1000000000;
}
export fn uacpi_kernel_stall(usec: u8) void {
_ = usec;
}
export fn uacpi_kernel_sleep(msec: u64) void {
_ = msec;
}
export fn uacpi_kernel_create_mutex() *anyopaque {
return undefined;
}
export fn uacpi_kernel_free_mutex(mutex: *anyopaque) void {
_ = mutex;
}
export fn uacpi_kernel_create_event() *anyopaque {
return undefined;
}
export fn uacpi_kernel_free_event(handle: *anyopaque) void {
_ = handle;
}
export fn uacpi_kernel_get_thread_id() uacpi.ThreadId {
return null;
}
export fn uacpi_kernel_acquire_mutex(mutex: *anyopaque, timeout: u16) uacpi.Status {
_ = mutex;
_ = timeout;
return .unimplemented;
}
export fn uacpi_kernel_release_mutex(mutex: *anyopaque) void {
_ = mutex;
}
export fn uacpi_kernel_wait_for_event(handle: *anyopaque, timeout: u16) bool {
_ = handle;
_ = timeout;
return false;
}
export fn uacpi_kernel_signal_event(handle: *anyopaque) void {
_ = handle;
}
export fn uacpi_kernel_reset_event(handle: *anyopaque) void {
_ = handle;
}
export fn uacpi_kernel_handle_firmware_request(request: *const anyopaque) uacpi.Status {
_ = request;
return .unimplemented;
}
export fn uacpi_kernel_install_interrupt_handler(irq: u32, handler: uacpi.InterruptHandler, ctx: *anyopaque, out_irq_handle: **anyopaque) uacpi.Status {
_ = irq;
_ = handler;
_ = ctx;
_ = out_irq_handle;
return .unimplemented;
}
export fn uacpi_kernel_uninstall_interrupt_handler(handler: uacpi.InterruptHandler, irq_handle: *anyopaque) uacpi.Status {
_ = handler;
_ = irq_handle;
return .unimplemented;
}
export fn uacpi_kernel_create_spinlock() *anyopaque {
return undefined;
}
export fn uacpi_kernel_free_spinlock(spinlock: *anyopaque) void {
_ = spinlock;
}
export fn uacpi_kernel_lock_spinlock(spinlock: *anyopaque) uacpi.CpuFlags {
_ = spinlock;
return 0;
}
export fn uacpi_kernel_unlock_spinlock(spinlock: *anyopaque) void {
_ = spinlock;
}
export fn uacpi_kernel_schedule_work(work_type: uacpi.WorkType, handler: uacpi.WorkHandler, ctx: *anyopaque) uacpi.Status {
_ = work_type;
_ = handler;
_ = ctx;
return .unimplemented;
}
export fn uacpi_kernel_wait_for_work_completion() void {
std.debug.panic("uacpi: waiting for work completion\n", .{});
}

View File

@ -0,0 +1,93 @@
const root = @import("root");
const limine = @import("limine");
const Mmio = root.machine.memory.Mmio;
const Framebuffer = limine.Framebuffer;
const BitmapFont = struct {
height: usize,
width: usize,
size: usize,
data: []const u8,
};
font: BitmapFont,
framebuffer: *Framebuffer,
x: usize = 0,
y: usize = 0,
pub const Color = u32;
pub var Instance: @This() = undefined;
pub fn init(framebuffer: *Framebuffer) void {
const font = BitmapFont {
.height = 16,
.width = 8,
.size = 16,
.data = @embedFile("bitmap.bin"),
};
Instance = .{
.font = font,
.framebuffer = framebuffer,
};
}
pub fn write(self: *@This(), data: []const u8) void {
for (data) |byte| {
// move to the next row (in full characters)
if (byte == '\n' or self.x >= self.framebuffer.width) {
self.y += self.font.height;
self.x = 0;
}
// scroll the screen by one row (in full characters)
if (self.y >= self.framebuffer.height) {
const height = self.framebuffer.height;
const width = self.framebuffer.width;
const offset = self.font.height * width;
const size = height * width - offset;
const address = self.framebuffer.address;
const pointer: [*]Color = @ptrCast(@alignCast(address));
// transpose data
for (0..size) |i| {
pointer[i] = pointer[i + offset];
}
// clear the rest of the screen
for (size..size + offset) |i| {
pointer[i] = 0;
}
self.y = height - self.font.height;
}
if (byte != '\n') {
self.draw(byte, 0xffffffff);
self.x += self.font.width;
}
}
}
fn draw(self: *@This(), char: u8, color: Color) void {
const address = self.framebuffer.address;
const pointer: [*]Color = @ptrCast(@alignCast(address));
const font = self.font.data[char * self.font.size..];
const mask = [_]u8{128, 64, 32, 16, 8, 4, 2, 1};
var offset = self.y * self.framebuffer.width + self.x;
for (0..self.font.height) |row| {
for (0..self.font.width) |col| {
if (font[row] & mask[col] > 0) {
pointer[col + offset] = color;
}
}
// Move to the next row (in pixels)
offset += self.framebuffer.width;
}
}

Binary file not shown.

50
src/machine/machine.zig Normal file
View File

@ -0,0 +1,50 @@
const std = @import("std");
const root = @import("root");
const limine = @import("limine");
pub const pci = @import("pci.zig");
pub const acpi = @import("acpi/acpi.zig");
pub const memory = @import("memory/memory.zig");
pub const Console = @import("framebuffer/Console.zig");
export var base_revision: limine.BaseRevision = .{ .revision = 2 };
export var framebuffer_request: limine.Framebuffer.Request = .{};
export var memory_map_request: limine.MemoryMap.Request = .{};
export var boot_time_request: limine.BootTime.Request = .{};
export var hhdm_request: limine.Hhdm.Request = .{};
export var rsdp_request: limine.Rsdp.Request = .{};
pub fn init() callconv(.C) noreturn {
// Ensure the bootloader actually understands our base revision.
if (!base_revision.is_supported()) {
root.debug.abort();
}
if (framebuffer_request.response) |res| {
if (res.framebuffer_count > 0) {
Console.init(res.getFramebuffers()[0]);
}
}
if (rsdp_request.response) |res| {
acpi.rsdp = res.address;
}
if (boot_time_request.response) |res| {
acpi.boot_time = res.boot_time;
}
if (hhdm_request.response) |res| {
memory.offset = res.offset;
}
if (memory_map_request.response) |res| {
memory.map = res.getEntries();
}
memory.init();
//memory.manager = .init();
root.arch.machine.init();
std.debug.panic("System halted.\n", .{});
}

114
src/machine/memory/heap.zig Normal file
View File

@ -0,0 +1,114 @@
pub const Allocator = struct {
var head: Node = .{
.size = 0,
.next = null,
};
const Node = struct {
size: usize,
next: ?*Node,
fn start(self: *@This()) VirtAddress {
return .{ .address = @intFromPtr(self) };
}
fn end(self: *@This()) VirtAddress {
return .{ .address = self.start().address + self.size };
}
};
const Layout = struct {
alignment: usize,
size: usize,
fn init(alignment: std.mem.Alignment, len: usize) @This() {
const adjusted = alignment.max(.fromByteUnits(@alignOf(Node)));
const mask = adjusted.toByteUnits() - 1;
const size = (len + mask) & ~mask;
return .{
.alignment = adjusted.toByteUnits(),
.size = @max(size, @sizeOf(Node)),
};
}
};
pub fn append(address: VirtualAddress, size: usize) void {
const base: usize = address.toAddr();
const node: *Node = @ptrFromInt(base);
node.size = size;
node.next = head.next;
head.next = node;
}
pub fn allocator() std.mem.Allocator {
return .{
.ptr = undefined,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
},
};
}
fn alloc(_: *anyopaque, len: usize, alignment: std.mem.Alignment, _: usize) ?[*]u8 {
const layout: Layout = .init(alignment, len);
var current: ?*Node = &head;
while (current.?.next != null) : (current = current.?.next) {
const region = current.?.next.?;
// check if size too small
if (region.size < layout.size) continue;
const start = common.utils.alignUp(region.start().address, layout.alignment);
// check if size too large
const added = @addWithOverflow(start, layout.size);
const end = if (added[1] == 0) added[0] else continue;
// check if region too small
const subed = @subWithOverflow(region.end().address, end);
const excess = if (subed[1] == 0) subed[0] else continue;
// Try split the region into two when there's excessive space.
// Go to the next node if the unobtained part is too small for another node.
if (excess > 0) {
if (excess < @sizeOf(Node)) continue;
append(.{ .address = end }, excess);
}
// remove region from the list
current.?.next = region.next;
return @ptrFromInt(start);
}
return null;
}
fn resize(_: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool {
_ = memory;
_ = alignment;
_ = new_len;
_ = ret_addr;
return false;
}
fn remap(_: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
_ = memory;
_ = alignment;
_ = new_len;
_ = ret_addr;
return null;
}
fn free(_: *anyopaque, memory: []u8, alignment: std.mem.Alignment, _: usize) void {
const layout: Layout = .init(alignment, memory.len);
const address: VirtAddress = .{ .address = @intFromPtr(memory.ptr) };
append(address, layout.size);
}
};

View File

@ -0,0 +1,57 @@
const root = @import("root");
const libk = @import("libk");
const PhysicalAddress = root.machine.memory.PhysicalAddress;
const VirtualAddress = root.machine.memory.VirtualAddress;
pub const MemoryManager = struct {
head: ?*Node = null,
pub const Node = struct {
base: *anyopaque,
length: usize,
flags: Flags,
next: *@This(),
};
pub const Flags = libk.utils.BitFields(packed struct(usize) {
writable: bool,
executable: bool,
user_accessible: bool,
});
pub fn init() @This() {
}
pub fn deinit() void {
}
pub fn allocate(self: *@This(), length: usize, flags: Flags) void {
_ = self;
_ = length;
_ = flags;
}
pub fn free(self: *@This(), address: *anyopaque) void {
_ = self;
_ = address;
}
pub fn map(self: *@This(), virt: VirtualAddress, phys: PhysicalAddress, length: usize) void {
_ = self;
_ = phys;
_ = virt;
_ = length;
}
fn insertNode(self: *@This(), address: VirtualAddress, length: usize) void {
var current: ?*Node = self.head;
var last: ?*Node = null;
while (current) |node| {
}
}
};

View File

@ -0,0 +1,123 @@
const std = @import("std");
const libk = @import("libk");
const root = @import("root");
const limine = @import("limine");
//pub const heap = @import("heap.zig");
//pub const logical = @import("logical.zig");
pub var map: ?[]*limine.MemoryMap.Entry = null;
pub var offset: ?usize = null;
//pub var manager: logical.MemoryManager = .{};
pub const Error = error {
NotAvailable,
OutOfMemory,
};
pub const PhysicalAddress = struct {
/// base address
base: usize,
pub fn init(address: usize) @This() {
return .{ .base = address };
}
pub fn cast(self: *const @This(), comptime T: type) T {
return self.toVirtual().cast(T);
}
pub inline fn toVirtual(self: *const @This()) VirtualAddress {
return .{ .base = self.base + offset.? };
}
};
pub const VirtualAddress = struct {
/// base address
base: usize,
pub fn init(address: usize) @This() {
return .{ .base = address };
}
pub fn cast(self: *const @This(), comptime T: type) T {
return @ptrFromInt(self.base);
}
pub inline fn toPhysical(self: *const @This()) PhysicalAddress {
return .{ .base = self.base - offset.? };
}
};
pub var frames: ?[*]u8 = null;
pub var length: usize = 0;
pub fn init() void {
var total: usize = 0;
var limit: usize = 0;
var max_size: usize = 0;
var max_size_index: usize = 0;
while (limit < map.?.len) : (limit += 1) {
const entry = map.?[limit];
const size = entry.length;
if (entry.type == .usable and size > max_size) {
max_size = size;
max_size_index = limit;
}
total += size;
}
if (limit == 0) {
std.debug.panic("memory map has no entry", .{});
}
const tail = map.?[max_size_index];
const size: usize = total / @intFromEnum(root.arch.paging.frame_size);
const address: usize = tail.base + (tail.length - size);
// allocate space for storing usage data of physical frames
frames = PhysicalAddress.init(address).cast([*]u8);
length = size;
for (map.?) |entry| {
// mark all memory that is not usable as busy
if (entry.type != .usable) {
const start: usize = entry.base;
const end: usize = start + entry.length;
for (indexOf(.init(start))..indexOf(.init(end))) |i| {
frames.?[i] = .busy;
}
}
}
for (indexOf(.init(address))..indexOf(.init(address + size))) |i| {
// mark where frame data live as allocated, too
frames.?[i] = .busy;
}
}
pub fn locationOf(address: PhysicalAddress) struct {usize, u6} {
const location = address.base / @intFromEnum(root.arch.paging.frame_size);
const row: usize = location / 8;
const column: u6 = location % 8;
return .{ row, column };
}
pub fn addressOf(index: usize) usize {
return index * @intFromEnum(root.arch.paging.frame_size);
}
pub fn setFrame(address: PhysicalAddress, size: usize, value: bool) void {
const location = locationOf(address);
const row =
}
pub fn getFrame(address: PhysicalAddress) bool {
const location = locationOf(address);
}

0
src/machine/pci.zig Normal file
View File

146
src/main.zig Executable file
View File

@ -0,0 +1,146 @@
const std = @import("std");
pub const arch = @import("arch/arch.zig");
pub const debug = @import("debug.zig");
pub const machine = @import("machine/machine.zig");
pub fn main() void {
for (0..128) |i| {
debug.print("{}: hello world\n", .{i});
}
for (0..256) |i| {
debug.print("{s}", .{&[1]u8{@intCast(i)}});
}
debug.print("\n", .{});
// provoke a breakpoint exception
arch.int3();
const addresses = [_]usize{
// the identity-mapped vga buffer page
0xb8000,
// some code page
0x201008,
// some stack page
0x0100_0020_1a10,
// virtual address mapped to physical address 0
0,
};
for (addresses) |i| {
const phys = machine.memory.PhysicalAddress.init(i);
const virt = phys.toVirtual();
debug.print("phys: {!}\n", .{arch.paging.translate(virt)});
}
for (machine.memory.map.?) |region| {
debug.print("\n", .{});
debug.print("base: {}\n", .{region.base});
debug.print("length: {}\n", .{region.length});
debug.print("type: {}\n", .{region.type});
}
const virt: machine.memory.VirtualAddress = .init(0xffffffff8003ffff);
const phys = arch.paging.translate(virt);
debug.print("\n", .{});
debug.print("{!}\n", .{phys});
const vendor = arch.cpuid.Vendor.get();
debug.print("vendor: {s}\n", .{vendor.id});
//const features = arch.cpuid.Features.get();
//debug.print("features: {}\n", .{features});
const bus_frequency = arch.cpuid.BusFrequency.get();
debug.print("bus frequency: {}\n", .{bus_frequency});
const core_frequency = arch.cpuid.CoreFrequency.get();
debug.print("core frequency: {}\n", .{core_frequency});
const apic_base = arch.ModelSpecificRegister.apic_base.read();
debug.print("apic_base: 0x{x}\n", .{apic_base});
const apic_base_phys = machine.memory.PhysicalAddress.init(apic_base);
const apic_base_virt = apic_base_phys.toVirtual();
debug.print("apic_base virt: 0x{x}\n", .{apic_base_virt.base});
const apic_base_translated = arch.paging.translate(apic_base_virt);
debug.print("apic_base translated: {!}\n", .{apic_base_translated});
debug.print("fin.\n", .{});
arch.interrupt.hardware.disable();
//const time = arch.interrupt.hardware.Rtc.time();
//debug.print("time: {}\n", .{time});
arch.interrupt.hardware.enable();
var totalmem: usize = 0;
for (machine.memory.map.?) |entry| {
totalmem += entry.length;
}
debug.print("total memory: {}\n", .{totalmem});
var freemem: usize = 0;
for (machine.memory.map.?) |entry| {
if (entry.type == .usable) {
freemem += entry.length;
}
}
debug.print("free memory: {}\n", .{freemem});
debug.print("sleeping 2000 milliseconds...\n", .{});
arch.machine.time.wait(2000);
debug.print("done sleeping.\n", .{});
// disabled
while (false) {
//const allocator = machine.memory.Allocator.allocator();
//var list = std.ArrayList(u8).init(allocator);
//while (true) {
// list.append(42) catch break;
//}
//if (list.items.len == 0) break;
//debug.print("list items len: {}\n", .{list.items.len});
//list.deinit();
}
const boot_time = machine.acpi.boot_time.?;
debug.print("boot time: {}\n", .{boot_time});
const timestamp = arch.machine.time.getUnixTimestampMs();
debug.print("time: {}\n", .{timestamp});
// disabled
while (false) {
const rtc = arch.machine.time.Rtc.getUnixTimestamp();
debug.print("RTC: {}\n", .{rtc});
const system_time = arch.machine.time.getUnixTimestampMs();
debug.print("System time: {}\n\n", .{system_time});
}
// trigger interrupt 32
//asm volatile ("int $0x20");
//const elapsed = asm volatile (
// \\call uacpi_kernel_get_nanoseconds_since_boot
// : [ret] "=r" (-> u64)
//);
//debug.print("elapsed: {}\n", .{elapsed});
// provoke a page fault exception
//const pointer: *align(1) u64 = @ptrFromInt(0xdeadbeaf);
//pointer.* = 42;
}
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
debug.print("Kernel panic: {s}\n", .{message});
debug.abort();
}
comptime {
@export(&machine.init, .{ .name = "_start", .linkage = .strong });
}