commit 207c775cf59c5a84f46ad654e1a60f1a63c7cb88 Author: Break27 Date: Fri May 9 23:16:25 2025 +0800 initial commit diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..98740e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.vscode +/.zig-cache +/zig-out \ No newline at end of file diff --git a/.zigversion b/.zigversion new file mode 100644 index 0000000..0548fb4 --- /dev/null +++ b/.zigversion @@ -0,0 +1 @@ +0.14.0 \ No newline at end of file diff --git a/build.zig b/build.zig new file mode 100755 index 0000000..4e699c2 --- /dev/null +++ b/build.zig @@ -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); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100755 index 0000000..86f3bda --- /dev/null +++ b/build.zig.zon @@ -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 `, 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", + }, +} diff --git a/lib/kernel/io/IoMap.zig b/lib/kernel/io/IoMap.zig new file mode 100644 index 0000000..b9ff131 --- /dev/null +++ b/lib/kernel/io/IoMap.zig @@ -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); + } + }; + } + }; +} diff --git a/lib/kernel/io/io.zig b/lib/kernel/io/io.zig new file mode 100644 index 0000000..eb3181a --- /dev/null +++ b/lib/kernel/io/io.zig @@ -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); + } + }; +} diff --git a/lib/kernel/kernel.zig b/lib/kernel/kernel.zig new file mode 100644 index 0000000..5ae9e77 --- /dev/null +++ b/lib/kernel/kernel.zig @@ -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"); diff --git a/lib/kernel/machine/machine.zig b/lib/kernel/machine/machine.zig new file mode 100644 index 0000000..860f795 --- /dev/null +++ b/lib/kernel/machine/machine.zig @@ -0,0 +1 @@ +pub const serial = @import("serial/serial.zig"); diff --git a/lib/kernel/machine/serial/16550.zig b/lib/kernel/machine/serial/16550.zig new file mode 100755 index 0000000..414e98a --- /dev/null +++ b/lib/kernel/machine/serial/16550.zig @@ -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; + } + }; +} diff --git a/lib/kernel/machine/serial/pl011.zig b/lib/kernel/machine/serial/pl011.zig new file mode 100755 index 0000000..3787274 --- /dev/null +++ b/lib/kernel/machine/serial/pl011.zig @@ -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; + } +}; diff --git a/lib/kernel/machine/serial/serial.zig b/lib/kernel/machine/serial/serial.zig new file mode 100644 index 0000000..4e69d85 --- /dev/null +++ b/lib/kernel/machine/serial/serial.zig @@ -0,0 +1,2 @@ +pub const uart_16550 = @import("16550.zig"); +pub const uart_pl011 = @import("pl011.zig"); diff --git a/lib/kernel/object.zig b/lib/kernel/object.zig new file mode 100644 index 0000000..bd3d2b2 --- /dev/null +++ b/lib/kernel/object.zig @@ -0,0 +1,11 @@ +pub const ObjectType = enum { + untyped, + endpoint, + notification, + cnode, + tcb, + schedctx, + reply, + interrupt, + vspace, +}; diff --git a/lib/kernel/syscall.zig b/lib/kernel/syscall.zig new file mode 100644 index 0000000..e69de29 diff --git a/lib/kernel/utils/BitFields.zig b/lib/kernel/utils/BitFields.zig new file mode 100644 index 0000000..a6c9125 --- /dev/null +++ b/lib/kernel/utils/BitFields.zig @@ -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)); + } + }; +} diff --git a/lib/kernel/utils/utils.zig b/lib/kernel/utils/utils.zig new file mode 100644 index 0000000..e282a67 --- /dev/null +++ b/lib/kernel/utils/utils.zig @@ -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; +} diff --git a/lib/limine/limine.conf b/lib/limine/limine.conf new file mode 100755 index 0000000..fed1ca4 --- /dev/null +++ b/lib/limine/limine.conf @@ -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 \ No newline at end of file diff --git a/lib/limine/limine.zig b/lib/limine/limine.zig new file mode 100755 index 0000000..366254a --- /dev/null +++ b/lib/limine/limine.zig @@ -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, + }; +}; diff --git a/lib/uacpi/uacpi.zig b/lib/uacpi/uacpi.zig new file mode 100644 index 0000000..b7370d4 --- /dev/null +++ b/lib/uacpi/uacpi.zig @@ -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; \ No newline at end of file diff --git a/src/arch/aarch64/aarch64.zig b/src/arch/aarch64/aarch64.zig new file mode 100755 index 0000000..52dfb2c --- /dev/null +++ b/src/arch/aarch64/aarch64.zig @@ -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"); +} diff --git a/src/arch/aarch64/debug.zig b/src/arch/aarch64/debug.zig new file mode 100644 index 0000000..22c624a --- /dev/null +++ b/src/arch/aarch64/debug.zig @@ -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; +} diff --git a/src/arch/aarch64/machine/machine.zig b/src/arch/aarch64/machine/machine.zig new file mode 100644 index 0000000..50105cf --- /dev/null +++ b/src/arch/aarch64/machine/machine.zig @@ -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(); +} diff --git a/src/arch/aarch64/machine/serial.zig b/src/arch/aarch64/machine/serial.zig new file mode 100644 index 0000000..1825c45 --- /dev/null +++ b/src/arch/aarch64/machine/serial.zig @@ -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 {}; +} diff --git a/src/arch/arch.zig b/src/arch/arch.zig new file mode 100644 index 0000000..b8ec5b9 --- /dev/null +++ b/src/arch/arch.zig @@ -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), +}; \ No newline at end of file diff --git a/src/arch/loong64/debug.zig b/src/arch/loong64/debug.zig new file mode 100644 index 0000000..22c624a --- /dev/null +++ b/src/arch/loong64/debug.zig @@ -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; +} diff --git a/src/arch/loong64/loong64.zig b/src/arch/loong64/loong64.zig new file mode 100755 index 0000000..4e60e5e --- /dev/null +++ b/src/arch/loong64/loong64.zig @@ -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"); +} diff --git a/src/arch/loong64/machine/machine.zig b/src/arch/loong64/machine/machine.zig new file mode 100644 index 0000000..56228f5 --- /dev/null +++ b/src/arch/loong64/machine/machine.zig @@ -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(); +} diff --git a/src/arch/loong64/machine/serial.zig b/src/arch/loong64/machine/serial.zig new file mode 100644 index 0000000..0060cdd --- /dev/null +++ b/src/arch/loong64/machine/serial.zig @@ -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 {}; +} diff --git a/src/arch/riscv64/debug.zig b/src/arch/riscv64/debug.zig new file mode 100644 index 0000000..22c624a --- /dev/null +++ b/src/arch/riscv64/debug.zig @@ -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; +} diff --git a/src/arch/riscv64/machine/machine.zig b/src/arch/riscv64/machine/machine.zig new file mode 100644 index 0000000..8cb0653 --- /dev/null +++ b/src/arch/riscv64/machine/machine.zig @@ -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(); +} diff --git a/src/arch/riscv64/machine/serial.zig b/src/arch/riscv64/machine/serial.zig new file mode 100644 index 0000000..f6ac288 --- /dev/null +++ b/src/arch/riscv64/machine/serial.zig @@ -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 {}; +} diff --git a/src/arch/riscv64/riscv64.zig b/src/arch/riscv64/riscv64.zig new file mode 100755 index 0000000..52dfb2c --- /dev/null +++ b/src/arch/riscv64/riscv64.zig @@ -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"); +} diff --git a/src/arch/x86_64/cpuid.zig b/src/arch/x86_64/cpuid.zig new file mode 100644 index 0000000..562a197 --- /dev/null +++ b/src/arch/x86_64/cpuid.zig @@ -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), + }; + } +}; diff --git a/src/arch/x86_64/debug.zig b/src/arch/x86_64/debug.zig new file mode 100644 index 0000000..6b38cc5 --- /dev/null +++ b/src/arch/x86_64/debug.zig @@ -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; +} diff --git a/src/arch/x86_64/interrupt/exception.zig b/src/arch/x86_64/interrupt/exception.zig new file mode 100644 index 0000000..6eb9bb8 --- /dev/null +++ b/src/arch/x86_64/interrupt/exception.zig @@ -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(); + } +}); diff --git a/src/arch/x86_64/interrupt/hardware.zig b/src/arch/x86_64/interrupt/hardware.zig new file mode 100644 index 0000000..a84d5f0 --- /dev/null +++ b/src/arch/x86_64/interrupt/hardware.zig @@ -0,0 +1,7 @@ +pub inline fn enable() void { + asm volatile ("sti"); +} + +pub inline fn disable() void { + asm volatile ("cli"); +} diff --git a/src/arch/x86_64/interrupt/interrupt.zig b/src/arch/x86_64/interrupt/interrupt.zig new file mode 100644 index 0000000..3a7a741 --- /dev/null +++ b/src/arch/x86_64/interrupt/interrupt.zig @@ -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); +} diff --git a/src/arch/x86_64/machine/apic/Apic.zig b/src/arch/x86_64/machine/apic/Apic.zig new file mode 100644 index 0000000..627f501 --- /dev/null +++ b/src/arch/x86_64/machine/apic/Apic.zig @@ -0,0 +1,4 @@ +pub const ioApic = @import("ioApic.zig"); +pub const x2Apic = @import("x2Apic.zig"); +pub const xApic = @import("xApic.zig"); + diff --git a/src/arch/x86_64/machine/apic/ioApic.zig b/src/arch/x86_64/machine/apic/ioApic.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/arch/x86_64/machine/apic/x2Apic.zig b/src/arch/x86_64/machine/apic/x2Apic.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/arch/x86_64/machine/apic/xApic.zig b/src/arch/x86_64/machine/apic/xApic.zig new file mode 100644 index 0000000..a154826 --- /dev/null +++ b/src/arch/x86_64/machine/apic/xApic.zig @@ -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); +} diff --git a/src/arch/x86_64/machine/cmos.zig b/src/arch/x86_64/machine/cmos.zig new file mode 100644 index 0000000..a8c777c --- /dev/null +++ b/src/arch/x86_64/machine/cmos.zig @@ -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); +} diff --git a/src/arch/x86_64/machine/machine.zig b/src/arch/x86_64/machine/machine.zig new file mode 100755 index 0000000..dbd7e9e --- /dev/null +++ b/src/arch/x86_64/machine/machine.zig @@ -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(); +} diff --git a/src/arch/x86_64/machine/pic.zig b/src/arch/x86_64/machine/pic.zig new file mode 100644 index 0000000..431d907 --- /dev/null +++ b/src/arch/x86_64/machine/pic.zig @@ -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); + } + }); +}; diff --git a/src/arch/x86_64/machine/ps2.zig b/src/arch/x86_64/machine/ps2.zig new file mode 100644 index 0000000..455e782 --- /dev/null +++ b/src/arch/x86_64/machine/ps2.zig @@ -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 { + + } +}; diff --git a/src/arch/x86_64/machine/serial.zig b/src/arch/x86_64/machine/serial.zig new file mode 100644 index 0000000..312d3f7 --- /dev/null +++ b/src/arch/x86_64/machine/serial.zig @@ -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 {}; +} diff --git a/src/arch/x86_64/machine/time.zig b/src/arch/x86_64/machine/time.zig new file mode 100644 index 0000000..8bc5c55 --- /dev/null +++ b/src/arch/x86_64/machine/time.zig @@ -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; +} diff --git a/src/arch/x86_64/paging.zig b/src/arch/x86_64/paging.zig new file mode 100644 index 0000000..dadea6c --- /dev/null +++ b/src/arch/x86_64/paging.zig @@ -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 }; +} diff --git a/src/arch/x86_64/segmentation.zig b/src/arch/x86_64/segmentation.zig new file mode 100644 index 0000000..40eeb34 --- /dev/null +++ b/src/arch/x86_64/segmentation.zig @@ -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 + 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(); +} diff --git a/src/arch/x86_64/x86_64.zig b/src/arch/x86_64/x86_64.zig new file mode 100755 index 0000000..6872355 --- /dev/null +++ b/src/arch/x86_64/x86_64.zig @@ -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)), + } +} diff --git a/src/debug.zig b/src/debug.zig new file mode 100755 index 0000000..fb9d8e3 --- /dev/null +++ b/src/debug.zig @@ -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; +} diff --git a/src/machine/acpi/acpi.zig b/src/machine/acpi/acpi.zig new file mode 100644 index 0000000..6efe897 --- /dev/null +++ b/src/machine/acpi/acpi.zig @@ -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", .{}); +} diff --git a/src/machine/framebuffer/Console.zig b/src/machine/framebuffer/Console.zig new file mode 100755 index 0000000..48f6beb --- /dev/null +++ b/src/machine/framebuffer/Console.zig @@ -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; + } +} diff --git a/src/machine/framebuffer/bitmap.bin b/src/machine/framebuffer/bitmap.bin new file mode 100755 index 0000000..24edd76 Binary files /dev/null and b/src/machine/framebuffer/bitmap.bin differ diff --git a/src/machine/machine.zig b/src/machine/machine.zig new file mode 100644 index 0000000..34e7d99 --- /dev/null +++ b/src/machine/machine.zig @@ -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", .{}); +} diff --git a/src/machine/memory/heap.zig b/src/machine/memory/heap.zig new file mode 100644 index 0000000..56bdcb4 --- /dev/null +++ b/src/machine/memory/heap.zig @@ -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); + } +}; diff --git a/src/machine/memory/logical.zig b/src/machine/memory/logical.zig new file mode 100644 index 0000000..6231ed4 --- /dev/null +++ b/src/machine/memory/logical.zig @@ -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| { + + } + } +}; diff --git a/src/machine/memory/memory.zig b/src/machine/memory/memory.zig new file mode 100644 index 0000000..c75248a --- /dev/null +++ b/src/machine/memory/memory.zig @@ -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); +} diff --git a/src/machine/pci.zig b/src/machine/pci.zig new file mode 100644 index 0000000..e69de29 diff --git a/src/main.zig b/src/main.zig new file mode 100755 index 0000000..46ae8ea --- /dev/null +++ b/src/main.zig @@ -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 }); +}