Merge pull request #78 from SamTebbs33/feature/adding-tests-for-gdt

Feature/adding tests for gdt
This commit is contained in:
Edward Dean 2019-09-16 22:53:56 +01:00 committed by GitHub
commit 9d9c5d6a39
18 changed files with 1141 additions and 590 deletions

View file

@ -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",
};
}

View file

@ -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,
};

View file

@ -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();
}

View file

@ -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

View file

@ -1 +1,2 @@
/// The virtual address where the kernel will be loaded. This is at 3GB.
pub const KERNEL_ADDR_OFFSET = 0xC0000000;

View file

@ -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
View 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");

View file

@ -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();

View file

@ -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

View file

@ -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();
}

View file

@ -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"]),

View file

@ -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);
}

View 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);
}

View file

@ -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)),
};
}

View file

@ -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");

View file

@ -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");
}

View file

@ -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();
}

View file

@ -1,4 +0,0 @@
test "all" {
_ = @import("kernel/test_vga.zig");
_ = @import("kernel/test_tty.zig");
}