diff --git a/build.zig b/build.zig index 7886d24..0890f00 100644 --- a/build.zig +++ b/build.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const Builder = std.build.Builder; +const LibExeObjStep = std.build.LibExeObjStep; const Step = std.build.Step; const Target = std.build.Target; const fs = std.fs; @@ -16,17 +17,18 @@ pub fn build(b: *Builder) !void { }; const target_str = switch (target.getArch()) { - builtin.Arch.i386 => "x86", + .i386 => "x86", else => unreachable, }; + const debug = b.option(bool, "debug", "build with debug symbols / make qemu wait for a debug connection") orelse false; const rt_test = b.option(bool, "rt-test", "enable/disable runtime testing") orelse false; const main_src = "src/kernel/kmain.zig"; + const exec = b.addExecutable("pluto", main_src); - exec.setMainPkgPath("."); - const const_path = try fs.path.join(b.allocator, [_][]const u8{ "src/kernel/arch/", target_str, "/constants.zig" }); - exec.addPackagePath("constants", const_path); + const constants_path = try fs.path.join(b.allocator, [_][]const u8{ "src/kernel/arch", target_str, "constants.zig" }); + exec.addPackagePath("constants", constants_path); exec.addBuildOption(bool, "rt_test", rt_test); exec.setLinkerScriptPath("link.ld"); exec.setTheTarget(target); @@ -35,12 +37,12 @@ pub fn build(b: *Builder) !void { exec.addAssemblyFile("src/kernel/arch/x86/irq_asm.s"); exec.addAssemblyFile("src/kernel/arch/x86/isr_asm.s"); }, - else => {}, + else => unreachable, } - const iso_path = fs.path.join(b.allocator, [_][]const u8{ b.exe_dir, "pluto.iso" }) catch unreachable; - const grub_build_path = fs.path.join(b.allocator, [_][]const u8{ b.exe_dir, "iso", "boot" }) catch unreachable; - const iso_dir_path = fs.path.join(b.allocator, [_][]const u8{ b.exe_dir, "iso" }) catch unreachable; + const iso_path = try fs.path.join(b.allocator, [_][]const u8{ b.exe_dir, "pluto.iso" }); + const grub_build_path = try fs.path.join(b.allocator, [_][]const u8{ b.exe_dir, "iso", "boot" }); + const iso_dir_path = try fs.path.join(b.allocator, [_][]const u8{ b.exe_dir, "iso" }); const mkdir_cmd = b.addSystemCommand([_][]const u8{ "mkdir", "-p", fs.path.dirname(grub_build_path).? }); @@ -59,7 +61,10 @@ pub fn build(b: *Builder) !void { b.default_step.dependOn(&iso_cmd.step); const run_step = b.step("run", "Run with qemu"); - const qemu_bin = if (target.getArch() == builtin.Arch.i386) "qemu-system-i386" else unreachable; + const qemu_bin = switch (target.getArch()) { + .i386 => "qemu-system-i386", + else => unreachable, + }; const qemu_cmd = b.addSystemCommand([_][]const u8{ qemu_bin, "-cdrom", @@ -69,10 +74,15 @@ pub fn build(b: *Builder) !void { "-serial", "stdio", }); - if (debug) + + if (debug) { qemu_cmd.addArgs([_][]const u8{ "-s", "-S" }); - if (rt_test) + } + + if (rt_test) { qemu_cmd.addArgs([_][]const u8{ "-display", "none" }); + } + run_step.dependOn(&qemu_cmd.step); qemu_cmd.step.dependOn(&iso_cmd.step); @@ -81,15 +91,24 @@ pub fn build(b: *Builder) !void { const script = b.addSystemCommand([_][]const u8{ "python3", "test/rt-test.py", "x86", b.zig_exe }); test_step.dependOn(&script.step); } else { - inline for ([_]Mode{ Mode.Debug, Mode.ReleaseFast, Mode.ReleaseSafe, Mode.ReleaseSmall }) |test_mode| { - const mode_str = comptime modeToString(test_mode); - const unit_tests = b.addTest("test/unittests/test_all.zig"); + const mock_path = "\"" ++ "../../test/mock/kernel/" ++ "\""; + const arch_mock_path = "\"" ++ "../../../../test/mock/kernel/" ++ "\""; + const modes = [_]Mode{ Mode.Debug, Mode.ReleaseFast, Mode.ReleaseSafe, Mode.ReleaseSmall }; + inline for (modes) |test_mode| { + const mode_str = switch (test_mode) { + Mode.Debug => "debug", + Mode.ReleaseFast => "release-fast", + Mode.ReleaseSafe => "release-safe", + Mode.ReleaseSmall => "release-small", + }; + const unit_tests = b.addTest(main_src); unit_tests.setBuildMode(test_mode); unit_tests.setMainPkgPath("."); unit_tests.setNamePrefix(mode_str ++ " - "); - unit_tests.addPackagePath("mocking", "test/mock/kernel/mocking.zig"); - unit_tests.addPackagePath("constants", const_path); + unit_tests.addPackagePath("constants", constants_path); unit_tests.addBuildOption(bool, "rt_test", rt_test); + unit_tests.addBuildOption([]const u8, "mock_path", mock_path); + unit_tests.addBuildOption([]const u8, "arch_mock_path", arch_mock_path); test_step.dependOn(&unit_tests.step); } } @@ -107,12 +126,3 @@ pub fn build(b: *Builder) !void { }); debug_step.dependOn(&debug_cmd.step); } - -fn modeToString(comptime mode: Mode) []const u8 { - return switch (mode) { - Mode.Debug => "debug", - Mode.ReleaseFast => "release-fast", - Mode.ReleaseSafe => "release-safe", - Mode.ReleaseSmall => "release-small", - }; -} diff --git a/src/kernel/arch.zig b/src/kernel/arch.zig index d8d6781..fcf6d8f 100644 --- a/src/kernel/arch.zig +++ b/src/kernel/arch.zig @@ -1,6 +1,8 @@ const builtin = @import("builtin"); +const is_test = builtin.is_test; +const build_options = @import("build_options"); -pub const internals = if (builtin.is_test) @import("mocking").arch else switch (builtin.arch) { - builtin.Arch.i386 => @import("arch/x86/arch.zig"), +pub const internals = if (is_test) @import(build_options.mock_path ++ "arch_mock.zig") else switch (builtin.arch) { + .i386 => @import("arch/x86/arch.zig"), else => unreachable, }; diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index a4e3eab..b07b007 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -1,17 +1,19 @@ -// Zig version: 0.4.0 - const std = @import("std"); +const Allocator = std.mem.Allocator; const builtin = @import("builtin"); const gdt = @import("gdt.zig"); const idt = @import("idt.zig"); const irq = @import("irq.zig"); const isr = @import("isr.zig"); -const log = @import("../../log.zig"); const pit = @import("pit.zig"); const paging = @import("paging.zig"); -const MemProfile = @import("../../mem.zig").MemProfile; const syscalls = @import("syscalls.zig"); +const mem = @import("../../mem.zig"); +const log = @import("../../log.zig"); +const MemProfile = mem.MemProfile; +/// The interrupt context that is given to a interrupt handler. It contains most of the registers +/// and the interrupt number and error code (if there is one). pub const InterruptContext = struct { // Extra segments gs: u32, @@ -44,29 +46,7 @@ pub const InterruptContext = struct { }; /// -/// Initialise the architecture -/// -pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator, comptime options: type) void { - disableInterrupts(); - - gdt.init(); - idt.init(); - - isr.init(); - irq.init(); - - pit.init(); - - paging.init(mem_profile, allocator); - - syscalls.init(options); - - // Enable interrupts - enableInterrupts(); -} - -/// -/// Inline assembly to write to a given port with a byte of data. +/// Assembly to write to a given port with a byte of data. /// /// Arguments: /// IN port: u16 - The port to write to. @@ -81,12 +61,12 @@ pub fn outb(port: u16, data: u8) void { } /// -/// Inline assembly that reads data from a given port and returns its value. +/// Assembly that reads data from a given port and returns its value. /// /// Arguments: /// IN port: u16 - The port to read data from. /// -/// Return: +/// Return: u8 /// The data that the port returns. /// pub fn inb(port: u16) u8 { @@ -101,6 +81,7 @@ pub fn inb(port: u16) u8 { /// event being waited. /// pub fn ioWait() void { + // Port 0x80 is free to use outb(0x80, 0); } @@ -114,13 +95,22 @@ pub fn ioWait() void { /// pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void { // Load the GDT into the CPU - asm volatile ("lgdt (%%eax)" : : [gdt_ptr] "{eax}" (gdt_ptr)); + asm volatile ("lgdt (%%eax)" + : + : [gdt_ptr] "{eax}" (gdt_ptr) + ); + // Load the kernel data segment, index into the GDT - asm volatile ("mov %%bx, %%ds" : : [KERNEL_DATA_OFFSET] "{bx}" (gdt.KERNEL_DATA_OFFSET)); + asm volatile ("mov %%bx, %%ds" + : + : [KERNEL_DATA_OFFSET] "{bx}" (gdt.KERNEL_DATA_OFFSET) + ); + asm volatile ("mov %%bx, %%es"); asm volatile ("mov %%bx, %%fs"); asm volatile ("mov %%bx, %%gs"); asm volatile ("mov %%bx, %%ss"); + // Load the kernel code segment into the CS register asm volatile ( \\ljmp $0x08, $1f @@ -129,33 +119,61 @@ pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void { } /// -/// Load the TSS into the CPU. +/// Get the previously loaded GDT from the CPU. /// -pub fn ltr() void { - asm volatile ("ltr %%ax" : : [TSS_OFFSET] "{ax}" (gdt.TSS_OFFSET)); +/// Return: gdt.GdtPtr +/// The previously loaded GDT from the CPU. +/// +pub fn sgdt() gdt.GdtPtr { + var gdt_ptr: gdt.GdtPtr = gdt.GdtPtr{ .limit = 0, .base = 0 }; + asm volatile ("sgdt %[tab]" + : [tab] "=m" (gdt_ptr) + ); + return gdt_ptr; } +/// +/// Tell the CPU where the TSS is located in the GDT. +/// +/// Arguments: +/// IN offset: u16 - The offset in the GDT where the TSS segment is located. +/// +pub fn ltr(offset: u16) void { + asm volatile ("ltr %%ax" + : + : [offset] "{ax}" (offset) + ); +} + +/// /// Load the IDT into the CPU. +/// +/// Arguments: +/// IN idt_ptr: *const idt.IdtPtr - The address of the iDT. +/// pub fn lidt(idt_ptr: *const idt.IdtPtr) void { - asm volatile ("lidt (%%eax)" : : [idt_ptr] "{eax}" (idt_ptr)); + asm volatile ("lidt (%%eax)" + : + : [idt_ptr] "{eax}" (idt_ptr) + ); } /// -/// Enable interrupts +/// Enable interrupts. /// pub fn enableInterrupts() void { asm volatile ("sti"); } /// -/// Disable interrupts +/// Disable interrupts. /// pub fn disableInterrupts() void { asm volatile ("cli"); } /// -/// Halt the CPU, but interrupts will still be called +/// Halt the CPU, but interrupts will still be called. /// pub fn halt() void { asm volatile ("hlt"); @@ -173,7 +191,7 @@ pub fn spinWait() noreturn { } /// -/// Halt the kernel. +/// Halt the kernel. No interrupts will be handled. /// pub fn haltNoInterrupts() noreturn { while (true) { @@ -183,12 +201,29 @@ pub fn haltNoInterrupts() noreturn { } /// -/// Register an interrupt handler. The interrupt number should be the arch-specific number. +/// Initialise the architecture /// /// Arguments: -/// IN int: u16 - The arch-specific interrupt number to register for. -/// IN handler: fn (ctx: *InterruptContext) void - The handler to assign to the interrupt. +/// IN mem_profile: *const MemProfile - The memory profile of the computer. Used to set up +/// paging. +/// IN allocator: *Allocator - The allocator use to handle memory. +/// IN comptime options: type - The build options that is passed to the kernel to be +/// used for run time testing. /// -pub fn registerInterruptHandler(int: u16, handler: fn (ctx: *InterruptContext) void) void { - irq.registerIrq(int, handler); +pub fn init(mem_profile: *const MemProfile, allocator: *Allocator, comptime options: type) void { + disableInterrupts(); + + gdt.init(); + idt.init(); + + isr.init(); + irq.init(); + + pit.init(); + + paging.init(mem_profile, allocator); + + syscalls.init(options); + + enableInterrupts(); } diff --git a/src/kernel/arch/x86/boot.zig b/src/kernel/arch/x86/boot.zig index c0d61de..96de75f 100644 --- a/src/kernel/arch/x86/boot.zig +++ b/src/kernel/arch/x86/boot.zig @@ -1,5 +1,12 @@ const constants = @import("constants"); +/// The multiboot header +const MultiBoot = packed struct { + magic: i32, + flags: i32, + checksum: i32, +}; + const ALIGN = 1 << 0; const MEMINFO = 1 << 1; const MAGIC = 0x1BADB002; @@ -9,14 +16,6 @@ const KERNEL_PAGE_NUMBER = constants.KERNEL_ADDR_OFFSET >> 22; // The number of pages occupied by the kernel, will need to be increased as we add a heap etc. const KERNEL_NUM_PAGES = 1; -extern fn kmain() void; - -const MultiBoot = packed struct { - magic: i32, - flags: i32, - checksum: i32, -}; - export var multiboot align(4) linksection(".rodata.boot") = MultiBoot{ .magic = MAGIC, .flags = FLAGS, @@ -65,19 +64,23 @@ export var boot_page_directory: [1024]u32 align(4096) linksection(".rodata.boot" export var kernel_stack: [16 * 1024]u8 align(16) linksection(".bss.stack") = undefined; +extern fn kmain() void; + export nakedcc fn _start() align(16) linksection(".text.boot") noreturn { - // Seth the page directory to the boot directory + // Set the page directory to the boot directory asm volatile ( \\.extern boot_page_directory \\mov $boot_page_directory, %%ecx \\mov %%ecx, %%cr3 ); + // Enable 4 MiB pages asm volatile ( \\mov %%cr4, %%ecx \\or $0x00000010, %%ecx \\mov %%ecx, %%cr4 ); + // Enable paging asm volatile ( \\mov %%cr0, %%ecx @@ -91,12 +94,14 @@ export nakedcc fn _start() align(16) linksection(".text.boot") noreturn { export nakedcc fn start_higher_half() noreturn { // Invalidate the page for the first 4MiB as it's no longer needed asm volatile ("invlpg (0)"); + // Setup the stack asm volatile ( \\.extern KERNEL_STACK_END \\mov $KERNEL_STACK_END, %%esp \\mov %%esp, %%ebp ); + // Push the bootloader magic number and multiboot header address with virtual offset asm volatile ( \\.extern KERNEL_ADDR_OFFSET diff --git a/src/kernel/arch/x86/constants.zig b/src/kernel/arch/x86/constants.zig index bdf088e..16e6b59 100644 --- a/src/kernel/arch/x86/constants.zig +++ b/src/kernel/arch/x86/constants.zig @@ -1 +1,2 @@ +/// The virtual address where the kernel will be loaded. This is at 3GB. pub const KERNEL_ADDR_OFFSET = 0xC0000000; diff --git a/src/kernel/arch/x86/gdt.zig b/src/kernel/arch/x86/gdt.zig index 59dd024..01ed3f9 100644 --- a/src/kernel/arch/x86/gdt.zig +++ b/src/kernel/arch/x86/gdt.zig @@ -1,77 +1,65 @@ -// Zig version: 0.4.0 +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const builtin = @import("builtin"); +const is_test = builtin.is_test; -const arch = @import("arch.zig"); -const log = @import("../../log.zig"); +const build_options = @import("build_options"); +const arch = if (is_test) @import(build_options.arch_mock_path ++ "arch_mock.zig") else @import("arch.zig"); +const log = if (is_test) @import(build_options.arch_mock_path ++ "log_mock.zig") else @import("../../log.zig"); -const NUMBER_OF_ENTRIES: u16 = 0x06; -const TABLE_SIZE: u16 = @sizeOf(GdtEntry) * NUMBER_OF_ENTRIES - 1; +/// The access bits for a GDT entry. +const AccessBits = packed struct { + /// Whether the segment has been access. This shouldn't be set as it is set by the CPU when the + /// segment is accessed. + accessed: u1, -// The indexes into the GDT where each segment resides. + /// For code segments, when set allows the code segment to be readable. Code segments are + /// always executable. For data segments, when set allows the data segment to be writeable. + /// Data segments are always readable. + read_write: u1, -/// The index of the NULL GDT entry. -const NULL_INDEX: u16 = 0x00; + /// For code segments, when set allows this code segments to be executed from a equal or lower + /// privilege level. The privilege bits represent the highest privilege level that is allowed + /// to execute this segment. If not set, then the code segment can only be executed from the + /// same ring level specified in the privilege level bits. For data segments, when set the data + /// segment grows downwards. When not set, the data segment grows upwards. So for both code and + /// data segments, this shouldn't be set. + direction_conforming: u1, -/// The index of the kernel code GDT entry. -const KERNEL_CODE_INDEX: u16 = 0x01; + /// When set, the segment can be executed, a code segments. When not set, the segment can't be + /// executed, data segment. + executable: u1, -/// The index of the kernel data GDT entry. -const KERNEL_DATA_INDEX: u16 = 0x02; + /// Should be set for code and data segments, but not set for TSS. + descriptor: u1, -/// The index of the user code GDT entry. -const USER_CODE_INDEX: u16 = 0x03; + /// Privilege/ring level. The kernel level is level 3, the highest privilege. The user level is + /// level 0, the lowest privilege. + privilege: u2, -/// The index of the user data GDT entry. -const USER_DATA_INDEX: u16 = 0x04; + /// Whether the segment is present. This must be set for all valid selectors, not the null + /// segment. + present: u1, +}; -/// The index of the task state segment GDT entry. -const TSS_INDEX: u16 = 0x05; +/// The flag bits for a GDT entry. +const FlagBits = packed struct { + /// The lowest bits must be 0 as this is reserved for future use. + reserved_zero: u1, -// The offsets into the GDT where each segment resides. + /// When set indicates the segment is a x86-64 segment. If set, then the IS_32_BIT flag must + /// not be set. If both are set, then will throw an exception. + is_64_bit: u1, -/// The offset of the NULL GDT entry. -pub const NULL_OFFSET: u16 = 0x00; + /// When set indicates the segment is a 32 bit protected mode segment. When not set, indicates + /// the segment is a 16 bit protected mode segment. + is_32_bit: u1, -/// The offset of the kernel code GDT entry. -pub const KERNEL_CODE_OFFSET: u16 = 0x08; - -/// The offset of the kernel data GDT entry. -pub const KERNEL_DATA_OFFSET: u16 = 0x10; - -/// The offset of the user code GDT entry. -pub const USER_CODE_OFFSET: u16 = 0x18; - -/// The offset of the user data GDT entry. -pub const USER_DATA_OFFSET: u16 = 0x20; - -/// The offset of the TTS GDT entry. -pub const TSS_OFFSET: u16 = 0x28; - -// The access bits -const ACCESSED_BIT = 0x01; // 00000001 -const WRITABLE_BIT = 0x02; // 00000010 -const DIRECTION_CONFORMING_BIT = 0x04; // 00000100 -const EXECUTABLE_BIT = 0x08; // 00001000 -const DESCRIPTOR_BIT = 0x10; // 00010000 - -const PRIVILEGE_RING_0 = 0x00; // 00000000 -const PRIVILEGE_RING_1 = 0x20; // 00100000 -const PRIVILEGE_RING_2 = 0x40; // 01000000 -const PRIVILEGE_RING_3 = 0x60; // 01100000 - -const PRESENT_BIT = 0x80; // 10000000 - -const KERNEL_SEGMENT = PRESENT_BIT | PRIVILEGE_RING_0 | DESCRIPTOR_BIT; -const USER_SEGMENT = PRESENT_BIT | PRIVILEGE_RING_3 | DESCRIPTOR_BIT; - -const CODE_SEGMENT = EXECUTABLE_BIT | WRITABLE_BIT; -const DATA_SEGMENT = WRITABLE_BIT; - -const TSS_SEGMENT = PRESENT_BIT | EXECUTABLE_BIT | ACCESSED_BIT; - -// The flag bits -const IS_64_BIT = 0x02; // 0010 -const IS_32_BIT = 0x04; // 0100 -const IS_LIMIT_4K_BIT = 0x08; // 1000 + /// The granularity bit. When set the limit is in 4KB blocks (page granularity). When not set, + /// then limit is in 1B blocks (byte granularity). This should be set as we are doing paging. + granularity: u1, +}; /// The structure that contains all the information that each GDT entry needs. const GdtEntry = packed struct { @@ -81,41 +69,20 @@ const GdtEntry = packed struct { /// The lower 24 bits of the base address. Describes the start of memory for the entry. base_low: u24, - /// Bit 0 : accessed - The CPU will set this when the GDT entry is accessed. - /// Bit 1 : writable - The writable bit to say if the memory region is writable. If set, then memory region is readable and writable. If not set, then the memory region is just readable. - /// Bit 2 : direction_conforming - For a code segment: if set (1), then the code segment can be executed from a lower ring level. If unset (0), then the code segment can only be executed from the same ring level in the privilege flag. For the data segment: if set (1), then the data segment grows downwards. If unset (0), then the data segment grows upwards. - /// Bit 3 : executable - The execution bit to say that the memory region is executable. - /// Bit 4 : descriptor_bit - The descriptor bit. - /// Bit 5-6 : privilege - The ring level of the memory region. - /// Bit 7 : present - The present bit to tell that this GDT entry is present. - access: u8, + /// The access bits, see AccessBits for all the options. 8 bits. + access: AccessBits, /// The upper 4 bits of the limit address. Describes the size of memory that can be addressed. limit_high: u4, - /// Bit 0 : reserved_zero - This must always be zero. - /// Bit 1 : is_64bits - Whether this is a 64 bit system. - /// Bit 2 : is_32bits - Whether this is a 32 bit system. - /// Bit 3 : is_limit_4K - Whether paging is turned on, and each address is addressed as if it is a page number not physical/logical linear address. - flags: u4, + /// The flag bits, see above for all the options. 4 bits. + flags: FlagBits, /// The upper 8 bits of the base address. Describes the start of memory for the entry. base_high: u8, }; -/// The GDT pointer structure that contains the pointer to the beginning of the GDT and the number -/// of the table (minus 1). Used to load the GDT with LGDT instruction. -pub const GdtPtr = packed struct { - /// 16bit entry for the number of entries (minus 1). - limit: u16, - - /// 32bit entry for the base address for the GDT. - base: *GdtEntry, -}; - -/// /// The TSS entry structure -/// const TtsEntry = packed struct { /// Pointer to the previous TSS entry prev_tss: u32, @@ -199,60 +166,186 @@ const TtsEntry = packed struct { io_permissions_base_offset: u16, }; -/// -/// Make a GDT entry. -/// -/// Arguments: -/// IN access: u8 - The access bits for the descriptor. -/// IN flags: u4 - The flag bits for the descriptor. -/// -/// Return: -/// A new GDT entry with the give access and flag bits set with the base at 0x00000000 and limit at 0xFFFFF. -/// -fn makeEntry(base: u32, limit: u20, access: u8, flags: u4) GdtEntry { - return GdtEntry{ - .limit_low = @truncate(u16, limit), - .base_low = @truncate(u24, base), - .access = access, - .limit_high = @truncate(u4, limit >> 16), - .flags = flags, - .base_high = @truncate(u8, base >> 24), - }; -} +/// The GDT pointer structure that contains the pointer to the beginning of the GDT and the number +/// of the table (minus 1). Used to load the GDT with LGDT instruction. +pub const GdtPtr = packed struct { + /// 16bit entry for the number of entries (minus 1). + limit: u16, + + /// 32bit entry for the base address for the GDT. + base: u32, +}; + +/// The total number of entries in the GTD: null, kernel code, kernel data, user code, user data +/// and TSS +const NUMBER_OF_ENTRIES: u16 = 0x06; + +/// The size of the GTD in bytes (minus 1). +const TABLE_SIZE: u16 = @sizeOf(GdtEntry) * NUMBER_OF_ENTRIES - 1; + +// ---------- +// The indexes into the GDT where each segment resides. +// ---------- + +/// The index of the NULL GDT entry. +const NULL_INDEX: u16 = 0x00; + +/// The index of the kernel code GDT entry. +const KERNEL_CODE_INDEX: u16 = 0x01; + +/// The index of the kernel data GDT entry. +const KERNEL_DATA_INDEX: u16 = 0x02; + +/// The index of the user code GDT entry. +const USER_CODE_INDEX: u16 = 0x03; + +/// The index of the user data GDT entry. +const USER_DATA_INDEX: u16 = 0x04; + +/// The index of the task state segment GDT entry. +const TSS_INDEX: u16 = 0x05; + +/// The null segment, everything is set to zero. +const NULL_SEGMENT: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 0, + .direction_conforming = 0, + .executable = 0, + .descriptor = 0, + .privilege = 0, + .present = 0, +}; + +/// This bit pattern represents a kernel code segment with bits: readable, executable, descriptor, +/// privilege 0, and present set. +const KERNEL_SEGMENT_CODE: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 1, + .descriptor = 1, + .privilege = 0, + .present = 1, +}; + +/// This bit pattern represents a kernel data segment with bits: writeable, descriptor, privilege 0, +/// and present set. +const KERNEL_SEGMENT_DATA: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 0, + .descriptor = 1, + .privilege = 0, + .present = 1, +}; + +/// This bit pattern represents a user code segment with bits: readable, executable, descriptor, +/// privilege 3, and present set. +const USER_SEGMENT_CODE: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 1, + .descriptor = 1, + .privilege = 3, + .present = 1, +}; + +/// This bit pattern represents a user data segment with bits: writeable, descriptor, privilege 3, +/// and present set. +const USER_SEGMENT_DATA: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 0, + .descriptor = 1, + .privilege = 3, + .present = 1, +}; + +/// This bit pattern represents a TSS segment with bits: accessed, executable and present set. +const TSS_SEGMENT: AccessBits = AccessBits{ + .accessed = 1, + .read_write = 0, + .direction_conforming = 0, + .executable = 1, + .descriptor = 0, + .privilege = 0, + .present = 1, +}; + +/// The bit pattern for all bits set to zero. +const NULL_FLAGS: FlagBits = FlagBits{ + .reserved_zero = 0, + .is_64_bit = 0, + .is_32_bit = 0, + .granularity = 0, +}; + +/// The bit pattern for all segments where we are in 32 bit protected mode and paging enabled. +const PAGING_32_BIT: FlagBits = FlagBits{ + .reserved_zero = 0, + .is_64_bit = 0, + .is_32_bit = 1, + .granularity = 1, +}; + +// ---------- +// The offsets into the GDT where each segment resides. +// ---------- + +/// The offset of the NULL GDT entry. +pub const NULL_OFFSET: u16 = 0x00; + +/// The offset of the kernel code GDT entry. +pub const KERNEL_CODE_OFFSET: u16 = 0x08; + +/// The offset of the kernel data GDT entry. +pub const KERNEL_DATA_OFFSET: u16 = 0x10; + +/// The offset of the user code GDT entry. +pub const USER_CODE_OFFSET: u16 = 0x18; + +/// The offset of the user data GDT entry. +pub const USER_DATA_OFFSET: u16 = 0x20; + +/// The offset of the TTS GDT entry. +pub const TSS_OFFSET: u16 = 0x28; /// The GDT entry table of NUMBER_OF_ENTRIES entries. var gdt_entries: [NUMBER_OF_ENTRIES]GdtEntry = [_]GdtEntry{ // Null descriptor - makeEntry(0, 0, 0, 0), + makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS), // Kernel Code - makeEntry(0, 0xFFFFF, KERNEL_SEGMENT | CODE_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT), + makeEntry(0, 0xFFFFF, KERNEL_SEGMENT_CODE, PAGING_32_BIT), // Kernel Data - makeEntry(0, 0xFFFFF, KERNEL_SEGMENT | DATA_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT), + makeEntry(0, 0xFFFFF, KERNEL_SEGMENT_DATA, PAGING_32_BIT), // User Code - makeEntry(0, 0xFFFFF, USER_SEGMENT | CODE_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT), + makeEntry(0, 0xFFFFF, USER_SEGMENT_CODE, PAGING_32_BIT), // User Data - makeEntry(0, 0xFFFFF, USER_SEGMENT | DATA_SEGMENT, IS_32_BIT | IS_LIMIT_4K_BIT), + makeEntry(0, 0xFFFFF, USER_SEGMENT_DATA, PAGING_32_BIT), // Fill in TSS at runtime - makeEntry(0, 0, 0, 0), + makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS), }; /// The GDT pointer that the CPU is loaded with that contains the base address of the GDT and the /// size. -const gdt_ptr: GdtPtr = GdtPtr{ +var gdt_ptr: GdtPtr = GdtPtr{ .limit = TABLE_SIZE, - .base = &gdt_entries[0], + .base = undefined, }; /// The task state segment entry. var tss: TtsEntry = TtsEntry{ .prev_tss = 0, - .esp0 = undefined, - .ss0 = KERNEL_DATA_OFFSET, + .esp0 = 0, + .ss0 = u32(KERNEL_DATA_OFFSET), .esp1 = 0, .ss1 = 0, .esp2 = 0, @@ -276,31 +369,316 @@ var tss: TtsEntry = TtsEntry{ .gs = 0, .ldtr = 0, .trap = 0, - .io_permissions_base_offset = @sizeOf(TtsEntry), + .io_permissions_base_offset = u16(@sizeOf(TtsEntry)), }; /// -/// Set the stack pointer in the TSS entry +/// Make a GDT entry. /// /// Arguments: -/// IN esp0: u32 - The stack pointer +/// IN base: u32 - The linear address where the segment begins. +/// IN limit: u20 - The maximum addressable unit whether it is 1B units or page units. +/// IN access: AccessBits - The access bits for the descriptor. +/// IN flags: FlagBits - The flag bits for the descriptor. +/// +/// Return: GdtEntry +/// A new GDT entry with the give access and flag bits set with the base at 0x00000000 and +/// limit at 0xFFFFF. +/// +fn makeEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntry { + return GdtEntry{ + .limit_low = @truncate(u16, limit), + .base_low = @truncate(u24, base), + .access = AccessBits{ + .accessed = access.accessed, + .read_write = access.read_write, + .direction_conforming = access.direction_conforming, + .executable = access.executable, + .descriptor = access.descriptor, + .privilege = access.privilege, + .present = access.present, + }, + .limit_high = @truncate(u4, limit >> 16), + .flags = FlagBits{ + .reserved_zero = flags.reserved_zero, + .is_64_bit = flags.is_64_bit, + .is_32_bit = flags.is_32_bit, + .granularity = flags.granularity, + }, + .base_high = @truncate(u8, base >> 24), + }; +} + +/// +/// Set the stack pointer in the TSS entry. +/// +/// Arguments: +/// IN esp0: u32 - The stack pointer. /// pub fn setTssStack(esp0: u32) void { tss.esp0 = esp0; } /// -/// Initialise the Global Descriptor table +/// Initialise the Global Descriptor table. /// pub fn init() void { log.logInfo("Init gdt\n"); // Initiate TSS - gdt_entries[TSS_INDEX] = makeEntry(@ptrToInt(&tss), @sizeOf(TtsEntry) - 1, TSS_SEGMENT, 0); + gdt_entries[TSS_INDEX] = makeEntry(@intCast(u32, @ptrToInt(&tss)), @sizeOf(TtsEntry) - 1, TSS_SEGMENT, NULL_FLAGS); + + // Set the base address where all the GDT entries are. + gdt_ptr.base = @intCast(u32, @ptrToInt(&gdt_entries[0])); // Load the GDT arch.lgdt(&gdt_ptr); // Load the TSS - arch.ltr(); + arch.ltr(TSS_OFFSET); + log.logInfo("Done\n"); + + if (build_options.rt_test) runtimeTests(); +} + +fn mock_lgdt(ptr: *const GdtPtr) void { + expectEqual(TABLE_SIZE, ptr.limit); + expectEqual(@intCast(u32, @ptrToInt(&gdt_entries[0])), ptr.base); +} + +test "GDT entries" { + expectEqual(u32(1), @sizeOf(AccessBits)); + expectEqual(u32(1), @sizeOf(FlagBits)); + expectEqual(u32(8), @sizeOf(GdtEntry)); + expectEqual(u32(104), @sizeOf(TtsEntry)); + expectEqual(u32(6), @sizeOf(GdtPtr)); + + const null_entry = gdt_entries[NULL_INDEX]; + expectEqual(u64(0), @bitCast(u64, null_entry)); + + const kernel_code_entry = gdt_entries[KERNEL_CODE_INDEX]; + expectEqual(u64(0xCF9A000000FFFF), @bitCast(u64, kernel_code_entry)); + + const kernel_data_entry = gdt_entries[KERNEL_DATA_INDEX]; + expectEqual(u64(0xCF92000000FFFF), @bitCast(u64, kernel_data_entry)); + + const user_code_entry = gdt_entries[USER_CODE_INDEX]; + expectEqual(u64(0xCFFA000000FFFF), @bitCast(u64, user_code_entry)); + + const user_data_entry = gdt_entries[USER_DATA_INDEX]; + expectEqual(u64(0xCFF2000000FFFF), @bitCast(u64, user_data_entry)); + + const tss_entry = gdt_entries[TSS_INDEX]; + expectEqual(u64(0), @bitCast(u64, tss_entry)); + + expectEqual(TABLE_SIZE, gdt_ptr.limit); + + expectEqual(u32(0), tss.prev_tss); + expectEqual(u32(0), tss.esp0); + expectEqual(u32(KERNEL_DATA_OFFSET), tss.ss0); + expectEqual(u32(0), tss.esp1); + expectEqual(u32(0), tss.ss1); + expectEqual(u32(0), tss.esp2); + expectEqual(u32(0), tss.ss2); + expectEqual(u32(0), tss.cr3); + expectEqual(u32(0), tss.eip); + expectEqual(u32(0), tss.eflags); + expectEqual(u32(0), tss.eax); + expectEqual(u32(0), tss.ecx); + expectEqual(u32(0), tss.edx); + expectEqual(u32(0), tss.ebx); + expectEqual(u32(0), tss.esp); + expectEqual(u32(0), tss.ebp); + expectEqual(u32(0), tss.esi); + expectEqual(u32(0), tss.edi); + expectEqual(u32(0), tss.es); + expectEqual(u32(0), tss.cs); + expectEqual(u32(0), tss.ss); + expectEqual(u32(0), tss.ds); + expectEqual(u32(0), tss.fs); + expectEqual(u32(0), tss.gs); + expectEqual(u32(0), tss.ldtr); + expectEqual(u16(0), tss.trap); + + // Size of TtsEntry will fit in a u16 as 104 < 65535 (2^16) + expectEqual(u16(@sizeOf(TtsEntry)), tss.io_permissions_base_offset); +} + +test "makeEntry NULL" { + const actual = makeEntry(u32(0), u32(0), NULL_SEGMENT, NULL_FLAGS); + + const expected = u64(0); + expectEqual(expected, @bitCast(u64, actual)); +} + +test "makeEntry alternating bit pattern" { + const alt_access = AccessBits{ + .accessed = 1, + .read_write = 0, + .direction_conforming = 1, + .executable = 0, + .descriptor = 1, + .privilege = 0b10, + .present = 0, + }; + + expectEqual(u8(0b01010101), @bitCast(u8, alt_access)); + + const alt_flag = FlagBits{ + .reserved_zero = 1, + .is_64_bit = 0, + .is_32_bit = 1, + .granularity = 0, + }; + + expectEqual(u4(0b0101), @bitCast(u4, alt_flag)); + + const actual = makeEntry(u32(0b01010101010101010101010101010101), u20(0b01010101010101010101), alt_access, alt_flag); + + const expected = u64(0b0101010101010101010101010101010101010101010101010101010101010101); + expectEqual(expected, @bitCast(u64, actual)); +} + +test "setTssStack" { + // Pre-testing + expectEqual(u32(0), tss.prev_tss); + expectEqual(u32(0), tss.esp0); + expectEqual(u32(KERNEL_DATA_OFFSET), tss.ss0); + expectEqual(u32(0), tss.esp1); + expectEqual(u32(0), tss.ss1); + expectEqual(u32(0), tss.esp2); + expectEqual(u32(0), tss.ss2); + expectEqual(u32(0), tss.cr3); + expectEqual(u32(0), tss.eip); + expectEqual(u32(0), tss.eflags); + expectEqual(u32(0), tss.eax); + expectEqual(u32(0), tss.ecx); + expectEqual(u32(0), tss.edx); + expectEqual(u32(0), tss.ebx); + expectEqual(u32(0), tss.esp); + expectEqual(u32(0), tss.ebp); + expectEqual(u32(0), tss.esi); + expectEqual(u32(0), tss.edi); + expectEqual(u32(0), tss.es); + expectEqual(u32(0), tss.cs); + expectEqual(u32(0), tss.ss); + expectEqual(u32(0), tss.ds); + expectEqual(u32(0), tss.fs); + expectEqual(u32(0), tss.gs); + expectEqual(u32(0), tss.ldtr); + expectEqual(u16(0), tss.trap); + expectEqual(u16(@sizeOf(TtsEntry)), tss.io_permissions_base_offset); + + // Call function + setTssStack(u32(100)); + + // Post-testing + expectEqual(u32(0), tss.prev_tss); + expectEqual(u32(100), tss.esp0); + expectEqual(u32(KERNEL_DATA_OFFSET), tss.ss0); + expectEqual(u32(0), tss.esp1); + expectEqual(u32(0), tss.ss1); + expectEqual(u32(0), tss.esp2); + expectEqual(u32(0), tss.ss2); + expectEqual(u32(0), tss.cr3); + expectEqual(u32(0), tss.eip); + expectEqual(u32(0), tss.eflags); + expectEqual(u32(0), tss.eax); + expectEqual(u32(0), tss.ecx); + expectEqual(u32(0), tss.edx); + expectEqual(u32(0), tss.ebx); + expectEqual(u32(0), tss.esp); + expectEqual(u32(0), tss.ebp); + expectEqual(u32(0), tss.esi); + expectEqual(u32(0), tss.edi); + expectEqual(u32(0), tss.es); + expectEqual(u32(0), tss.cs); + expectEqual(u32(0), tss.ss); + expectEqual(u32(0), tss.ds); + expectEqual(u32(0), tss.fs); + expectEqual(u32(0), tss.gs); + expectEqual(u32(0), tss.ldtr); + expectEqual(u16(0), tss.trap); + expectEqual(u16(@sizeOf(TtsEntry)), tss.io_permissions_base_offset); + + // Clean up + setTssStack(u32(0)); + + expectEqual(u32(0), tss.prev_tss); + expectEqual(u32(0), tss.esp0); + expectEqual(u32(KERNEL_DATA_OFFSET), tss.ss0); + expectEqual(u32(0), tss.esp1); + expectEqual(u32(0), tss.ss1); + expectEqual(u32(0), tss.esp2); + expectEqual(u32(0), tss.ss2); + expectEqual(u32(0), tss.cr3); + expectEqual(u32(0), tss.eip); + expectEqual(u32(0), tss.eflags); + expectEqual(u32(0), tss.eax); + expectEqual(u32(0), tss.ecx); + expectEqual(u32(0), tss.edx); + expectEqual(u32(0), tss.ebx); + expectEqual(u32(0), tss.esp); + expectEqual(u32(0), tss.ebp); + expectEqual(u32(0), tss.esi); + expectEqual(u32(0), tss.edi); + expectEqual(u32(0), tss.es); + expectEqual(u32(0), tss.cs); + expectEqual(u32(0), tss.ss); + expectEqual(u32(0), tss.ds); + expectEqual(u32(0), tss.fs); + expectEqual(u32(0), tss.gs); + expectEqual(u32(0), tss.ldtr); + expectEqual(u16(0), tss.trap); + expectEqual(u16(@sizeOf(TtsEntry)), tss.io_permissions_base_offset); +} + +test "init" { + // Set up + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("ltr", TSS_OFFSET); + + arch.addConsumeFunction("lgdt", mock_lgdt); + + // Call function + init(); + + // Post testing + const tss_entry = gdt_entries[TSS_INDEX]; + const tss_limit = @sizeOf(TtsEntry) - 1; + const tss_addr = @ptrToInt(&tss); + + var expected = u64(0); + expected |= u64(@truncate(u16, tss_limit)); + expected |= u64(@truncate(u24, tss_addr)) << 16; + expected |= u64(0x89) << (16 + 24); + expected |= u64(@truncate(u4, tss_limit >> 16)) << (16 + 24 + 8); + // Flags are zero + expected |= u64(@truncate(u8, tss_addr >> 24)) << (16 + 24 + 8 + 4 + 4); + + expectEqual(expected, @bitCast(u64, tss_entry)); + + // Reset + gdt_ptr.base = 0; + gdt_entries[TSS_INDEX] = makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS); +} + +/// +/// Check that the GDT table was loaded properly by getting the previously loaded table and +/// compare the limit and base address. +/// +fn rt_loadedGDTSuccess() void { + const loaded_gdt = arch.sgdt(); + expect(gdt_ptr.limit == loaded_gdt.limit); + expect(gdt_ptr.base == loaded_gdt.base); +} + +/// +/// Run all the runtime tests. +/// +fn runtimeTests() void { + rt_loadedGDTSuccess(); + log.logInfo("GDT: Tested loading GDT\n"); } diff --git a/src/kernel/kernel.zig b/src/kernel/kernel.zig new file mode 100644 index 0000000..63b3199 --- /dev/null +++ b/src/kernel/kernel.zig @@ -0,0 +1,7 @@ +pub const arch = @import("arch.zig").internals; +pub const log = @import("log.zig"); +pub const mem = @import("mem.zig"); +pub const panic = @import("panic.zig"); +pub const serial = @import("serial.zig"); +pub const tty = @import("tty.zig"); +pub const vga = @import("vga.zig"); diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 2034986..1aa4421 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -1,15 +1,13 @@ -// Zig version: 0.4.0 - const std = @import("std"); const builtin = @import("builtin"); +const build_options = @import("build_options"); const arch = @import("arch.zig").internals; const multiboot = @import("multiboot.zig"); const tty = @import("tty.zig"); const vga = @import("vga.zig"); const log = @import("log.zig"); const serial = @import("serial.zig"); -const mem = if (builtin.is_test) @import("mocking").mem else @import("mem.zig"); -const options = @import("build_options"); +const mem = if (builtin.is_test) @import(build_options.mock_path ++ "mem_mock.zig") else @import("mem.zig"); comptime { switch (builtin.arch) { @@ -24,7 +22,7 @@ export var KERNEL_ADDR_OFFSET: u32 = if (builtin.is_test) 0xC0000000 else undefi // Need to import this as we need the panic to be in the root source file, or zig will just use the // builtin panic and just loop, which is what we don't want -const panic_root = if (builtin.is_test) @import("mocking").panic else @import("panic.zig"); +const panic_root = if (builtin.is_test) @import(build_options.mock_path ++ "panic_mock.zig") else @import("panic.zig"); // Just call the panic function, as this need to be in the root source file pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn { @@ -33,7 +31,7 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn panic_root.panicFmt(error_return_trace, "{}", msg); } -pub export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void { +export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void { if (mb_magic == multiboot.MULTIBOOT_BOOTLOADER_MAGIC) { // Booted with compatible bootloader const mem_profile = mem.init(mb_info); @@ -43,7 +41,7 @@ pub export fn kmain(mb_info: *multiboot.multiboot_info_t, mb_magic: u32) void { serial.init(serial.DEFAULT_BAUDRATE, serial.Port.COM1) catch unreachable; log.logInfo("Init arch " ++ @tagName(builtin.arch) ++ "\n"); - arch.init(&mem_profile, &fixed_allocator.allocator, options); + arch.init(&mem_profile, &fixed_allocator.allocator, build_options); log.logInfo("Arch init done\n"); vga.init(); tty.init(); diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig index 7f127be..f9c9a5d 100644 --- a/src/kernel/tty.zig +++ b/src/kernel/tty.zig @@ -1,4 +1,5 @@ -const is_test = @import("builtin").is_test; +const builtin = @import("builtin"); +const is_test = builtin.is_test; const std = @import("std"); const fmt = std.fmt; @@ -6,8 +7,9 @@ const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; -const vga = if (is_test) @import("mocking").vga else @import("vga.zig"); -const log = if (is_test) @import("mocking").log else @import("log.zig"); +const build_options = @import("build_options"); +const vga = if (is_test) @import(build_options.mock_path ++ "vga_mock.zig") else @import("vga.zig"); +const log = if (is_test) @import(build_options.mock_path ++ "log_mock.zig") else @import("log.zig"); /// The number of rows down from the top (row 0) where the displayable region starts. Above is /// where the logo and time is printed diff --git a/src/kernel/vga.zig b/src/kernel/vga.zig index 244ad37..d2da678 100644 --- a/src/kernel/vga.zig +++ b/src/kernel/vga.zig @@ -1,80 +1,82 @@ +const std = @import("std"); +const expectEqual = std.testing.expectEqual; const arch = @import("arch.zig").internals; /// The port address for the VGA register selection. -pub const PORT_ADDRESS: u16 = 0x03D4; +const PORT_ADDRESS: u16 = 0x03D4; /// The port address for the VGA data. -pub const PORT_DATA: u16 = 0x03D5; +const PORT_DATA: u16 = 0x03D5; /// The indexes that is passed to the address port to select the register for the data to be /// read or written to. -pub const REG_HORIZONTAL_TOTAL: u8 = 0x00; -pub const REG_HORIZONTAL_DISPLAY_ENABLE_END: u8 = 0x01; -pub const REG_START_HORIZONTAL_BLINKING: u8 = 0x02; -pub const REG_END_HORIZONTAL_BLINKING: u8 = 0x03; -pub const REG_START_HORIZONTAL_RETRACE_PULSE: u8 = 0x04; -pub const REG_END_HORIZONTAL_RETRACE_PULSE: u8 = 0x05; -pub const REG_VERTICAL_TOTAL: u8 = 0x06; -pub const REG_OVERFLOW: u8 = 0x07; -pub const REG_PRESET_ROW_SCAN: u8 = 0x08; -pub const REG_MAXIMUM_SCAN_LINE: u8 = 0x09; +const REG_HORIZONTAL_TOTAL: u8 = 0x00; +const REG_HORIZONTAL_DISPLAY_ENABLE_END: u8 = 0x01; +const REG_START_HORIZONTAL_BLINKING: u8 = 0x02; +const REG_END_HORIZONTAL_BLINKING: u8 = 0x03; +const REG_START_HORIZONTAL_RETRACE_PULSE: u8 = 0x04; +const REG_END_HORIZONTAL_RETRACE_PULSE: u8 = 0x05; +const REG_VERTICAL_TOTAL: u8 = 0x06; +const REG_OVERFLOW: u8 = 0x07; +const REG_PRESET_ROW_SCAN: u8 = 0x08; +const REG_MAXIMUM_SCAN_LINE: u8 = 0x09; /// The register select for setting the cursor scan lines. -pub const REG_CURSOR_START: u8 = 0x0A; -pub const REG_CURSOR_END: u8 = 0x0B; -pub const REG_START_ADDRESS_HIGH: u8 = 0x0C; -pub const REG_START_ADDRESS_LOW: u8 = 0x0D; +const REG_CURSOR_START: u8 = 0x0A; +const REG_CURSOR_END: u8 = 0x0B; +const REG_START_ADDRESS_HIGH: u8 = 0x0C; +const REG_START_ADDRESS_LOW: u8 = 0x0D; /// The command for setting the cursor's linear location. -pub const REG_CURSOR_LOCATION_HIGH: u8 = 0x0E; -pub const REG_CURSOR_LOCATION_LOW: u8 = 0x0F; +const REG_CURSOR_LOCATION_HIGH: u8 = 0x0E; +const REG_CURSOR_LOCATION_LOW: u8 = 0x0F; /// Other VGA registers. -pub const REG_VERTICAL_RETRACE_START: u8 = 0x10; -pub const REG_VERTICAL_RETRACE_END: u8 = 0x11; -pub const REG_VERTICAL_DISPLAY_ENABLE_END: u8 = 0x12; -pub const REG_OFFSET: u8 = 0x13; -pub const REG_UNDERLINE_LOCATION: u8 = 0x14; -pub const REG_START_VERTICAL_BLINKING: u8 = 0x15; -pub const REG_END_VERTICAL_BLINKING: u8 = 0x16; -pub const REG_CRT_MODE_CONTROL: u8 = 0x17; -pub const REG_LINE_COMPARE: u8 = 0x18; +const REG_VERTICAL_RETRACE_START: u8 = 0x10; +const REG_VERTICAL_RETRACE_END: u8 = 0x11; +const REG_VERTICAL_DISPLAY_ENABLE_END: u8 = 0x12; +const REG_OFFSET: u8 = 0x13; +const REG_UNDERLINE_LOCATION: u8 = 0x14; +const REG_START_VERTICAL_BLINKING: u8 = 0x15; +const REG_END_VERTICAL_BLINKING: u8 = 0x16; +const REG_CRT_MODE_CONTROL: u8 = 0x17; +const REG_LINE_COMPARE: u8 = 0x18; /// The start of the cursor scan line, the very beginning. -pub const CURSOR_SCANLINE_START: u8 = 0x0; +const CURSOR_SCANLINE_START: u8 = 0x0; /// The scan line for use in the underline cursor shape. -pub const CURSOR_SCANLINE_MIDDLE: u8 = 0xE; +const CURSOR_SCANLINE_MIDDLE: u8 = 0xE; /// The end of the cursor scan line, the very end. -pub const CURSOR_SCANLINE_END: u8 = 0xF; +const CURSOR_SCANLINE_END: u8 = 0xF; /// If set, disables the cursor. -pub const CURSOR_DISABLE: u8 = 0x20; +const CURSOR_DISABLE: u8 = 0x20; /// The number of characters wide the screen is. -pub const WIDTH: u16 = 80; +pub const WIDTH: u16 = 80; /// The number of characters heigh the screen is. -pub const HEIGHT: u16 = 25; +pub const HEIGHT: u16 = 25; /// The set of colours that VGA supports and can display for the foreground and background. -pub const COLOUR_BLACK: u4 = 0x00; -pub const COLOUR_BLUE: u4 = 0x01; -pub const COLOUR_GREEN: u4 = 0x02; -pub const COLOUR_CYAN: u4 = 0x03; -pub const COLOUR_RED: u4 = 0x04; -pub const COLOUR_MAGENTA: u4 = 0x05; -pub const COLOUR_BROWN: u4 = 0x06; -pub const COLOUR_LIGHT_GREY: u4 = 0x07; -pub const COLOUR_DARK_GREY: u4 = 0x08; -pub const COLOUR_LIGHT_BLUE: u4 = 0x09; -pub const COLOUR_LIGHT_GREEN: u4 = 0x0A; -pub const COLOUR_LIGHT_CYAN: u4 = 0x0B; -pub const COLOUR_LIGHT_RED: u4 = 0x0C; -pub const COLOUR_LIGHT_MAGENTA: u4 = 0x0D; -pub const COLOUR_LIGHT_BROWN: u4 = 0x0E; -pub const COLOUR_WHITE: u4 = 0x0F; +pub const COLOUR_BLACK: u4 = 0x00; +pub const COLOUR_BLUE: u4 = 0x01; +pub const COLOUR_GREEN: u4 = 0x02; +pub const COLOUR_CYAN: u4 = 0x03; +pub const COLOUR_RED: u4 = 0x04; +pub const COLOUR_MAGENTA: u4 = 0x05; +pub const COLOUR_BROWN: u4 = 0x06; +pub const COLOUR_LIGHT_GREY: u4 = 0x07; +pub const COLOUR_DARK_GREY: u4 = 0x08; +pub const COLOUR_LIGHT_BLUE: u4 = 0x09; +pub const COLOUR_LIGHT_GREEN: u4 = 0x0A; +pub const COLOUR_LIGHT_CYAN: u4 = 0x0B; +pub const COLOUR_LIGHT_RED: u4 = 0x0C; +pub const COLOUR_LIGHT_MAGENTA: u4 = 0x0D; +pub const COLOUR_LIGHT_BROWN: u4 = 0x0E; +pub const COLOUR_WHITE: u4 = 0x0F; /// The set of shapes that can be displayed. pub const CursorShape = enum { @@ -234,3 +236,235 @@ pub fn init() void { // Set by default the underline cursor setCursorShape(CursorShape.UNDERLINE); } + +test "entryColour" { + var fg: u4 = COLOUR_BLACK; + var bg: u4 = COLOUR_BLACK; + var res: u8 = entryColour(fg, bg); + expectEqual(u8(0x00), res); + + fg = COLOUR_LIGHT_GREEN; + bg = COLOUR_BLACK; + res = entryColour(fg, bg); + expectEqual(u8(0x0A), res); + + fg = COLOUR_BLACK; + bg = COLOUR_LIGHT_GREEN; + res = entryColour(fg, bg); + expectEqual(u8(0xA0), res); + + fg = COLOUR_BROWN; + bg = COLOUR_LIGHT_GREEN; + res = entryColour(fg, bg); + expectEqual(u8(0xA6), res); +} + +test "entry" { + var colour: u8 = entryColour(COLOUR_BROWN, COLOUR_LIGHT_GREEN); + expectEqual(u8(0xA6), colour); + + // Character '0' is 0x30 + var video_entry: u16 = entry('0', colour); + expectEqual(u16(0xA630), video_entry); + + video_entry = entry(0x55, colour); + expectEqual(u16(0xA655), video_entry); +} + +test "updateCursor width out of bounds" { + const x: u16 = WIDTH; + const y: u16 = 0; + + const max_cursor: u16 = (HEIGHT - 1) * WIDTH + (WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_LOW, PORT_DATA, expected_lower, PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH, PORT_DATA, expected_upper); + + updateCursor(x, y); +} + +test "updateCursor height out of bounds" { + const x: u16 = 0; + const y: u16 = HEIGHT; + + const max_cursor: u16 = (HEIGHT - 1) * WIDTH + (WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_LOW, PORT_DATA, expected_lower, PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH, PORT_DATA, expected_upper); + + updateCursor(x, y); +} + +test "updateCursor width and height out of bounds" { + const x: u16 = WIDTH; + const y: u16 = HEIGHT; + + const max_cursor: u16 = (HEIGHT - 1) * WIDTH + (WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_LOW, PORT_DATA, expected_lower, PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH, PORT_DATA, expected_upper); + + updateCursor(x, y); +} + +test "updateCursor width-1 and height out of bounds" { + const x: u16 = WIDTH - 1; + const y: u16 = HEIGHT; + + const max_cursor: u16 = (HEIGHT - 1) * WIDTH + (WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_LOW, PORT_DATA, expected_lower, PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH, PORT_DATA, expected_upper); + + updateCursor(x, y); +} + +test "updateCursor width and height-1 out of bounds" { + const x: u16 = WIDTH; + const y: u16 = HEIGHT - 1; + + const max_cursor: u16 = (HEIGHT - 1) * WIDTH + (WIDTH - 1); + const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); + const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_LOW, PORT_DATA, expected_lower, PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH, PORT_DATA, expected_upper); + + updateCursor(x, y); +} + +test "updateCursor in bounds" { + var x: u16 = 0x000A; + var y: u16 = 0x000A; + const expected: u16 = y * WIDTH + x; + + var expected_upper: u8 = @truncate(u8, (expected >> 8) & 0x00FF); + var expected_lower: u8 = @truncate(u8, expected & 0x00FF); + + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for changing the hardware cursor: + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_LOW, PORT_DATA, expected_lower, PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH, PORT_DATA, expected_upper); + updateCursor(x, y); +} + +test "getCursor 1: 10" { + const expect: u16 = u16(10); + + // Mocking out the arch.outb and arch.inb calls for getting the hardware cursor: + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_LOW); + + arch.addTestParams("inb", PORT_DATA, u8(10)); + + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH); + + arch.addTestParams("inb", PORT_DATA, u8(0)); + + const actual: u16 = getCursor(); + expectEqual(expect, actual); +} + +test "getCursor 2: 0xBEEF" { + const expect: u16 = u16(0xBEEF); + + // Mocking out the arch.outb and arch.inb calls for getting the hardware cursor: + arch.initTest(); + defer arch.freeTest(); + + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_LOW); + + arch.addTestParams("inb", PORT_DATA, u8(0xEF)); + + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_LOCATION_HIGH); + + arch.addTestParams("inb", PORT_DATA, u8(0xBE)); + + const actual: u16 = getCursor(); + expectEqual(expect, actual); +} + +test "enableCursor all" { + arch.initTest(); + defer arch.freeTest(); + + // Need to init the cursor start and end positions, so call the init() to set this up + arch.addTestParams("outb", PORT_ADDRESS, REG_MAXIMUM_SCAN_LINE, PORT_DATA, CURSOR_SCANLINE_END, PORT_ADDRESS, REG_CURSOR_START, PORT_DATA, CURSOR_SCANLINE_MIDDLE, PORT_ADDRESS, REG_CURSOR_END, PORT_DATA, CURSOR_SCANLINE_END, + // Mocking out the arch.outb calls for enabling the cursor: + // These are the default cursor positions from init() + PORT_ADDRESS, REG_CURSOR_START, PORT_DATA, CURSOR_SCANLINE_MIDDLE, PORT_ADDRESS, REG_CURSOR_END, PORT_DATA, CURSOR_SCANLINE_END); + + init(); + enableCursor(); +} + +test "disableCursor all" { + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for disabling the cursor: + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_START, PORT_DATA, CURSOR_DISABLE); + disableCursor(); +} + +test "setCursorShape UNDERLINE" { + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for setting the cursor shape to underline: + // This will also check that the scan line variables were set properly as these are using in + // the arch.outb call + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_START, PORT_DATA, CURSOR_SCANLINE_MIDDLE, PORT_ADDRESS, REG_CURSOR_END, PORT_DATA, CURSOR_SCANLINE_END); + + setCursorShape(CursorShape.UNDERLINE); +} + +test "setCursorShape BLOCK" { + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for setting the cursor shape to block: + // This will also check that the scan line variables were set properly as these are using in + // the arch.outb call + arch.addTestParams("outb", PORT_ADDRESS, REG_CURSOR_START, PORT_DATA, CURSOR_SCANLINE_START, PORT_ADDRESS, REG_CURSOR_END, PORT_DATA, CURSOR_SCANLINE_END); + + setCursorShape(CursorShape.BLOCK); +} + +test "init all" { + arch.initTest(); + defer arch.freeTest(); + + // Mocking out the arch.outb calls for setting the cursor max scan line and the shape to block: + // This will also check that the scan line variables were set properly as these are using in + // the arch.outb call for setting the cursor shape. + arch.addTestParams("outb", PORT_ADDRESS, REG_MAXIMUM_SCAN_LINE, PORT_DATA, CURSOR_SCANLINE_END, PORT_ADDRESS, REG_CURSOR_START, PORT_DATA, CURSOR_SCANLINE_MIDDLE, PORT_ADDRESS, REG_CURSOR_END, PORT_DATA, CURSOR_SCANLINE_END); + + init(); +} diff --git a/test/kernel/arch/x86/rt-test.py b/test/kernel/arch/x86/rt-test.py index 58e26ba..a2e103b 100644 --- a/test/kernel/arch/x86/rt-test.py +++ b/test/kernel/arch/x86/rt-test.py @@ -1,6 +1,7 @@ def getTestCases(TestCase): return [ TestCase("GDT init", [r"Init gdt", r"Done"]), + TestCase("GDT tests", [r"GDT: Tested loading GDT"]), TestCase("IDT init", [r"Init idt", r"Done"]), TestCase("PIT init", [r"Init pit", r".+", "Done"]), TestCase("Syscalls init", [r"Init syscalls", "Done"]), diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index c2652d5..30b3a0f 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -1,7 +1,8 @@ const std = @import("std"); -const MemProfile = @import("mem_mock.zig").MemProfile; -const expect = std.testing.expect; -const warn = std.debug.warn; +const Allocator = std.mem.Allocator; +const mem = @import("mem_mock.zig"); +const MemProfile = mem.MemProfile; +const gdt = @import("gdt_mock.zig"); const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; @@ -11,29 +12,20 @@ pub const addConsumeFunction = mock_framework.addConsumeFunction; pub const addRepeatFunction = mock_framework.addRepeatFunction; pub const InterruptContext = struct { - // Extra segments gs: u32, fs: u32, es: u32, ds: u32, - - // Destination, source, base pointer edi: u32, esi: u32, ebp: u32, esp: u32, - - // General registers ebx: u32, edx: u32, ecx: u32, eax: u32, - - // Interrupt number and error code int_num: u32, error_code: u32, - - // Instruction pointer, code segment and flags eip: u32, cs: u32, eflags: u32, @@ -41,10 +33,6 @@ pub const InterruptContext = struct { ss: u32, }; -pub fn init(mem_profile: *const MemProfile, allocator: *std.mem.Allocator, comptime options: type) void { - //return mock_framework.performAction("init", void, mem_profile, allocator); -} - pub fn outb(port: u16, data: u8) void { return mock_framework.performAction("outb", void, port, data); } @@ -57,20 +45,20 @@ pub fn ioWait() void { return mock_framework.performAction("ioWait", void); } -pub fn registerInterruptHandler(int: u16, ctx: fn (ctx: *InterruptContext) void) void { - return mock_framework.performAction("registerInterruptHandler", void, int, ctx); -} - pub fn lgdt(gdt_ptr: *const gdt.GdtPtr) void { - return mock_framework.performAction("lgdt", void, gdt_ptr.*); + return mock_framework.performAction("lgdt", void, gdt_ptr); } -pub fn ltr() void { - return mock_framework.performAction("ltr", void); +pub fn sgdt() gdt.GdtPtr { + return mock_framework.performAction("sgdt", gdt.GdtPtr); +} + +pub fn ltr(offset: u16) void { + return mock_framework.performAction("ltr", void, offset); } pub fn lidt(idt_ptr: *const idt.IdtPtr) void { - return mock_framework.performAction("lidt", void, idt_ptr.*); + return mock_framework.performAction("lidt", void, idt_ptr); } pub fn enableInterrupts() void { @@ -92,3 +80,9 @@ pub fn spinWait() noreturn { pub fn haltNoInterrupts() noreturn { while (true) {} } + +pub fn init(mem_profile: *const MemProfile, allocator: *Allocator, comptime options: type) void { + // I'll get back to this as this doesn't effect the GDT testing. + // When I come on to the mem.zig testing, I'll fix :) + //return mock_framework.performAction("init", void, mem_profile, allocator); +} diff --git a/test/mock/kernel/gdt_mock.zig b/test/mock/kernel/gdt_mock.zig new file mode 100644 index 0000000..031d697 --- /dev/null +++ b/test/mock/kernel/gdt_mock.zig @@ -0,0 +1,169 @@ +// Can't do: TODO: https://github.com/SamTebbs33/pluto/issues/77 +//const src_gdt = @import("arch").gdt; +const src_gdt = @import("../../../src/kernel/arch/x86/gdt.zig"); + +const mock_framework = @import("mock_framework.zig"); +pub const initTest = mock_framework.initTest; +pub const freeTest = mock_framework.freeTest; +pub const addTestParams = mock_framework.addTestParams; +pub const addConsumeFunction = mock_framework.addConsumeFunction; +pub const addRepeatFunction = mock_framework.addRepeatFunction; + +const AccessBits = packed struct { + accessed: u1, + read_write: u1, + direction_conforming: u1, + executable: u1, + descriptor: u1, + privilege: u2, + present: u1, +}; + +const FlagBits = packed struct { + reserved_zero: u1, + is_64_bit: u1, + is_32_bit: u1, + granularity: u1, +}; + +const GdtEntry = packed struct { + limit_low: u16, + base_low: u24, + access: AccessBits, + limit_high: u4, + flags: FlagBits, + base_high: u8, +}; + +const TtsEntry = packed struct { + prev_tss: u32, + esp0: u32, + ss0: u32, + esp1: u32, + ss1: u32, + esp2: u32, + ss2: u32, + cr3: u32, + eip: u32, + eflags: u32, + eax: u32, + ecx: u32, + edx: u32, + ebx: u32, + esp: u32, + ebp: u32, + esi: u32, + edi: u32, + es: u32, + cs: u32, + ss: u32, + ds: u32, + fs: u32, + gs: u32, + ldtr: u32, + trap: u16, + io_permissions_base_offset: u16, +}; + +// Need to use the type from the source file so that types match +pub const GdtPtr = src_gdt.GdtPtr; + +const NUMBER_OF_ENTRIES: u16 = 0x06; + +const TABLE_SIZE: u16 = @sizeOf(GdtEntry) * NUMBER_OF_ENTRIES - 1; + +const NULL_INDEX: u16 = 0x00; +const KERNEL_CODE_INDEX: u16 = 0x01; +const KERNEL_DATA_INDEX: u16 = 0x02; +const USER_CODE_INDEX: u16 = 0x03; +const USER_DATA_INDEX: u16 = 0x04; +const TSS_INDEX: u16 = 0x05; + +const NULL_SEGMENT: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 0, + .direction_conforming = 0, + .executable = 0, + .descriptor = 0, + .privilege = 0, + .present = 0, +}; + +const KERNEL_SEGMENT_CODE: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 1, + .descriptor = 1, + .privilege = 0, + .present = 1, +}; + +const KERNEL_SEGMENT_DATA: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 0, + .descriptor = 1, + .privilege = 0, + .present = 1, +}; + +const USER_SEGMENT_CODE: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 1, + .descriptor = 1, + .privilege = 3, + .present = 1, +}; + +const USER_SEGMENT_DATA: AccessBits = AccessBits{ + .accessed = 0, + .read_write = 1, + .direction_conforming = 0, + .executable = 0, + .descriptor = 1, + .privilege = 3, + .present = 1, +}; + +const TSS_SEGMENT: AccessBits = AccessBits{ + .accessed = 1, + .read_write = 0, + .direction_conforming = 0, + .executable = 1, + .descriptor = 0, + .privilege = 0, + .present = 1, +}; + +const NULL_FLAGS: FlagBits = FlagBits{ + .reserved_zero = 0, + .is_64_bit = 0, + .is_32_bit = 0, + .granularity = 0, +}; + +const PAGING_32_BIT: FlagBits = FlagBits{ + .reserved_zero = 0, + .is_64_bit = 0, + .is_32_bit = 1, + .granularity = 1, +}; + +pub const NULL_OFFSET: u16 = 0x00; +pub const KERNEL_CODE_OFFSET: u16 = 0x08; +pub const KERNEL_DATA_OFFSET: u16 = 0x10; +pub const USER_CODE_OFFSET: u16 = 0x18; +pub const USER_DATA_OFFSET: u16 = 0x20; +pub const TSS_OFFSET: u16 = 0x28; + +pub fn setTssStack(esp0: u32) void { + return mock_framework.performAction("setTssStack", void, esp0); +} + +pub fn init() void { + return mock_framework.performAction("init", void); +} diff --git a/test/mock/kernel/mock_framework.zig b/test/mock/kernel/mock_framework.zig index a331517..cc8eb81 100644 --- a/test/mock/kernel/mock_framework.zig +++ b/test/mock/kernel/mock_framework.zig @@ -1,11 +1,11 @@ const std = @import("std"); -const builtin = @import("builtin"); const StringHashMap = std.StringHashMap; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; const GlobalAllocator = std.debug.global_allocator; const TailQueue = std.TailQueue; const warn = std.debug.warn; +const gdt = @import("gdt_mock.zig"); /// /// The enumeration of types that the mocking framework supports. These include basic types like u8 @@ -16,14 +16,17 @@ const DataElementType = enum { U8, U16, U32, + PTR_CONST_GdtPtr, FN_OVOID, FN_OUSIZE, FN_OU16, + FN_IU16_OVOID, FN_IU16_OU8, FN_IU4_IU4_OU8, FN_IU8_IU8_OU16, FN_IU16_IU8_OVOID, FN_IU16_IU16_OVOID, + FN_IPTRCONSTGDTPTR_OVOID, }; /// @@ -36,14 +39,17 @@ const DataElement = union(DataElementType) { U8: u8, U16: u16, U32: u32, + PTR_CONST_GdtPtr: *const gdt.GdtPtr, FN_OVOID: fn () void, FN_OUSIZE: fn () usize, FN_OU16: fn () u16, + FN_IU16_OVOID: fn (u16) void, FN_IU16_OU8: fn (u16) u8, FN_IU4_IU4_OU8: fn (u4, u4) u8, FN_IU8_IU8_OU16: fn (u8, u8) u16, FN_IU16_IU8_OVOID: fn (u16, u8) void, FN_IU16_IU16_OVOID: fn (u16, u16) void, + FN_IPTRCONSTGDTPTR_OVOID: fn (*const gdt.GdtPtr) void, }; /// @@ -123,14 +129,17 @@ fn Mock() type { u8 => DataElement{ .U8 = arg }, u16 => DataElement{ .U16 = arg }, u32 => DataElement{ .U32 = arg }, + *const gdt.GdtPtr => DataElement{ .PTR_CONST_GdtPtr = arg }, fn () void => DataElement{ .FN_OVOID = arg }, fn () usize => DataElement{ .FN_OUSIZE = arg }, fn () u16 => DataElement{ .FN_OU16 = arg }, + fn (u16) void => DataElement{ .FN_IU16_OVOID = arg }, fn (u16) u8 => DataElement{ .FN_IU16_OU8 = arg }, fn (u4, u4) u8 => DataElement{ .FN_IU4_IU4_OU8 = arg }, fn (u8, u8) u16 => DataElement{ .FN_IU8_IU8_OU16 = arg }, fn (u16, u8) void => DataElement{ .FN_IU16_IU8_OVOID = arg }, fn (u16, u16) void => DataElement{ .FN_IU16_IU16_OVOID = arg }, + fn (*const gdt.GdtPtr) void => DataElement{ .FN_IPTRCONSTGDTPTR_OVOID = arg }, else => @compileError("Type not supported: " ++ @typeName(@typeOf(arg))), }; } @@ -150,13 +159,16 @@ fn Mock() type { u8 => DataElementType.U8, u16 => DataElementType.U16, u32 => DataElementType.U32, + *const gdt.GdtPtr => DataElement.PTR_CONST_GdtPtr, fn () void => DataElementType.FN_OVOID, fn () u16 => DataElementType.FN_OU16, + fn (u16) void => DataElementType.FN_IU16_OVOID, fn (u16) u8 => DataElementType.FN_IU16_OU8, fn (u4, u4) u8 => DataElementType.FN_IU4_IU4_OU8, fn (u8, u8) u16 => DataElementType.FN_IU8_IU8_OU16, fn (u16, u8) void => DataElementType.FN_IU16_IU8_OVOID, fn (u16, u16) void => DataElementType.FN_IU16_IU16_OVOID, + fn (*const gdt.GdtPtr) void => DataElementType.FN_IPTRCONSTGDTPTR_OVOID, else => @compileError("Type not supported: " ++ @typeName(T)), }; } @@ -178,13 +190,16 @@ fn Mock() type { u8 => element.U8, u16 => element.U16, u32 => element.U32, + *const gdt.GdtPtr => element.PTR_CONST_GdtPtr, fn () void => element.FN_OVOID, fn () u16 => element.FN_OU16, + fn (u16) void => element.FN_IU16_OVOID, fn (u16) u8 => element.FN_IU16_OU8, fn (u4, u4) u8 => element.FN_IU4_IU4_OU8, fn (u8, u8) u16 => element.FN_IU8_IU8_OU16, fn (u16, u8) void => element.FN_IU16_IU8_OVOID, fn (u16, u16) void => element.FN_IU16_IU16_OVOID, + fn (*const gdt.GdtPtr) void => element.FN_IPTRCONSTGDTPTR_OVOID, else => @compileError("Type not supported: " ++ @typeName(T)), }; } diff --git a/test/mock/kernel/mocking.zig b/test/mock/kernel/mocking.zig index c5ed21c..22d8523 100644 --- a/test/mock/kernel/mocking.zig +++ b/test/mock/kernel/mocking.zig @@ -1,5 +1,6 @@ +pub const arch = @import("arch_mock.zig"); +pub const gdt = @import("gdt_mock.zig"); pub const log = @import("log_mock.zig"); pub const mem = @import("mem_mock.zig"); -pub const vga = @import("vga_mock.zig"); -pub const arch = @import("arch_mock.zig"); pub const panic = @import("panic_mock.zig"); +pub const vga = @import("vga_mock.zig"); diff --git a/test/unittests/kernel/test_tty.zig b/test/unittests/kernel/test_tty.zig deleted file mode 100644 index 969a846..0000000 --- a/test/unittests/kernel/test_tty.zig +++ /dev/null @@ -1,4 +0,0 @@ -// I gave up on trying to get all the tests in a separate file for the tty -test "" { - _ = @import("../../../src/kernel/tty.zig"); -} diff --git a/test/unittests/kernel/test_vga.zig b/test/unittests/kernel/test_vga.zig deleted file mode 100644 index e388ff3..0000000 --- a/test/unittests/kernel/test_vga.zig +++ /dev/null @@ -1,293 +0,0 @@ -const vga = @import("../../../src/kernel/vga.zig"); -const arch = @import("../../../src/kernel/arch.zig").internals; - -const expectEqual = @import("std").testing.expectEqual; - -test "entryColour" { - var fg: u4 = vga.COLOUR_BLACK; - var bg: u4 = vga.COLOUR_BLACK; - var res: u8 = vga.entryColour(fg, bg); - expectEqual(u8(0x00), res); - - fg = vga.COLOUR_LIGHT_GREEN; - bg = vga.COLOUR_BLACK; - res = vga.entryColour(fg, bg); - expectEqual(u8(0x0A), res); - - fg = vga.COLOUR_BLACK; - bg = vga.COLOUR_LIGHT_GREEN; - res = vga.entryColour(fg, bg); - expectEqual(u8(0xA0), res); - - fg = vga.COLOUR_BROWN; - bg = vga.COLOUR_LIGHT_GREEN; - res = vga.entryColour(fg, bg); - expectEqual(u8(0xA6), res); -} - -test "entry" { - var colour: u8 = vga.entryColour(vga.COLOUR_BROWN, vga.COLOUR_LIGHT_GREEN); - expectEqual(u8(0xA6), colour); - - // Character '0' is 0x30 - var video_entry: u16 = vga.entry('0', colour); - expectEqual(u16(0xA630), video_entry); - - video_entry = vga.entry(0x55, colour); - expectEqual(u16(0xA655), video_entry); -} - -test "updateCursor width out of bounds" { - const x: u16 = vga.WIDTH; - const y: u16 = 0; - - const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); - const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); - const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); - - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for changing the hardware cursor: - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, - vga.PORT_DATA, expected_lower, - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, - vga.PORT_DATA, expected_upper); - - vga.updateCursor(x, y); -} - -test "updateCursor height out of bounds" { - const x: u16 = 0; - const y: u16 = vga.HEIGHT; - - const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); - const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); - const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); - - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for changing the hardware cursor: - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, - vga.PORT_DATA, expected_lower, - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, - vga.PORT_DATA, expected_upper); - - vga.updateCursor(x, y); -} - -test "updateCursor width and height out of bounds" { - const x: u16 = vga.WIDTH; - const y: u16 = vga.HEIGHT; - - const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); - const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); - const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); - - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for changing the hardware cursor: - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, - vga.PORT_DATA, expected_lower, - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, - vga.PORT_DATA, expected_upper); - - vga.updateCursor(x, y); -} - -test "updateCursor width-1 and height out of bounds" { - const x: u16 = vga.WIDTH - 1; - const y: u16 = vga.HEIGHT; - - const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); - const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); - const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); - - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for changing the hardware cursor: - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, - vga.PORT_DATA, expected_lower, - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, - vga.PORT_DATA, expected_upper); - - vga.updateCursor(x, y); -} - -test "updateCursor width and height-1 out of bounds" { - const x: u16 = vga.WIDTH; - const y: u16 = vga.HEIGHT - 1; - - const max_cursor: u16 = (vga.HEIGHT - 1) * vga.WIDTH + (vga.WIDTH - 1); - const expected_upper: u8 = @truncate(u8, (max_cursor >> 8) & 0x00FF); - const expected_lower: u8 = @truncate(u8, max_cursor & 0x00FF); - - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for changing the hardware cursor: - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, - vga.PORT_DATA, expected_lower, - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, - vga.PORT_DATA, expected_upper); - - vga.updateCursor(x, y); -} - -test "updateCursor in bounds" { - var x: u16 = 0x000A; - var y: u16 = 0x000A; - const expected: u16 = y * vga.WIDTH + x; - - var expected_upper: u8 = @truncate(u8, (expected >> 8) & 0x00FF); - var expected_lower: u8 = @truncate(u8, expected & 0x00FF); - - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for changing the hardware cursor: - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW, - vga.PORT_DATA, expected_lower, - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH, - vga.PORT_DATA, expected_upper); - vga.updateCursor(x, y); -} - -test "getCursor 1: 10" { - const expect: u16 = u16(10); - - // Mocking out the arch.outb and arch.inb calls for getting the hardware cursor: - arch.initTest(); - defer arch.freeTest(); - - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW); - - arch.addTestParams("inb", - vga.PORT_DATA, u8(10)); - - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH); - - arch.addTestParams("inb", - vga.PORT_DATA, u8(0)); - - const actual: u16 = vga.getCursor(); - expectEqual(expect, actual); -} - -test "getCursor 2: 0xBEEF" { - const expect: u16 = u16(0xBEEF); - - // Mocking out the arch.outb and arch.inb calls for getting the hardware cursor: - arch.initTest(); - defer arch.freeTest(); - - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_LOW); - - arch.addTestParams("inb", - vga.PORT_DATA, u8(0xEF)); - - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_LOCATION_HIGH); - - arch.addTestParams("inb", - vga.PORT_DATA, u8(0xBE)); - - const actual: u16 = vga.getCursor(); - expectEqual(expect, actual); -} - -test "enableCursor all" { - arch.initTest(); - defer arch.freeTest(); - - // Need to init the cursor start and end positions, so call the vga.init() to set this up - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_MAXIMUM_SCAN_LINE, - vga.PORT_DATA, vga.CURSOR_SCANLINE_END, - vga.PORT_ADDRESS, vga.REG_CURSOR_START, - vga.PORT_DATA, vga.CURSOR_SCANLINE_MIDDLE, - vga.PORT_ADDRESS, vga.REG_CURSOR_END, - vga.PORT_DATA, vga.CURSOR_SCANLINE_END, - // Mocking out the arch.outb calls for enabling the cursor: - // These are the default cursor positions from vga.init() - vga.PORT_ADDRESS, vga.REG_CURSOR_START, - vga.PORT_DATA, vga.CURSOR_SCANLINE_MIDDLE, - vga.PORT_ADDRESS, vga.REG_CURSOR_END, - vga.PORT_DATA, vga.CURSOR_SCANLINE_END); - - vga.init(); - vga.enableCursor(); -} - -test "disableCursor all" { - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for disabling the cursor: - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_START, - vga.PORT_DATA, vga.CURSOR_DISABLE); - vga.disableCursor(); -} - -test "setCursorShape UNDERLINE" { - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for setting the cursor shape to underline: - // This will also check that the scan line variables were set properly as these are using in - // the arch.outb call - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_START, - vga.PORT_DATA, vga.CURSOR_SCANLINE_MIDDLE, - vga.PORT_ADDRESS, vga.REG_CURSOR_END, - vga.PORT_DATA, vga.CURSOR_SCANLINE_END); - - vga.setCursorShape(vga.CursorShape.UNDERLINE); -} - -test "setCursorShape BLOCK" { - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for setting the cursor shape to block: - // This will also check that the scan line variables were set properly as these are using in - // the arch.outb call - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_CURSOR_START, - vga.PORT_DATA, vga.CURSOR_SCANLINE_START, - vga.PORT_ADDRESS, vga.REG_CURSOR_END, - vga.PORT_DATA, vga.CURSOR_SCANLINE_END); - - vga.setCursorShape(vga.CursorShape.BLOCK); -} - -test "init all" { - arch.initTest(); - defer arch.freeTest(); - - // Mocking out the arch.outb calls for setting the cursor max scan line and the shape to block: - // This will also check that the scan line variables were set properly as these are using in - // the arch.outb call for setting the cursor shape. - arch.addTestParams("outb", - vga.PORT_ADDRESS, vga.REG_MAXIMUM_SCAN_LINE, - vga.PORT_DATA, vga.CURSOR_SCANLINE_END, - vga.PORT_ADDRESS, vga.REG_CURSOR_START, - vga.PORT_DATA, vga.CURSOR_SCANLINE_MIDDLE, - vga.PORT_ADDRESS, vga.REG_CURSOR_END, - vga.PORT_DATA, vga.CURSOR_SCANLINE_END); - - vga.init(); -} diff --git a/test/unittests/test_all.zig b/test/unittests/test_all.zig deleted file mode 100644 index ef4006f..0000000 --- a/test/unittests/test_all.zig +++ /dev/null @@ -1,4 +0,0 @@ -test "all" { - _ = @import("kernel/test_vga.zig"); - _ = @import("kernel/test_tty.zig"); -}