From: Damian Myrda Date: Sat, 21 Sep 2024 16:54:11 +0000 (-0500) Subject: x86 basic kernel X-Git-Url: http://git.prime8.dev/?a=commitdiff_plain;h=f45fa74b9a16a946ce3228d9d0a163fac81181bc;p=mos.git x86 basic kernel --- f45fa74b9a16a946ce3228d9d0a163fac81181bc diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..f72f66b --- /dev/null +++ b/.gdbinit @@ -0,0 +1,2 @@ +file ./zig-out/root/boot/kernel +target remote localhost:1234 diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..d864d9e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/zig-cache/ +/zig-out/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..7e36748 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "limine"] + path = limine + url = https://github.com/limine-bootloader/limine.git + branch = binary diff --git a/README.md b/README.md new file mode 100755 index 0000000..e244a36 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# mos +A monkey's operating system. diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..c6408be --- /dev/null +++ b/build.zig @@ -0,0 +1,24 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) std.mem.Allocator.Error!void { + // filesystem + b.install_prefix = b.build_root.join(b.allocator, &[_][]const u8{ "zig-out", "root" }) catch unreachable; + b.install_path = b.install_prefix; + + // kernel + const kernel = b.addInstallArtifact(b.anonymousDependency("./kernel", @import("./kernel/build.zig"), .{}).artifact("kernel"), .{ + .dest_dir = .{ .override = .{ .custom = "/boot" } }, + }); + _ = b.step("kernel", "Build the operating system kernel").dependOn(&kernel.step); + + // create iso + const create_iso = b.addSystemCommand(&[_][]const u8{"./create_iso.sh"}); + create_iso.step.dependOn(&kernel.step); + _ = b.step("iso", "Create an iso image of the operating system").dependOn(&create_iso.step); + b.getInstallStep().dependOn(&create_iso.step); + + // emulation + const qemu = b.addSystemCommand(&[_][]const u8{"./qemu.sh"}); + _ = b.step("run", "Emulate the operating system using qemu").dependOn(&qemu.step); + qemu.step.dependOn(b.getInstallStep()); +} diff --git a/create_iso.sh b/create_iso.sh new file mode 100755 index 0000000..d9a2ff5 --- /dev/null +++ b/create_iso.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +mkdir -vp ./zig-out/root/boot +cp -v ./limine.cfg \ + ./limine/limine-bios.sys \ + ./limine/limine-bios-cd.bin \ + ./limine/limine-uefi-cd.bin \ + ./zig-out/root/boot/ + +mkdir -vp ./zig-out/root/boot/EFI/BOOT/ +cp -v ./limine/BOOTX64.EFI ./zig-out/root/boot/EFI/BOOT/ + +xorriso -as mkisofs -b /boot/limine-bios-cd.bin \ + -no-emul-boot -boot-load-size 4 -boot-info-table \ + --efi-boot /boot/limine-uefi-cd.bin \ + -efi-boot-part --efi-boot-image --protective-msdos-label \ + ./zig-out/root -o ./zig-out/mos.iso + +./limine/limine bios-install ./zig-out/mos.iso + diff --git a/kernel/.gitignore b/kernel/.gitignore new file mode 100644 index 0000000..d864d9e --- /dev/null +++ b/kernel/.gitignore @@ -0,0 +1,2 @@ +/zig-cache/ +/zig-out/ diff --git a/kernel/.gitmodules b/kernel/.gitmodules new file mode 100644 index 0000000..94b6d59 --- /dev/null +++ b/kernel/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/limine"] + path = deps/limine + url = git@github.com:limine-bootloader/limine-zig.git diff --git a/kernel/.pre-commit-config.yaml b/kernel/.pre-commit-config.yaml new file mode 100644 index 0000000..9f67749 --- /dev/null +++ b/kernel/.pre-commit-config.yaml @@ -0,0 +1,5 @@ +repos: + - repo: https://github.com/batmac/pre-commit-zig + rev: v0.3.0 + hooks: + - id: zig-fmt diff --git a/kernel/build.zig b/kernel/build.zig new file mode 100644 index 0000000..31d11cc --- /dev/null +++ b/kernel/build.zig @@ -0,0 +1,45 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +pub fn build(b: *std.Build) anyerror!void { + // build options + var target = b.resolveTargetQuery(.{ + .cpu_arch = .x86_64, + .os_tag = .freestanding, + .abi = .none, + }); + target.query.cpu_features_sub.addFeature(@intFromEnum(std.Target.x86.Feature.mmx)); + target.query.cpu_features_sub.addFeature(@intFromEnum(std.Target.x86.Feature.sse)); + target.query.cpu_features_sub.addFeature(@intFromEnum(std.Target.x86.Feature.sse2)); + target.query.cpu_features_sub.addFeature(@intFromEnum(std.Target.x86.Feature.avx)); + target.query.cpu_features_sub.addFeature(@intFromEnum(std.Target.x86.Feature.avx2)); + target.query.cpu_features_add.addFeature(@intFromEnum(std.Target.x86.Feature.soft_float)); + const optimize = b.standardOptimizeOption(.{ + .preferred_optimize_mode = .Debug, + }); + + // dependencies + const limine = b.createModule(.{ + .root_source_file = .{ .path = "./deps/limine/limine.zig" }, + }); + + // modules + const arch = b.createModule(.{ .root_source_file = .{ .path = switch (target.result.cpu.arch) { + .x86_64 => "./src/x86.zig", + else => unreachable, + } } }); + arch.addImport("limine", limine); + + // kernel binary + const kernel = b.addExecutable(.{ + .name = "kernel", + .root_source_file = .{ .path = "./src/main.zig" }, + .target = target, + .optimize = optimize, + }); + kernel.setLinkerScriptPath(.{ .path = "./linker.ld" }); + kernel.pie = true; + kernel.root_module.addImport("limine", limine); + kernel.root_module.addImport("arch", arch); + b.getInstallStep().dependOn(&b.addInstallArtifact(kernel, .{}).step); +} diff --git a/kernel/deps/limine/LICENSE b/kernel/deps/limine/LICENSE new file mode 100644 index 0000000..72b01a7 --- /dev/null +++ b/kernel/deps/limine/LICENSE @@ -0,0 +1,22 @@ +Copyright (C) 2022-2024 48cf and contributors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/kernel/deps/limine/README.md b/kernel/deps/limine/README.md new file mode 100644 index 0000000..2abf5ae --- /dev/null +++ b/kernel/deps/limine/README.md @@ -0,0 +1,6 @@ +# limine-zig + +This is a Zig library for working with [The Limine Boot Protocol](https://github.com/limine-bootloader/limine/blob/trunk/PROTOCOL.md). +You can find an example barebones kernel using this library [here](https://github.com/limine-bootloader/limine-zig-barebones). + +To use this library, you need at least Zig 0.11.x. diff --git a/kernel/deps/limine/build.zig b/kernel/deps/limine/build.zig new file mode 100644 index 0000000..6c82de3 --- /dev/null +++ b/kernel/deps/limine/build.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const CreateOptions = std.Build.Module.CreateOptions; + var options: CreateOptions = .{}; + + const root_source_path: std.Build.LazyPath = .{ .path = "limine.zig" }; + if (@hasField(CreateOptions, "source_file")) { + options.source_file = root_source_path; + } else if (@hasField(CreateOptions, "root_source_file")) { + options.root_source_file = root_source_path; + } else { + @compileError("unsupported zig version"); + } + + _ = b.addModule("limine", options); +} diff --git a/kernel/deps/limine/limine.zig b/kernel/deps/limine/limine.zig new file mode 100644 index 0000000..38c707b --- /dev/null +++ b/kernel/deps/limine/limine.zig @@ -0,0 +1,586 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +inline 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 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 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 inline fn data(self: *@This()) []u8 { + return self.address[0..self.size]; + } +}; + +// Boot info + +pub const BootloaderInfoResponse = extern struct { + revision: u64, + name: [*:0]u8, + version: [*:0]u8, +}; + +pub const BootloaderInfoRequest = extern struct { + id: [4]u64 = magic(0xf55038d8e2a1202f, 0x279426fcf5f59740), + revision: u64 = 0, + response: ?*BootloaderInfoResponse = null, +}; + +// Stack size + +pub const StackSizeResponse = extern struct { + revision: u64, +}; + +pub const StackSizeRequest = extern struct { + id: [4]u64 = magic(0x224ef0460a8e8926, 0xe1cb0fc25f46ea3d), + revision: u64 = 0, + response: ?*StackSizeResponse = null, + stack_size: u64, +}; + +// HHDM + +pub const HhdmResponse = extern struct { + revision: u64, + offset: u64, +}; + +pub const HhdmRequest = extern struct { + id: [4]u64 = magic(0x48dcf1cb8ad2b852, 0x63984e959a98244b), + revision: u64 = 0, + response: ?*HhdmResponse = null, +}; + +// Framebuffer + +pub const FramebufferMemoryModel = enum(u8) { + rgb = 1, + _, +}; + +pub const VideoMode = extern struct { + pitch: u64, + width: u64, + height: u64, + bpp: u16, + memory_model: u8, + red_mask_size: u8, + red_mask_shift: u8, + green_mask_size: u8, + green_mask_shift: u8, + blue_mask_size: u8, + blue_mask_shift: u8, +}; + +pub const Framebuffer = extern struct { + address: [*]u8, + width: u64, + height: u64, + pitch: u64, + bpp: u16, + memory_model: FramebufferMemoryModel, + 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 inline fn data(self: *@This()) []u8 { + return self.address[0 .. self.pitch * self.height]; + } + + pub inline fn edidData(self: *@This()) ?[]u8 { + if (self.edid) |edid_data| { + return edid_data[0..self.edid_size]; + } + return null; + } + + pub inline fn videoModes(self: *@This()) []*VideoMode { + return self.modes[0..self.mode_count]; + } +}; + +pub const FramebufferResponse = extern struct { + revision: u64, + framebuffer_count: u64, + framebuffers_ptr: [*]*Framebuffer, + + pub inline fn framebuffers(self: *@This()) []*Framebuffer { + return self.framebuffers_ptr[0..self.framebuffer_count]; + } +}; + +pub const FramebufferRequest = extern struct { + id: [4]u64 = magic(0x9d5827dcd881dd75, 0xa3148604f6fab11b), + revision: u64 = 1, + response: ?*FramebufferResponse = null, +}; + +// Terminal + +pub const Terminal = extern struct { + columns: u64, + rows: u64, + framebuffer: *Framebuffer, +}; + +pub const OobOutputFlags = enum(u64) { + ocrnl = 1 << 0, + ofdel = 1 << 1, + ofill = 1 << 2, + olcuc = 1 << 3, + onlcr = 1 << 4, + onlret = 1 << 5, + onocr = 1 << 6, + opost = 1 << 7, +}; + +pub const TerminalResponse = extern struct { + revision: u64, + terminal_count: u64, + terminals_ptr: [*]*Terminal, + write_fn: *const fn (*Terminal, [*]const u8, u64) callconv(.C) void, + + pub inline fn terminals(self: *@This()) []*Terminal { + return self.terminals_ptr[0..self.terminal_count]; + } + + pub inline fn write(self: *@This(), terminal: ?*Terminal, string: []const u8) void { + self.write_fn(terminal orelse self.terminals_ptr[0], string.ptr, string.len); + } + + pub inline fn ctxSize(self: *@This(), terminal: ?*Terminal) u64 { + var result: u64 = undefined; + self.write_fn(terminal orelse self.terminals_ptr[0], @as([*]const u8, @ptrCast(&result)), @as(u64, @bitCast(@as(i64, -1)))); + return result; + } + + pub inline fn ctxSave(self: *@This(), terminal: ?*Terminal, ctx: [*]u8) void { + self.write_fn(terminal orelse self.terminals_ptr[0], @as([*]const u8, @ptrCast(ctx)), @as(u64, @bitCast(@as(i64, -2)))); + } + + pub inline fn ctxRestore(self: *@This(), terminal: ?*Terminal, ctx: [*]const u8) void { + self.write_fn(terminal orelse self.terminals_ptr[0], @as([*]const u8, @ptrCast(ctx)), @as(u64, @bitCast(@as(i64, -3)))); + } + + pub inline fn fullRefresh(self: *@This(), terminal: ?*Terminal) void { + self.write_fn(terminal orelse self.terminals_ptr[0], "", @as(u64, @bitCast(@as(i64, -4)))); + } + + // Response revision 1 + pub inline fn oobOutputGet(self: *@This(), terminal: ?*Terminal) u64 { + var result: u64 = undefined; + self.write_fn(terminal orelse self.terminals_ptr[0], @as([*]const u8, @ptrCast(&result)), @as(u64, @bitCast(@as(i64, -10)))); + return result; + } + + pub inline fn oobOutputSet(self: *@This(), terminal: ?*Terminal, value: u64) void { + self.write_fn(terminal orelse self.terminals_ptr[0], @as([*]const u8, @ptrCast(&value)), @as(u64, @bitCast(@as(i64, -11)))); + } +}; + +pub const CallbackType = enum(u64) { + dec = 10, + bell = 20, + private_id = 30, + status_report = 40, + pos_report = 50, + kbd_leds = 60, + mode = 70, + linux = 80, + _, +}; + +pub const TerminalRequest = extern struct { + id: [4]u64 = magic(0xc8ac59310c2b0844, 0xa68d0c7265d38878), + revision: u64 = 0, + response: ?*TerminalResponse = null, + callback: ?*const fn (*Terminal, CallbackType, u64, u64, u64) callconv(.C) void = null, +}; + +// Paging mode + +const X86PagingMode = enum(u64) { + four_level = 0, + five_level = 1, + default = .four_level, +}; + +const AArch64PagingMode = enum(u64) { + four_level = 0, + five_level = 1, + default = .four_level, +}; + +const RiscVPagingMode = enum(u64) { + sv39 = 0, + sv48 = 1, + sv57 = 2, + default = .sv48, +}; + +pub const PagingMode = switch (builtin.cpu.arch) { + .x86, .x86_64 => X86PagingMode, + .aarch64 => AArch64PagingMode, + .riscv64 => RiscVPagingMode, + else => |arch| @compileError("Unsupported architecture: " ++ @tagName(arch)), +}; + +pub const PagingModeResponse = extern struct { + revision: u64, + mode: PagingMode, + flags: u64, +}; + +pub const PagingModeRequest = extern struct { + id: [4]u64 = magic(0x95c1a0edab0944cb, 0xa4e5cb3842f7488a), + revision: u64 = 0, + response: ?*PagingModeResponse = null, + mode: PagingMode, + flags: u64, +}; + +// 5-level paging + +pub const FiveLevelPagingResponse = extern struct { + revision: u64, +}; + +pub const FiveLevelPagingRequest = extern struct { + id: [4]u64 = magic(0x94469551da9b3192, 0xebe5e86db7382888), + revision: u64 = 0, + response: ?*FiveLevelPagingResponse = null, +}; + +// SMP +const X86SmpInfo = extern struct { + processor_id: u32, + lapic_id: u32, + reserved: u64, + goto_address: ?*const fn (*@This()) callconv(.C) noreturn, + extra_argument: u64, +}; + +const X86SmpFlags = enum(u32) { + x2apic = 1 << 0, +}; + +const X86SmpResponse = extern struct { + revision: u64, + flags: u32, + bsp_lapic_id: u32, + cpu_count: u64, + cpus_ptr: [*]*X86SmpInfo, + + pub inline fn cpus(self: *@This()) []*X86SmpInfo { + return self.cpus_ptr[0..self.cpu_count]; + } +}; + +const AArch64SmpInfo = extern struct { + processor_id: u32, + gic_iface_no: u32, + mpidr: u64, + reserved: u64, + goto_address: ?*const fn (*@This()) callconv(.C) noreturn, + extra_argument: u64, +}; + +const AArch64SmpFlags = enum(u32) {}; + +const AArch64SmpResponse = extern struct { + revision: u64, + flags: u32, + bsp_mpidr: u64, + cpu_count: u64, + cpus_ptr: [*]*AArch64SmpInfo, + + pub inline fn cpus(self: *@This()) []*AArch64SmpInfo { + return self.cpus_ptr[0..self.cpu_count]; + } +}; + +const RiscVSmpInfo = extern struct { + processor_id: u32, + hart_id: u32, + reserved: u64, + goto_address: ?*const fn (*@This()) callconv(.C) noreturn, + extra_argument: u64, +}; + +const RiscVSmpFlags = enum(u32) {}; + +const RiscVSmpResponse = extern struct { + revision: u64, + flags: u32, + bsp_hart_id: u64, + cpu_count: u64, + cpus_ptr: [*]*RiscVSmpInfo, + + pub inline fn cpus(self: *@This()) []*RiscVSmpInfo { + return self.cpus_ptr[0..self.cpu_count]; + } +}; + +pub const SmpInfo = switch (builtin.cpu.arch) { + .x86, .x86_64 => X86SmpInfo, + .aarch64 => AArch64SmpInfo, + .riscv64 => RiscVSmpInfo, + else => |arch| @compileError("Unsupported architecture: " ++ @tagName(arch)), +}; + +pub const SmpFlags = switch (builtin.cpu.arch) { + .x86, .x86_64 => X86SmpFlags, + .aarch64 => AArch64SmpFlags, + .riscv64 => RiscVSmpFlags, + else => |arch| @compileError("Unsupported architecture: " ++ @tagName(arch)), +}; + +pub const SmpResponse = switch (builtin.cpu.arch) { + .x86, .x86_64 => X86SmpResponse, + .aarch64 => AArch64SmpResponse, + .riscv64 => RiscVSmpResponse, + else => |arch| @compileError("Unsupported architecture: " ++ @tagName(arch)), +}; + +pub const SmpRequest = extern struct { + id: [4]u64 = magic(0x95a67b819a1b857e, 0xa0b61b723b6a73e0), + revision: u64 = 0, + response: ?*SmpResponse = null, + flags: u64 = 0, +}; + +// Memory map + +pub const MemoryMapEntryType = 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 MemoryMapEntry = extern struct { + base: u64, + length: u64, + kind: MemoryMapEntryType, +}; + +pub const MemoryMapResponse = extern struct { + revision: u64, + entry_count: u64, + entries_ptr: [*]*MemoryMapEntry, + + pub inline fn entries(self: *@This()) []*MemoryMapEntry { + return self.entries_ptr[0..self.entry_count]; + } +}; + +pub const MemoryMapRequest = extern struct { + id: [4]u64 = magic(0x67cf3d9d378a806f, 0xe304acdfc50c3c62), + revision: u64 = 0, + response: ?*MemoryMapResponse = null, +}; + +// Entry point + +pub const EntryPointResponse = extern struct { + revision: u64, +}; + +pub const EntryPointRequest = extern struct { + id: [4]u64 = magic(0x13d86c035a1cd3e1, 0x2b0caa89d8f3026a), + revision: u64 = 0, + response: ?*EntryPointResponse = null, + entry: ?*const fn () callconv(.C) noreturn = null, +}; + +// Kernel file + +pub const KernelFileResponse = extern struct { + revision: u64, + kernel_file: *File, +}; + +pub const KernelFileRequest = extern struct { + id: [4]u64 = magic(0xad97e90e83f1ed67, 0x31eb5d1c5ff23b69), + revision: u64 = 0, + response: ?*KernelFileResponse = null, +}; + +// Module + +pub const InternalModuleFlags = enum(u64) { + required = 1 << 0, +}; + +pub const InternalModule = extern struct { + path: [*:0]const u8, + cmdline: [*:0]const u8, + flags: InternalModuleFlags, +}; + +pub const ModuleResponse = extern struct { + revision: u64, + module_count: u64, + modules_ptr: [*]*File, + + pub inline fn modules(self: *@This()) []*File { + return self.modules_ptr[0..self.module_count]; + } +}; + +pub const ModuleRequest = extern struct { + id: [4]u64 = magic(0x3e7e279702be32af, 0xca1c4f3bd1280cee), + revision: u64 = 1, + response: ?*ModuleResponse = null, + + // Request revision 1 + internal_module_count: u64 = 0, + internal_modules: ?[*]const *const InternalModule = null, +}; + +// RSDP + +pub const RsdpResponse = extern struct { + revision: u64, + address: *anyopaque, +}; + +pub const RsdpRequest = extern struct { + id: [4]u64 = magic(0xc5e77b6b397e7b43, 0x27637845accdcf3c), + revision: u64 = 0, + response: ?*RsdpResponse = null, +}; + +// SMBIOS + +pub const SmbiosResponse = extern struct { + revision: u64, + entry_32: ?*anyopaque, + entry_64: ?*anyopaque, +}; + +pub const SmbiosRequest = extern struct { + id: [4]u64 = magic(0x9e9046f11e095391, 0xaa4a520fefbde5ee), + revision: u64 = 0, + response: ?*SmbiosResponse = null, +}; + +// EFI system table + +pub const EfiSystemStableResponse = extern struct { + revision: u64, + address: *anyopaque, +}; + +pub const EfiSystemTableRequest = extern struct { + id: [4]u64 = magic(0x5ceba5163eaaf6d6, 0x0a6981610cf65fcc), + revision: u64 = 0, + response: ?*EfiSystemStableResponse = null, +}; + +// EFI memory map + +pub const EfiMemoryMapResponse = extern struct { + revision: u64, + memmap: *anyopaque, + memmap_size: u64, + desc_size: u64, + desc_version: u64, +}; + +pub const EfiMemoryMapRequest = extern struct { + id: [4]u64 = magic(0x7df62a431d6872d5, 0xa4fcdfb3e57306c8), + revision: u64 = 0, + response: ?*EfiMemoryMapResponse = null, +}; + +// Boot time + +pub const BootTimeResponse = extern struct { + revision: u64, + boot_time: i64, +}; + +pub const BootTimeRequest = extern struct { + id: [4]u64 = magic(0x502746e184c088aa, 0xfbc5ec83e6327893), + revision: u64 = 0, + response: ?*BootTimeResponse = null, +}; + +// Kernel address + +pub const KernelAddressResponse = extern struct { + revision: u64, + physical_base: u64, + virtual_base: u64, +}; + +pub const KernelAddressRequest = extern struct { + id: [4]u64 = magic(0x71ba76863cc55f63, 0xb2644a48c516a487), + revision: u64 = 0, + response: ?*KernelAddressResponse = null, +}; + +// Device tree blob + +pub const DeviceTreeBlobResponse = extern struct { + revision: u64, + dtb: ?*anyopaque, +}; + +pub const DeviceTreeBlobRequest = extern struct { + id: [4]u64 = magic(0xb40ddb48fb54bac7, 0x545081493f81ffb7), + revision: u64 = 0, + response: ?*DeviceTreeBlobResponse = null, +}; diff --git a/kernel/deps/limine/zig-cache/h/5b6ecd7523182e2589cc991d9a944676.txt b/kernel/deps/limine/zig-cache/h/5b6ecd7523182e2589cc991d9a944676.txt new file mode 100644 index 0000000..e69de29 diff --git a/kernel/deps/limine/zig-cache/h/timestamp b/kernel/deps/limine/zig-cache/h/timestamp new file mode 100644 index 0000000..e69de29 diff --git a/kernel/deps/limine/zig-cache/o/df65dd7eb12dada13f920fe098a02adf/dependencies.zig b/kernel/deps/limine/zig-cache/o/df65dd7eb12dada13f920fe098a02adf/dependencies.zig new file mode 100644 index 0000000..72e4e83 --- /dev/null +++ b/kernel/deps/limine/zig-cache/o/df65dd7eb12dada13f920fe098a02adf/dependencies.zig @@ -0,0 +1,2 @@ +pub const packages = struct {}; +pub const root_deps: []const struct { []const u8, []const u8 } = &.{}; diff --git a/kernel/deps/limine/zig-cache/z/7cac848da18911d1686214078038a257 b/kernel/deps/limine/zig-cache/z/7cac848da18911d1686214078038a257 new file mode 100644 index 0000000..64fd5b4 Binary files /dev/null and b/kernel/deps/limine/zig-cache/z/7cac848da18911d1686214078038a257 differ diff --git a/kernel/linker.ld b/kernel/linker.ld new file mode 100644 index 0000000..b51359a --- /dev/null +++ b/kernel/linker.ld @@ -0,0 +1,47 @@ +OUTPUT_FORMAT(elf64-x86-64) +OUTPUT_ARCH(i386:x86-64) + +ENTRY(_start) + +PHDRS +{ + text PT_LOAD FLAGS((1 << 0) | (1 << 2)); /* Execute + Read */ + rodata PT_LOAD FLAGS((1 << 2)); /* Read only */ + data PT_LOAD FLAGS((1 << 1) | (1 << 2)); /* Write + Read */ + dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)); /* Dynamic PHDR for relocations */ +} + +SECTIONS +{ + . = 0xffffffff80000000; + + .text : { + *(.text .text.*) + } :text + + . = ALIGN(4K); + + .rodata : { + *(.rodata .rodata.*) + } :rodata + + . = ALIGN(4K); + + .data : { + *(.data .data.*) + } :data + + .dynamic : { + *(.dynamic) + } :data :dynamic + + .bss : { + *(.bss .bss.*) + *(COMMON) + } :data + + /DISCARD/ : { + *(.eh_frame) + *(.note .note.*) + } +} diff --git a/kernel/src/FONT.F16 b/kernel/src/FONT.F16 new file mode 100644 index 0000000..ee47a54 Binary files /dev/null and b/kernel/src/FONT.F16 differ diff --git a/kernel/src/cli.zig b/kernel/src/cli.zig new file mode 100644 index 0000000..1092b5f --- /dev/null +++ b/kernel/src/cli.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const log = std.log.scoped(.cli); +const srl = @import("./srl.zig"); + +pub fn run() void { + while (true) { + const byte = srl.read(u8); + log.debug("input: {X}", .{byte}); + } +} diff --git a/kernel/src/dbg.zig b/kernel/src/dbg.zig new file mode 100644 index 0000000..fe24334 --- /dev/null +++ b/kernel/src/dbg.zig @@ -0,0 +1,10 @@ +const std = @import("std"); +const dbg = @import("arch").dbg; + +pub var writer = std.io.Writer(std.log.Level, error{}, callback){ .context = std.log.Level.info }; +fn callback(_: std.log.Level, string: []const u8) error{}!usize { + for (string) |character| { + dbg.out(character); + } + return string.len; +} diff --git a/kernel/src/fb.zig b/kernel/src/fb.zig new file mode 100644 index 0000000..b994039 --- /dev/null +++ b/kernel/src/fb.zig @@ -0,0 +1,78 @@ +const std = @import("std"); +const log = std.log.scoped(.fb); +const limine = @import("limine"); +const font = @import("./font.zig"); + +const MARGIN = 5; + +const Color = u32; + +var framebuffer: *limine.Framebuffer = undefined; +var row: usize = 0; +var column: usize = 0; +var foreground: Color = 0xFFFFFF; +var background: Color = 0x000000; + +fn nextline() void { + column = 0; + if ((2 * MARGIN) + ((row + 1) * font.HEIGHT) > framebuffer.height) { + scroll(); + return; + } + row += 1; +} + +fn scroll() void {} + +pub var writer = std.io.Writer(std.log.Level, error{}, callback){ .context = std.log.Level.info }; +fn callback(level: std.log.Level, string: []const u8) error{}!usize { + if (framebuffer == undefined) + return 0; + foreground = switch (level) { + .debug => 0x0000FF, + .info => 0xFFFFFF, + .warn => 0xFFFF00, + .err => 0xFF0000, + }; + for (string) |character| { + const vertical_offset = MARGIN + (row * font.HEIGHT); + const horizontal_offset = MARGIN + (column * font.WIDTH); + if (character == '\n') { + nextline(); + continue; + } else if (character == '\t') { + column += 4; + continue; + } else if (horizontal_offset > framebuffer.width - font.WIDTH - MARGIN) { + nextline(); + } + for (0..font.HEIGHT) |i| { + var offset = ((vertical_offset + i) * framebuffer.pitch) + (horizontal_offset * 4); + var mask: u8 = 1 << 7; + for (0..font.WIDTH) |_| { + if (font.characters[character][i] & mask != 0) { + @as(*u32, @ptrCast(@alignCast(framebuffer.address + offset))).* = @bitCast(foreground); + } else { + @as(*u32, @ptrCast(@alignCast(framebuffer.address + offset))).* = @bitCast(background); + } + mask >>= 1; + offset += 4; + } + } + column += 1; + } + return string.len; +} + +export var fb_request: limine.FramebufferRequest = .{}; + +pub fn init() void { + const response = fb_request.response orelse { + log.err("request not fufilled", .{}); + return; + }; + if (response.framebuffer_count < 1) { + log.info("none found", .{}); + } + framebuffer = response.framebuffers()[0]; +} diff --git a/kernel/src/font.zig b/kernel/src/font.zig new file mode 100644 index 0000000..8d0c59b --- /dev/null +++ b/kernel/src/font.zig @@ -0,0 +1,19 @@ +const std = @import("std"); + +const FONT = "./FONT.F16"; +pub const WIDTH = 8; +pub const HEIGHT = 16; + +pub const characters = characters: { + @setEvalBranchQuota(10000); + const file = @embedFile(FONT); + var character_map: [256][HEIGHT]u8 = undefined; + for (0..256) |index| { + var rows: [HEIGHT]u8 = undefined; + for (0..HEIGHT) |row| { + rows[row] = file[(index * HEIGHT) + row]; + } + character_map[index] = rows; + } + break :characters character_map; +}; diff --git a/kernel/src/log.zig b/kernel/src/log.zig new file mode 100755 index 0000000..054315d --- /dev/null +++ b/kernel/src/log.zig @@ -0,0 +1,39 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const dbg = @import("./dbg.zig"); +const srl = @import("./srl.zig"); +const fb = @import("./fb.zig"); + +const MAX = size: { + var max = 0; + const levels = @typeInfo(std.log.Level).Enum.fields; + for (0..levels.len) |i| { + if (levels[i].name.len > max) { + max = levels[i].name.len; + } + } + break :size max; +}; + +pub fn print( + comptime level: std.log.Level, + comptime scope: @TypeOf(.EnumLiteral), + comptime format: []const u8, + args: anytype, +) void { + const prefix = "[" ++ @tagName(level) ++ "]" ++ [1]u8{' '} ** (MAX - @tagName(level).len) ++ " " ++ "(" ++ switch (scope) { + .default => "krl", + else => @tagName(scope), + } ++ "): "; + const suffix = "\n"; + const log = prefix ++ format ++ suffix; + if (builtin.mode == .Debug) { + dbg.writer.context = level; + dbg.writer.print(log, args) catch {}; + } + srl.writer.context = level; + srl.writer.print(log, args) catch {}; + fb.writer.context = level; + fb.writer.print(log, args) catch {}; +} diff --git a/kernel/src/main.zig b/kernel/src/main.zig new file mode 100755 index 0000000..8b4e542 --- /dev/null +++ b/kernel/src/main.zig @@ -0,0 +1,48 @@ +const std = @import("std"); +const limine = @import("limine"); + +const arch = @import("arch"); +const srl = @import("./srl.zig"); +const fb = @import("./fb.zig"); +const mem = @import("./mem.zig"); +const cli = @import("./cli.zig"); + +pub const std_options = struct { + pub const logFn = @import("./log.zig").print; +}; + +pub const os = struct { + pub const heap = struct { + pub const page_allocator = mem.allocator; + }; +}; + +export var base_revision: limine.BaseRevision = .{ .revision = 1 }; + +export fn _start() callconv(.C) noreturn { + if (!base_revision.is_supported()) + std.debug.panic("Unsupported version", .{}); + + srl.init(); + fb.init(); + + std.log.info("booting", .{}); + arch.init(); + + std.log.info("starting", .{}); + std.log.debug("Something happened!", .{}); + std.log.info("Hello World!", .{}); + std.log.warn("Be warned!", .{}); + std.log.err("Something went wrong!", .{}); + // std.debug.panic("something really fucked up!", .{}); + cli.run(); + while (true) {} +} + +pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, return_address: ?usize) noreturn { + @setCold(true); + _ = return_address; + _ = stack_trace; + std.log.err("panic!\nmsg: {s}", .{message}); + while (true) {} +} diff --git a/kernel/src/mem.zig b/kernel/src/mem.zig new file mode 100644 index 0000000..33283fe --- /dev/null +++ b/kernel/src/mem.zig @@ -0,0 +1,10 @@ +const std = @import("std"); + +const allocator = std.mem.Allocator{ + .ptr = undefined, + .vtable = .{ + .alloc = undefined, + .resize = undefined, + .free = undefined, + }, +}; diff --git a/kernel/src/srl.zig b/kernel/src/srl.zig new file mode 100644 index 0000000..3c2edcb --- /dev/null +++ b/kernel/src/srl.zig @@ -0,0 +1,157 @@ +const std = @import("std"); +const srl = @import("arch").srl; + +const Interrupt_Enable_Register = packed struct(u8) { + transmitter_empty: bool = false, + data_available: bool = false, + receiver_status: bool = false, + modem_status: bool = false, + sleep_mode: bool = false, + low_power_mode: bool = false, + _: u2 = 0, + + fn transmit(self: Interrupt_Enable_Register) void { + srl.Port.COM2.out(@as(u8, @bitCast(self))); + } +}; + +const Line_Control_Register = packed struct(u8) { + length: CharacterLength = CharacterLength{}, + stop_bits: StopBits = .one, + parity: Parity = .none, + break_enable: bool = false, + divisor_latch_access: bool = false, + + const Parity = enum(u3) { + none = 0b000, + odd = 0b001, + even = 0b011, + mark = 0b101, + space = 0b111, + }; + + const StopBits = enum(u1) { + one = 0, + two = 1, + }; + + const CharacterLength = packed struct(u2) { + size: u2 = 8 - 5, + + pub fn from(comptime bits: u4) CharacterLength { + return CharacterLength{ .size = bits - 5 }; + } + }; + + fn transmit(self: Line_Control_Register) void { + srl.Port.COM4.out(@as(u8, @bitCast(self))); + } +}; + +const FIFO_Control_Register = packed struct(u8) { + enabled: bool = false, + clear_recieve: bool = false, + clear_transmit: bool = false, + dma_mode: u1 = 0, + _: u1 = 0, + large: bool = false, // 64 bytes + size: FIFOBufferSize = .byte, + + const FIFOBufferSize = enum(u2) { + byte = 0b00, // 1 byte + word = 0b01, // 2 bytes + double_word = 0b10, // 4 bytes + huge = 0b11, // 14 bytes + }; + + fn transmit(self: FIFO_Control_Register) void { + srl.Port.COM3.out(@as(u8, @bitCast(self))); + } +}; + +const Modem_Control_Register = packed struct(u8) { + recieve: bool = false, + transmit: bool = false, + aux_output1: bool = false, + aux_output2: bool = false, + loopback: bool = false, + autoflow: bool = false, + _: u2 = 0, + + fn transmit(self: Modem_Control_Register) void { + srl.Port.COM5.out(@as(u8, @bitCast(self))); + } +}; + +const Line_Status_Register = packed struct(u8) { + data_ready: bool, + overrun_error: bool, + parity_error: bool, + framing_error: bool, + break_interrupt: bool, + transmitter_empty: bool, + data_holding_empty: bool, + fifo_error: bool, + + fn read() Line_Status_Register { + return @as(Line_Status_Register, @bitCast(srl.Port.COM6.in(u8))); + } +}; + +pub fn set_speed(divisor: u16) void { + Line_Control_Register.transmit(.{ + .divisor_latch_access = true, + }); + srl.Port.COM1.out(@as(u8, @truncate(divisor))); + srl.Port.COM2.out(@as(u8, @truncate(divisor >> 8))); +} + +pub fn data_recieved() bool { + return Line_Status_Register.read().data_ready; +} + +pub fn transmitter_ready() bool { + return Line_Status_Register.read().transmitter_empty; +} + +pub fn read(comptime Type: type) Type { + while (!data_recieved()) {} + return srl.Port.COM1.in(Type); +} + +pub fn write(byte: u8) void { + while (!transmitter_ready()) {} + srl.Port.COM1.out(byte); +} + +pub var writer = std.io.Writer(std.log.Level, error{}, callback){ .context = std.log.Level.info }; +fn callback(_: std.log.Level, string: []const u8) error{}!usize { + for (string) |character| { + write(character); + } + return string.len; +} + +pub fn init() void { + Interrupt_Enable_Register.transmit(.{}); + + set_speed(1); + + Line_Control_Register.transmit(.{ + .length = Line_Control_Register.CharacterLength.from(@bitSizeOf(u8)), + .stop_bits = .one, + .parity = .none, + }); + + FIFO_Control_Register.transmit(.{ + .enabled = true, + .size = .byte, + .clear_recieve = true, + .clear_transmit = true, + }); + + Modem_Control_Register.transmit(.{ + .recieve = true, + .transmit = true, + }); +} diff --git a/kernel/src/x86.zig b/kernel/src/x86.zig new file mode 100644 index 0000000..2008681 --- /dev/null +++ b/kernel/src/x86.zig @@ -0,0 +1,22 @@ +// cpu +pub const int = @import("./x86/int.zig"); +pub const idt = @import("./x86/idt.zig"); + +// memory +pub const gdt = @import("./x86/gdt.zig"); +pub const pmm = @import("./x86/pmm.zig"); +pub const pgn = @import("./x86/pgn.zig"); + +// io +pub const io = @import("./x86/io.zig"); +pub const dbg = @import("./x86/dbg.zig"); +pub const srl = @import("./x86/srl.zig"); + +pub fn init() void { + int.disable(); + gdt.init(); + pmm.init(); + pgn.init(); + idt.init(); + int.enable(); +} diff --git a/kernel/src/x86/dbg.zig b/kernel/src/x86/dbg.zig new file mode 100644 index 0000000..ea99d65 --- /dev/null +++ b/kernel/src/x86/dbg.zig @@ -0,0 +1,7 @@ +const io = @import("./io.zig"); + +const DEBUG_PORT = 0xE9; + +pub fn out(data: anytype) void { + io.out(DEBUG_PORT, data); +} diff --git a/kernel/src/x86/gdt.zig b/kernel/src/x86/gdt.zig new file mode 100644 index 0000000..f6a68e2 --- /dev/null +++ b/kernel/src/x86/gdt.zig @@ -0,0 +1,203 @@ +const std = @import("std"); +const log = std.log.scoped(.gdt); + +const Table = enum(u1) { + gdt = 0, + ldt = 1, +}; + +const Type = enum(u2) { + null = 0, + code = 0b11, + data = 0b10, +}; + +const Flags = packed struct(u4) { + const Mode = enum(u2) { + bits16 = 0b00, // 16 bit + bits32 = 0b10, // 32 bit + bits64 = 0b01, // 64 bit + }; + const Granularity = enum(u1) { + byte = 0, // one byte + page = 1, // 4 kilobytes + }; + + _: u1 = 0, + mode: Mode = .bits32, + granularity: Granularity = .page, +}; + +const NullSegment = packed struct(u64) { + _: usize = 0, +}; + +const CodeSegment = packed struct(u64) { + const Access = packed struct(u8) { + accessed: bool = false, + readable: bool = true, + unprivilaged: bool = false, + kind: Type = .code, + level: u2, + present: bool = true, + }; + + limit_low: u16, + base_low: u24, + access: Access, + limit_high: u4, + flags: Flags, + base_high: u8, + + fn new(base: u32, limit: u20, access: Access, flags: Flags) CodeSegment { + return CodeSegment{ + .base_low = @truncate(base), + .base_high = @truncate(base >> 24), + .limit_low = @truncate(limit), + .limit_high = @truncate(limit >> 16), + .access = access, + .flags = flags, + }; + } +}; + +const DataSegment = packed struct(u64) { + const Access = packed struct(u8) { + const Direction = enum(u1) { + up = 0, + down = 1, + }; + accessed: bool = false, + writable: bool = true, + direction: Direction = .up, + kind: Type = .data, + level: u2, + present: bool = true, + }; + + limit_low: u16, + base_low: u24, + access: Access, + limit_high: u4, + flags: Flags, + base_high: u8, + + fn new(base: u32, limit: u20, access: Access, flags: Flags) DataSegment { + return .{ + .base_low = @truncate(base), + .base_high = @truncate(base >> 24), + .limit_low = @truncate(limit), + .limit_high = @truncate(limit >> 16), + .access = access, + .flags = flags, + }; + } +}; + +var entries = [_]u64{ + @bitCast( + NullSegment{}, + ), + @bitCast( + CodeSegment.new( + 0, + std.math.maxInt(u20), + .{ + .level = 0, + }, + .{ + .mode = .bits64, + }, + ), + ), + @bitCast( + DataSegment.new( + 0, + std.math.maxInt(u20), + .{ + .level = 0, + }, + .{}, + ), + ), + @bitCast( + CodeSegment.new( + 0, + std.math.maxInt(u20), + .{ + .level = 3, + }, + .{ + .mode = .bits64, + }, + ), + ), + @bitCast( + DataSegment.new( + 0, + std.math.maxInt(u20), + .{ + .level = 3, + }, + .{}, + ), + ), +}; + +pub const Selector = packed struct(u16) { + level: u2, + table: Table, + index: u13, + + pub const KERNEL_CODE = Selector{ + .index = 1, + .level = 0, + .table = .gdt, + }; + pub const KERNEL_DATA = Selector{ + .index = 2, + .level = 0, + .table = .gdt, + }; +}; + +const Descriptor = packed struct(u80) { + size: u16, + base: u64, + + fn new(base: u64, size: u16) Descriptor { + return .{ + .base = base, + .size = size - 1, + }; + } + + fn load(self: Descriptor) void { + asm volatile ("lgdt (%%rax)" + : + : [ptr] "{rax}" (&self), + ); + log.debug("loaded", .{}); + asm volatile ( + \\push $0x08 + \\leaq reload(%rip), %%rax + \\push %%rax + \\lretq + \\reload: + \\mov $0x10, %%ax + \\mov %%ax, %%ds + \\mov %%ax, %%ss + \\mov %%ax, %%es + \\mov %%ax, %%fs + \\mov %%ax, %%gs + ); + log.debug("flushed segment registers", .{}); + } +}; + +var descriptor: Descriptor = Descriptor.new(undefined, @sizeOf(@TypeOf(entries))); + +pub fn init() void { + descriptor.base = @intFromPtr(&entries); + descriptor.load(); +} diff --git a/kernel/src/x86/idt.zig b/kernel/src/x86/idt.zig new file mode 100644 index 0000000..1762a8e --- /dev/null +++ b/kernel/src/x86/idt.zig @@ -0,0 +1,91 @@ +const std = @import("std"); +const log = std.log.scoped(.idt); +const int = @import("./int.zig"); +const gdt = @import("./gdt.zig"); + +const Gate = packed struct(u128) { + const Type = enum(u4) { + interrupt = 0xE, + trap = 0xF, + }; + + address_low: u16, + segment: gdt.Selector = gdt.Selector.KERNEL_CODE, + ist_offset: u3 = 0, + _: u5 = 0, + kind: Type, + _2: u1 = 0, + level: u2 = 0, + present: bool = true, + address_high: u48, + _3: u32 = 0, + + fn new(handler: usize, kind: Type) Gate { + return .{ + .address_low = @truncate(handler), + .address_high = @truncate(handler >> 16), + .kind = kind, + }; + } +}; + +var entries: [256]Gate = undefined; + +const Descriptor = packed struct(u80) { + size: u16, + base: u64, + + fn new(base: u64, size: u16) Descriptor { + return .{ + .base = base, + .size = size - 1, + }; + } + + fn load(self: Descriptor) void { + asm volatile ("lidt (%%rax)" + : + : [ptr] "{rax}" (&self), + ); + } +}; + +var descriptor: Descriptor = Descriptor.new(undefined, @sizeOf(@TypeOf(entries))); + +pub fn init() void { + descriptor.base = @intFromPtr(&entries); + + entries[0] = Gate.new(@intFromPtr(&int.division_error), .interrupt); + entries[1] = Gate.new(@intFromPtr(&int.debug), .interrupt); + entries[2] = Gate.new(@intFromPtr(&int.nonmaskable_interrupt), .interrupt); + entries[3] = Gate.new(@intFromPtr(&int.breakpoint), .interrupt); + entries[4] = Gate.new(@intFromPtr(&int.overflow), .interrupt); + entries[5] = Gate.new(@intFromPtr(&int.bound_range_exceeded), .interrupt); + entries[6] = Gate.new(@intFromPtr(&int.invalid_opcode), .interrupt); + entries[7] = Gate.new(@intFromPtr(&int.device_not_available), .interrupt); + entries[8] = Gate.new(@intFromPtr(&int.double_fault), .interrupt); + entries[9] = Gate.new(@intFromPtr(&int.unhandled), .interrupt); + entries[10] = Gate.new(@intFromPtr(&int.invalid_tss), .interrupt); + entries[11] = Gate.new(@intFromPtr(&int.segment_not_present), .interrupt); + entries[12] = Gate.new(@intFromPtr(&int.stack_segment_fault), .interrupt); + entries[13] = Gate.new(@intFromPtr(&int.general_protection_fault), .interrupt); + entries[14] = Gate.new(@intFromPtr(&int.page_fault), .interrupt); + entries[15] = Gate.new(@intFromPtr(&int.unhandled), .interrupt); + entries[16] = Gate.new(@intFromPtr(&int.floating_point_exception), .interrupt); + entries[17] = Gate.new(@intFromPtr(&int.alignment_check), .interrupt); + entries[18] = Gate.new(@intFromPtr(&int.machine_check), .interrupt); + entries[19] = Gate.new(@intFromPtr(&int.simd_floating_point_exception), .interrupt); + entries[20] = Gate.new(@intFromPtr(&int.virtualization_exception), .interrupt); + entries[21] = Gate.new(@intFromPtr(&int.control_protection_exception), .interrupt); + inline for (22..28) |i| { + entries[i] = Gate.new(@intFromPtr(&int.unhandled), .interrupt); + } + entries[28] = Gate.new(@intFromPtr(&int.hypervisor_injection_exception), .interrupt); + entries[29] = Gate.new(@intFromPtr(&int.vmm_communication_exception), .interrupt); + entries[30] = Gate.new(@intFromPtr(&int.security_exception), .interrupt); + inline for (31..256) |i| { + entries[i] = Gate.new(@intFromPtr(&int.unhandled), .interrupt); + } + + descriptor.load(); +} diff --git a/kernel/src/x86/int.zig b/kernel/src/x86/int.zig new file mode 100644 index 0000000..bc5536d --- /dev/null +++ b/kernel/src/x86/int.zig @@ -0,0 +1,108 @@ +const std = @import("std"); +const log = std.log.scoped(.int); + +pub fn disable() void { + asm volatile ("cli"); + log.debug("disabled", .{}); +} + +pub fn enable() void { + asm volatile ("sti"); + log.debug("enabled", .{}); +} + +pub fn unhandled() callconv(.Interrupt) void { + log.debug("unimplemented", .{}); +} + +pub fn division_error() callconv(.Interrupt) void { + log.err("divison error", .{}); +} + +pub fn debug() callconv(.Interrupt) void { + log.debug("debug", .{}); +} + +pub fn nonmaskable_interrupt() callconv(.Interrupt) void { + log.err("non-maskable interrupt", .{}); +} + +pub fn breakpoint() callconv(.Interrupt) void { + log.debug("breakpoint", .{}); +} + +pub fn overflow() callconv(.Interrupt) void { + log.err("overflow", .{}); +} + +pub fn bound_range_exceeded() callconv(.Interrupt) void { + log.err("bound range exceeded", .{}); +} + +pub fn invalid_opcode() callconv(.Interrupt) void { + log.err("invalid opcode", .{}); +} + +pub fn device_not_available() callconv(.Interrupt) void { + log.err("device not available", .{}); +} + +pub fn double_fault(_: u64) callconv(.Interrupt) void { + log.err("double fault", .{}); +} + +pub fn invalid_tss(code: u64) callconv(.Interrupt) void { + log.err("invalid tss: {X}", .{code}); +} + +pub fn segment_not_present(code: u64) callconv(.Interrupt) void { + log.err("segment not present: {X}", .{code}); +} + +pub fn stack_segment_fault(code: u64) callconv(.Interrupt) void { + log.err("stack segment fault: {X}", .{code}); +} + +pub fn general_protection_fault(code: u64) callconv(.Interrupt) void { + log.err("general protection fault: {X}", .{code}); +} + +pub fn page_fault(code: u64) callconv(.Interrupt) void { + log.err("page fault: {X}", .{code}); +} + +pub fn floating_point_exception() callconv(.Interrupt) void { + log.err("floating point exception", .{}); +} + +pub fn alignment_check(code: u64) callconv(.Interrupt) void { + log.err("alignment check: {X}", .{code}); +} + +pub fn machine_check() callconv(.Interrupt) void { + log.err("machine check", .{}); +} + +pub fn simd_floating_point_exception() callconv(.Interrupt) void { + log.err("simd floating point exception", .{}); +} + +pub fn virtualization_exception() callconv(.Interrupt) void { + log.err("virtualization exception", .{}); +} + +pub fn control_protection_exception(code: u64) callconv(.Interrupt) void { + log.err("control protection exception: {}", .{code}); +} + +pub fn hypervisor_injection_exception() callconv(.Interrupt) void { + log.err("hypervisor injection exception", .{}); +} + +pub fn vmm_communication_exception(code: u64) callconv(.Interrupt) void { + log.err("vmm communication exception: {}", .{code}); +} + +pub fn security_exception(code: u64) callconv(.Interrupt) void { + log.err("security exception: {}", .{code}); +} diff --git a/kernel/src/x86/io.zig b/kernel/src/x86/io.zig new file mode 100644 index 0000000..bc6c51d --- /dev/null +++ b/kernel/src/x86/io.zig @@ -0,0 +1,44 @@ +const WAIT_PORT: u16 = 0x80; + +pub inline fn in(port: u16, comptime Type: type) Type { + return switch (Type) { + u8 => asm volatile ("inb %[port], %[result]" + : [result] "={al}" (-> Type), + : [port] "N{dx}" (port), + ), + u16 => asm volatile ("inw %[port], %[result]" + : [result] "={ax}" (-> Type), + : [port] "N{dx}" (port), + ), + u32 => asm volatile ("inl %[port], %[result]" + : [result] "={eax}" (-> Type), + : [port] "N{dx}" (port), + ), + else => @compileError("Invalid data type. Only u8, u16, and u32 are allowed. Found: " ++ @typeName(@TypeOf(Type))), + }; +} + +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 => @compileError("Invalid data type. Only u8, u16, and u32 are allowed. Found: " ++ @typeName(@TypeOf(data))), + } +} + +pub inline fn wait() void { + out(WAIT_PORT, @as(u8, 0)); +} diff --git a/kernel/src/x86/lvl.zig b/kernel/src/x86/lvl.zig new file mode 100644 index 0000000..e69de29 diff --git a/kernel/src/x86/pgn.zig b/kernel/src/x86/pgn.zig new file mode 100644 index 0000000..e17c44e --- /dev/null +++ b/kernel/src/x86/pgn.zig @@ -0,0 +1,60 @@ +const std = @import("std"); +const pmm = @import("./pmm.zig"); + +pub const PAGE_SIZE: usize = 4096; + +const RECURSE: u9 = std.math.maxInt(u9); +const KERNEL: u9 = RECURSE - 1; + +const Entry = packed struct(u64) { + const Access = enum(u1) { + supervisor = 0, + user = 1, + }; + + present: bool = true, + writable: bool = true, + access: Access = .supervisor, + write_through: bool = false, + cache_disabled: bool = false, + accessed: bool = false, + dirty: bool = false, + _: u2 = 0, + unused: u3 = 0, // free to use by us + pte: u12, + pde: u9, + pdpte: u9, + pml4e: u9, + unused2: u4 = 0, + execute_disable: bool = false, +}; + +const Table = packed struct(u64) { + _: u3 = 0, + write_through: bool = false, + cache_disabled: bool = false, + _2: u7 = 0, + address: u40, + _3: u12 = 0, +}; + +pub fn init() void { + // asm volatile ("mov %%rax, %%cr3" + // : + // : [pdpt] "{rax}" (&table), + // ); +} + +fn page_table(lvl4: u9, lvl3: u9, lvl2: u9, lvl1: u9, offset: u12) *Entry { + return @ptrFromInt((lvl4 << 39) | (lvl3 << 30) | (lvl2 << 21) | (lvl1 << 12) | offset); +} + +pub fn map(virtual_address: usize, physical_address: usize) void { + const pml4e: u9 = virtual_address >> 39; + const pdpte: u9 = virtual_address >> 30; + const pde: u9 = virtual_address >> 21; + const pte: u9 = virtual_address >> 12; + + const pt = page_table(RECURSE, RECURSE, RECURSE, RECURSE); +} + diff --git a/kernel/src/x86/pic.zig b/kernel/src/x86/pic.zig new file mode 100644 index 0000000..6c08045 --- /dev/null +++ b/kernel/src/x86/pic.zig @@ -0,0 +1,205 @@ +const io = @import("./io.zig"); + +const ICW1 = packed struct(u8) { + ICW4_needed: bool = false, + setup: Controller_Setup = .cascading, + call_address_interval: Call_Address_Interval = .EVERY8, + trigger_mode: Trigger_Mode = .edge, + _: u4 = 1, + + const Trigger_Mode = enum(u1) { + edge = 0, + level = 1, + }; + + const Call_Address_Interval = enum(u1) { + EVERY4 = 1, + EVERY8 = 0, + }; + + const Controller_Setup = enum(u1) { + single = 1, + cascading = 0, + }; + + fn transmit(self: ICW1) void { + Master.command.out(@as(u8, @bitCast(self))); + Slave.command.out(@as(u8, @bitCast(self))); + } +}; + +const ICW2 = struct { + master_ivt_offset: ?u8 = null, + slave_ivt_offset: ?u8 = null, + + fn transmit(self: ICW2) void { + if (self.master_ivt_offset) |offset| { + Master.data.out(offset); + } + if (self.slave_ivt_offset) |offset| { + Slave.data.out(offset); + } + } +}; + +const ICW3 = enum(u3) { + IRQ0_has_slave = 0, + IRQ1_has_slave = 1, + IRQ2_has_slave = 2, + IRQ3_has_slave = 3, + IRQ4_has_slave = 4, + IRQ5_has_slave = 5, + IRQ6_has_slave = 6, + IRQ7_has_slave = 7, + + fn transmit(self: ICW3) void { + Master.data.out(@as(u8, 1) << @intFromEnum(self)); + Slave.data.out(@as(u8, @intFromEnum(self))); + } +}; + +const ICW4 = packed struct(u8) { + operating_mode: Operating_Mode = .mcs, + auto_end_of_interrupt: bool = false, + buffer_mode: Buffer_Mode = .neither, + special_fully_nested_mode: bool = false, + _: u3 = 0, + + const Buffer_Mode = enum(u2) { + master = 0b11, + slave = 0b10, + neither = 0b00, + }; + + const Operating_Mode = enum(u1) { + mcs = 0, + x86 = 1, + }; + + fn transmit(self: ICW4) void { + Master.data.out(@as(u8, @bitCast(self))); + Slave.data.out(@as(u8, @bitCast(self))); + } +}; + +const OCW2 = packed struct(u8) { + interrupt_level: u3 = 0, + _: u2 = 0, + end_of_interrupt_mode: End_Of_Interrupt_Mode = .non_specific_command, + + const End_Of_Interrupt_Mode = enum(u3) { + non_specific_command = 0b001, + specific_command = 0b011, + rotate_on_non_specific_command = 0b101, + rotate_on_specific_command = 0b111, + rotate_in_automatic_mode_set = 0b100, + rotate_in_automatic_mode_clear = 0b000, + set_priority_command = 0b110, + no_operation = 0b010, + }; +}; + +const OCW3 = packed struct(u8) {}; + +const Master = struct { + const Port = enum(u16) { + command = 0x20, + data = 0x21, + + fn in(Type: type) Type { + return io.in(@intFromEnum(.data), Type); + } + + fn out(port: Port, data: anytype) void { + io.out(@intFromEnum(port), data); + io.wait(); + } + }; + + const IRQs = packed struct(u8) { + pit: bool = false, + keyboard: bool = false, + slave: bool = false, + COM2_and_COM4: bool = false, + COM1_and_COM3: bool = false, + LPT2: bool = false, + floppy: bool = false, + LPT1: bool = false, + }; + + fn enable(irqs: IRQs) void { + Port.data.out(@as(u8, @bitCast(irqs))); + } + + fn end_of_interrupt() void { + Port.command.out(@as(u8, @bitCast(OCW2{}))); + } + + fn disable() void { + Port.data.out(@as(u8, @bitCast(IRQs))); + } +}; + +const Slave = enum(u16) { + const Port = enum(u16) { + command = 0xA0, + data = 0xA1, + + fn in(Type: type) Type { + return io.in(@intFromEnum(.data), Type); + } + + fn out(port: Port, data: anytype) void { + io.out(@intFromEnum(port), data); + io.wait(); + } + }; + + const IRQs = packed struct(u8) { + rtc: bool = false, + _: u3 = 0, + mouse: bool = false, + math_coprocessor: bool = false, + disk1: bool = false, + disk2: bool = false, + }; + + fn enable(irqs: IRQs) void { + Port.data.out(@as(u8, @bitCast(irqs))); + } + + fn end_of_interrupt() void { + Port.command.out(@as(u8, @bitCast(OCW2{}))); + } + + fn disable() void { + Port.data.out(@as(u8, @bitCast(IRQs))); + } +}; + +pub fn end_of_interrupt() void { + Master.end_of_interrupt(); + Slave.end_of_interrupt(); +} + +pub fn disable() void { + Master.disable(); + Slave.disable(); +} + +pub fn init() void { + disable(); + + ICW1.transmit(.{ + .ICW4_needed = true, + }); + + // add offsets here + ICW2.transmit(.{}); + + ICW3.transmit(.IRQ2_has_slave); + + ICW4.transmit(.{ + .operating_mode = .x86, + }); +} diff --git a/kernel/src/x86/pmm.zig b/kernel/src/x86/pmm.zig new file mode 100644 index 0000000..9900d10 --- /dev/null +++ b/kernel/src/x86/pmm.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +const log = std.log.scoped(.mem); +const limine = @import("limine"); +const pgn = @import("./pgn.zig"); + +var stack: [*]usize = undefined; +var stack_index: usize = 0; + +pub export var directmap_request: limine.HhdmRequest = .{}; +export var memmap_request: limine.MemoryMapRequest = .{}; + +pub fn init() void { + const directmap = directmap_request.response orelse { + std.debug.panic("no directmap found", .{}); + return; + }; + stack = @ptrFromInt(directmap.offset); + + const memory_map = memmap_request.response orelse { + std.debug.panic("no memmap found", .{}); + return; + }; + for (0..memory_map.entry_count) |i| { + const entry = memory_map.entries_ptr[i]; + switch (entry.kind) { + .usable, .bootloader_reclaimable, .acpi_reclaimable => { + var j: usize = 0; + while (j < entry.length) : (j += pgn.PAGE_SIZE) { + free(entry.base + j); + } + }, + else => {}, + } + } + log.info("{} blocks free", .{stack_index}); +} + +pub fn alloc() std.mem.Allocator.Error!usize { + if (stack_index == 0) + return error.OutOfMemory; + stack_index -= 1; + const address = stack[stack_index]; + return address; +} + +pub fn free(address: usize) void { + stack[stack_index] = std.mem.alignBackward(usize, address, pgn.PAGE_SIZE); + stack_index += 1; +} diff --git a/kernel/src/x86/srl.zig b/kernel/src/x86/srl.zig new file mode 100644 index 0000000..f71112d --- /dev/null +++ b/kernel/src/x86/srl.zig @@ -0,0 +1,21 @@ +const io = @import("./io.zig"); + +pub const Port = enum(u16) { + const BASE = 0x3F8; + COM1 = BASE, + COM2 = BASE + 1, + COM3 = BASE + 2, + COM4 = BASE + 3, + COM5 = BASE + 4, + COM6 = BASE + 5, + COM7 = BASE + 6, + COM8 = BASE + 7, + + pub fn in(self: Port, comptime Type: type) Type { + return io.in(@intFromEnum(self), Type); + } + + pub fn out(self: Port, data: anytype) void { + io.out(@intFromEnum(self), data); + } +}; diff --git a/limine b/limine new file mode 160000 index 0000000..749a320 --- /dev/null +++ b/limine @@ -0,0 +1 @@ +Subproject commit 749a32066cd4aaf03ca6e352a918dc4992b9fa5c diff --git a/limine.cfg b/limine.cfg new file mode 100644 index 0000000..820a7a4 --- /dev/null +++ b/limine.cfg @@ -0,0 +1,7 @@ +TIMEOUT=0 + +:mos + PROTOCOL=limine + KASLR=no + KERNEL_PATH=boot:///boot/kernel + diff --git a/qemu.sh b/qemu.sh new file mode 100755 index 0000000..932dbf5 --- /dev/null +++ b/qemu.sh @@ -0,0 +1,13 @@ +#!/bin/sh +clear + +FLAGS="\ +-m 64M \ +-display none \ +-serial stdio" + +if [[ $1 == "debug" ]]; then + FLAGS="-s -S -no-reboot -d int $FLAGS" +fi + +qemu-system-x86_64 $FLAGS -cdrom ./zig-out/mos.iso