Added gdt unit and runtime tests
Updated runtime tests Added doc comments for runtime tests PR review WIP Fixed testing Import GDT to run the unit tests Removed redundant arch tests Removed whitespace
This commit is contained in:
parent
9c35de8673
commit
07cc1ae89b
18 changed files with 1141 additions and 590 deletions
60
build.zig
60
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",
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
/// The virtual address where the kernel will be loaded. This is at 3GB.
|
||||
pub const KERNEL_ADDR_OFFSET = 0xC0000000;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
7
src/kernel/kernel.zig
Normal file
7
src/kernel/kernel.zig
Normal file
|
@ -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");
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,56 +1,58 @@
|
|||
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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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"]),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
169
test/mock/kernel/gdt_mock.zig
Normal file
169
test/mock/kernel/gdt_mock.zig
Normal file
|
@ -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);
|
||||
}
|
|
@ -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)),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
test "all" {
|
||||
_ = @import("kernel/test_vga.zig");
|
||||
_ = @import("kernel/test_tty.zig");
|
||||
}
|
Loading…
Reference in a new issue