Merge pull request #138 from SamTebbs33/feature/initial_multitasking

Initial scheduler
This commit is contained in:
Edward Dean 2020-07-18 23:48:51 +01:00 committed by GitHub
commit 7e0c1fd589
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1127 additions and 395 deletions

View file

@ -37,3 +37,5 @@ jobs:
run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Initialisation ${{ matrix.build_mode }} run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Initialisation ${{ matrix.build_mode }}
- name: Run runtime test - Panic - name: Run runtime test - Panic
run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Panic ${{ matrix.build_mode }} run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Panic ${{ matrix.build_mode }}
- name: Run runtime test - Scheduler
run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Scheduler ${{ matrix.build_mode }}

3
.gitignore vendored
View file

@ -38,6 +38,9 @@
# Intellij # Intellij
.idea/ .idea/
# VSCode
.vscode/
# Zig # Zig
zig-cache zig-cache

View file

@ -3,8 +3,6 @@ const builtin = @import("builtin");
const rt = @import("test/runtime_test.zig"); const rt = @import("test/runtime_test.zig");
const RuntimeStep = rt.RuntimeStep; const RuntimeStep = rt.RuntimeStep;
const Builder = std.build.Builder; const Builder = std.build.Builder;
const LibExeObjStep = std.build.LibExeObjStep;
const Step = std.build.Step;
const Target = std.Target; const Target = std.Target;
const CrossTarget = std.zig.CrossTarget; const CrossTarget = std.zig.CrossTarget;
const fs = std.fs; const fs = std.fs;

View file

@ -1,8 +1,10 @@
const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const is_test = builtin.is_test; const is_test = builtin.is_test;
const build_options = @import("build_options"); const build_options = @import("build_options");
const mock_path = build_options.mock_path;
pub const internals = if (is_test) @import(build_options.mock_path ++ "arch_mock.zig") else switch (builtin.arch) { pub const internals = if (is_test) @import(mock_path ++ "arch_mock.zig") else switch (builtin.arch) {
.i386 => @import("arch/x86/arch.zig"), .i386 => @import("arch/x86/arch.zig"),
else => unreachable, else => unreachable,
}; };

View file

@ -1,47 +1,54 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const builtin = @import("builtin"); const builtin = @import("builtin");
const cmos = @import("cmos.zig");
const gdt = @import("gdt.zig"); const gdt = @import("gdt.zig");
const idt = @import("idt.zig"); const idt = @import("idt.zig");
const pic = @import("pic.zig");
const irq = @import("irq.zig"); const irq = @import("irq.zig");
const isr = @import("isr.zig"); const isr = @import("isr.zig");
const paging = @import("paging.zig");
const pic = @import("pic.zig");
const pit = @import("pit.zig"); const pit = @import("pit.zig");
const rtc = @import("rtc.zig"); const rtc = @import("rtc.zig");
const serial = @import("serial.zig"); const serial = @import("serial.zig");
const paging = @import("paging.zig");
const syscalls = @import("syscalls.zig"); const syscalls = @import("syscalls.zig");
const mem = @import("../../mem.zig");
const multiboot = @import("multiboot.zig");
const pmm = @import("pmm.zig");
const vmm = @import("../../vmm.zig");
const log = @import("../../log.zig");
const tty = @import("tty.zig"); const tty = @import("tty.zig");
const vga = @import("vga.zig"); const vga = @import("vga.zig");
const mem = @import("../../mem.zig");
const multiboot = @import("multiboot.zig");
const vmm = @import("../../vmm.zig");
const log = @import("../../log.zig");
const Serial = @import("../../serial.zig").Serial; const Serial = @import("../../serial.zig").Serial;
const panic = @import("../../panic.zig").panic; const panic = @import("../../panic.zig").panic;
const TTY = @import("../../tty.zig").TTY; const TTY = @import("../../tty.zig").TTY;
const MemProfile = mem.MemProfile; const MemProfile = mem.MemProfile;
/// The virtual end of the kernel code /// The virtual end of the kernel code.
extern var KERNEL_VADDR_END: *u32; extern var KERNEL_VADDR_END: *u32;
/// The virtual start of the kernel code /// The virtual start of the kernel code.
extern var KERNEL_VADDR_START: *u32; extern var KERNEL_VADDR_START: *u32;
/// The physical end of the kernel code /// The physical end of the kernel code.
extern var KERNEL_PHYSADDR_END: *u32; extern var KERNEL_PHYSADDR_END: *u32;
/// The physical start of the kernel code /// The physical start of the kernel code.
extern var KERNEL_PHYSADDR_START: *u32; extern var KERNEL_PHYSADDR_START: *u32;
/// The boot-time offset that the virtual addresses are from the physical addresses /// The boot-time offset that the virtual addresses are from the physical addresses.
extern var KERNEL_ADDR_OFFSET: *u32; extern var KERNEL_ADDR_OFFSET: *u32;
/// The virtual address of the top limit of the stack.
extern var KERNEL_STACK_START: *u32;
/// The virtual address of the base of the stack.
extern var KERNEL_STACK_END: *u32;
/// The interrupt context that is given to a interrupt handler. It contains most of the registers /// 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). /// and the interrupt number and error code (if there is one).
pub const InterruptContext = struct { pub const CpuState = packed struct {
// Extra segments // Extra segments
ss: u32,
gs: u32, gs: u32,
fs: u32, fs: u32,
es: u32, es: u32,
@ -68,7 +75,7 @@ pub const InterruptContext = struct {
cs: u32, cs: u32,
eflags: u32, eflags: u32,
user_esp: u32, user_esp: u32,
ss: u32, user_ss: u32,
}; };
/// x86's boot payload is the multiboot info passed by grub /// x86's boot payload is the multiboot info passed by grub
@ -89,6 +96,9 @@ pub const VMM_MAPPER: vmm.Mapper(VmmPayload) = vmm.Mapper(VmmPayload){ .mapFn =
/// The size of each allocatable block of memory, normally set to the page size. /// The size of each allocatable block of memory, normally set to the page size.
pub const MEMORY_BLOCK_SIZE: usize = paging.PAGE_SIZE_4KB; pub const MEMORY_BLOCK_SIZE: usize = paging.PAGE_SIZE_4KB;
/// The default stack size of a task. Currently this is set to a page size.
pub const STACK_SIZE: u32 = MEMORY_BLOCK_SIZE / @sizeOf(u32);
/// ///
/// Assembly to write to a given port with a byte of data. /// Assembly to write to a given port with a byte of data.
/// ///
@ -239,10 +249,9 @@ pub fn halt() void {
/// Wait the kernel but still can handle interrupts. /// Wait the kernel but still can handle interrupts.
/// ///
pub fn spinWait() noreturn { pub fn spinWait() noreturn {
while (true) {
enableInterrupts(); enableInterrupts();
while (true) {
halt(); halt();
disableInterrupts();
} }
} }
@ -312,13 +321,21 @@ pub fn initTTY(boot_payload: BootPayload) TTY {
/// Return: mem.MemProfile /// Return: mem.MemProfile
/// The constructed memory profile /// The constructed memory profile
/// ///
/// Error: std.mem.Allocator.Error /// Error: Allocator.Error
/// std.mem.Allocator.Error.OutOfMemory - There wasn't enough memory in the allocated created to populate the memory profile, consider increasing mem.FIXED_ALLOC_SIZE /// Allocator.Error.OutOfMemory - There wasn't enough memory in the allocated created to populate the memory profile, consider increasing mem.FIXED_ALLOC_SIZE
/// ///
pub fn initMem(mb_info: BootPayload) std.mem.Allocator.Error!MemProfile { pub fn initMem(mb_info: BootPayload) Allocator.Error!MemProfile {
log.logInfo("Init mem\n", .{}); log.logInfo("Init mem\n", .{});
defer log.logInfo("Done mem\n", .{}); defer log.logInfo("Done mem\n", .{});
log.logDebug("KERNEL_ADDR_OFFSET: 0x{X}\n", .{@ptrToInt(&KERNEL_ADDR_OFFSET)});
log.logDebug("KERNEL_STACK_START: 0x{X}\n", .{@ptrToInt(&KERNEL_STACK_START)});
log.logDebug("KERNEL_STACK_END: 0x{X}\n", .{@ptrToInt(&KERNEL_STACK_END)});
log.logDebug("KERNEL_VADDR_START: 0x{X}\n", .{@ptrToInt(&KERNEL_VADDR_START)});
log.logDebug("KERNEL_VADDR_END: 0x{X}\n", .{@ptrToInt(&KERNEL_VADDR_END)});
log.logDebug("KERNEL_PHYSADDR_START: 0x{X}\n", .{@ptrToInt(&KERNEL_PHYSADDR_START)});
log.logDebug("KERNEL_PHYSADDR_END: 0x{X}\n", .{@ptrToInt(&KERNEL_PHYSADDR_END)});
const mods_count = mb_info.mods_count; const mods_count = mb_info.mods_count;
mem.ADDR_OFFSET = @ptrToInt(&KERNEL_ADDR_OFFSET); mem.ADDR_OFFSET = @ptrToInt(&KERNEL_ADDR_OFFSET);
const mmap_addr = mb_info.mmap_addr; const mmap_addr = mb_info.mmap_addr;
@ -338,6 +355,7 @@ pub fn initMem(mb_info: BootPayload) std.mem.Allocator.Error!MemProfile {
try reserved_physical_mem.append(.{ .start = @intCast(usize, entry.addr), .end = end }); try reserved_physical_mem.append(.{ .start = @intCast(usize, entry.addr), .end = end });
} }
} }
// Map the multiboot info struct itself // Map the multiboot info struct itself
const mb_region = mem.Range{ const mb_region = mem.Range{
.start = @ptrToInt(mb_info), .start = @ptrToInt(mb_info),
@ -384,19 +402,63 @@ pub fn initMem(mb_info: BootPayload) std.mem.Allocator.Error!MemProfile {
}; };
} }
///
/// Initialise a 32bit kernel stack used for creating a task.
/// Currently only support fn () noreturn functions for the entry point.
///
/// Arguments:
/// IN entry_point: usize - The pointer to the entry point of the function. Functions only
/// supported is fn () noreturn
/// IN allocator: *Allocator - The allocator use for allocating a stack.
///
/// Return: struct { stack: []u32, pointer: usize }
/// The stack and stack pointer with the stack initialised as a 32bit kernel stack.
///
/// Error: Allocator.Error
/// OutOfMemory - Unable to allocate space for the stack.
///
pub fn initTaskStack(entry_point: usize, allocator: *Allocator) Allocator.Error!struct { stack: []u32, pointer: usize } {
// TODO Will need to add the exit point
// Set up everything as a kernel task
var stack = try allocator.alloc(u32, STACK_SIZE);
stack[STACK_SIZE - 18] = gdt.KERNEL_DATA_OFFSET; // ss
stack[STACK_SIZE - 17] = gdt.KERNEL_DATA_OFFSET; // gs
stack[STACK_SIZE - 16] = gdt.KERNEL_DATA_OFFSET; // fs
stack[STACK_SIZE - 15] = gdt.KERNEL_DATA_OFFSET; // es
stack[STACK_SIZE - 14] = gdt.KERNEL_DATA_OFFSET; // ds
stack[STACK_SIZE - 13] = 0; // edi
stack[STACK_SIZE - 12] = 0; // esi
// End of the stack
stack[STACK_SIZE - 11] = @ptrToInt(&stack[STACK_SIZE - 1]); // ebp
stack[STACK_SIZE - 10] = 0; // esp (temp) this won't be popped by popa bc intel is dump XD
stack[STACK_SIZE - 9] = 0; // ebx
stack[STACK_SIZE - 8] = 0; // edx
stack[STACK_SIZE - 7] = 0; // ecx
stack[STACK_SIZE - 6] = 0; // eax
stack[STACK_SIZE - 5] = 0; // int_num
stack[STACK_SIZE - 4] = 0; // error_code
stack[STACK_SIZE - 3] = entry_point; // eip
stack[STACK_SIZE - 2] = gdt.KERNEL_CODE_OFFSET; // cs
stack[STACK_SIZE - 1] = 0x202; // eflags
const ret = .{ .stack = stack, .pointer = @ptrToInt(&stack[STACK_SIZE - 18]) };
return ret;
}
/// ///
/// Initialise the architecture /// Initialise the architecture
/// ///
/// Arguments: /// Arguments:
/// IN boot_payload: BootPayload - The multiboot information from the GRUB bootloader.
/// IN mem_profile: *const MemProfile - The memory profile of the computer. Used to set up /// IN mem_profile: *const MemProfile - The memory profile of the computer. Used to set up
/// paging. /// paging.
/// IN allocator: *Allocator - The allocator use to handle memory. /// 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 init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile, allocator: *Allocator) void { pub fn init(boot_payload: BootPayload, mem_profile: *const MemProfile, allocator: *Allocator) void {
disableInterrupts();
gdt.init(); gdt.init();
idt.init(); idt.init();
@ -404,15 +466,13 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile
isr.init(); isr.init();
irq.init(); irq.init();
paging.init(mb_info, mem_profile, allocator); paging.init(boot_payload, mem_profile, allocator);
pit.init(); pit.init();
rtc.init(); rtc.init();
syscalls.init(); syscalls.init();
enableInterrupts();
// Initialise the VGA and TTY here since their tests belong the architecture and so should be a part of the // Initialise the VGA and TTY here since their tests belong the architecture and so should be a part of the
// arch init test messages // arch init test messages
vga.init(); vga.init();
@ -420,17 +480,5 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile
} }
test "" { test "" {
_ = @import("gdt.zig"); std.meta.refAllDecls(@This());
_ = @import("idt.zig");
_ = @import("pic.zig");
_ = @import("isr.zig");
_ = @import("irq.zig");
_ = @import("pit.zig");
_ = @import("cmos.zig");
_ = @import("rtc.zig");
_ = @import("syscalls.zig");
_ = @import("paging.zig");
_ = @import("serial.zig");
_ = @import("tty.zig");
_ = @import("vga.zig");
} }

View file

@ -1,5 +1,5 @@
const constants = @import("constants"); const constants = @import("constants");
const multiboot_info = @import("multiboot.zig").multiboot_info_t; const arch = @import("arch.zig");
/// The multiboot header /// The multiboot header
const MultiBoot = packed struct { const MultiBoot = packed struct {
@ -66,7 +66,7 @@ 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; export var kernel_stack: [16 * 1024]u8 align(16) linksection(".bss.stack") = undefined;
extern var KERNEL_ADDR_OFFSET: *u32; extern var KERNEL_ADDR_OFFSET: *u32;
extern fn kmain(mb_info: *multiboot_info) void; extern fn kmain(mb_info: arch.BootPayload) void;
export fn _start() align(16) linksection(".text.boot") callconv(.Naked) noreturn { export fn _start() align(16) linksection(".text.boot") callconv(.Naked) noreturn {
// Set the page directory to the boot directory // Set the page directory to the boot directory
@ -109,6 +109,6 @@ export fn start_higher_half() callconv(.Naked) noreturn {
\\mov %%ebx, %[res] \\mov %%ebx, %[res]
: [res] "=r" (-> usize) : [res] "=r" (-> usize)
) + @ptrToInt(&KERNEL_ADDR_OFFSET); ) + @ptrToInt(&KERNEL_ADDR_OFFSET);
kmain(@intToPtr(*multiboot_info, mb_info_addr)); kmain(@intToPtr(arch.BootPayload, mb_info_addr));
while (true) {} while (true) {}
} }

View file

@ -84,27 +84,31 @@ const GdtEntry = packed struct {
}; };
/// The TSS entry structure /// The TSS entry structure
const TtsEntry = packed struct { const Tss = packed struct {
/// Pointer to the previous TSS entry /// Pointer to the previous TSS entry
prev_tss: u32, prev_tss: u16,
reserved1: u16,
/// Ring 0 32 bit stack pointer. /// Ring 0 32 bit stack pointer.
esp0: u32, esp0: u32,
/// Ring 0 32 bit stack pointer. /// Ring 0 32 bit stack pointer.
ss0: u32, ss0: u16,
reserved2: u16,
/// Ring 1 32 bit stack pointer. /// Ring 1 32 bit stack pointer.
esp1: u32, esp1: u32,
/// Ring 1 32 bit stack pointer. /// Ring 1 32 bit stack pointer.
ss1: u32, ss1: u16,
reserved3: u16,
/// Ring 2 32 bit stack pointer. /// Ring 2 32 bit stack pointer.
esp2: u32, esp2: u32,
/// Ring 2 32 bit stack pointer. /// Ring 2 32 bit stack pointer.
ss2: u32, ss2: u16,
reserved4: u16,
/// The CR3 control register 3. /// The CR3 control register 3.
cr3: u32, cr3: u32,
@ -140,25 +144,32 @@ const TtsEntry = packed struct {
edi: u32, edi: u32,
/// The extra segment. /// The extra segment.
es: u32, es: u16,
reserved5: u16,
/// The code segment. /// The code segment.
cs: u32, cs: u16,
reserved6: u16,
/// The stack segment. /// The stack segment.
ss: u32, ss: u16,
reserved7: u16,
/// The data segment. /// The data segment.
ds: u32, ds: u16,
reserved8: u16,
/// A extra segment FS. /// A extra segment FS.
fs: u32, fs: u16,
reserved9: u16,
/// A extra segment GS. /// A extra segment GS.
gs: u32, gs: u16,
reserved10: u16,
/// The local descriptor table register. /// The local descriptor table register.
ldtr: u32, ldtr: u16,
reserved11: u16,
/// ? /// ?
trap: u16, trap: u16,
@ -177,8 +188,8 @@ pub const GdtPtr = packed struct {
base: u32, base: u32,
}; };
/// The total number of entries in the GTD: null, kernel code, kernel data, user code, user data /// The total number of entries in the GDT including: null, kernel code, kernel data, user code,
/// and TSS /// user data and the TSS.
const NUMBER_OF_ENTRIES: u16 = 0x06; const NUMBER_OF_ENTRIES: u16 = 0x06;
/// The size of the GTD in bytes (minus 1). /// The size of the GTD in bytes (minus 1).
@ -315,24 +326,28 @@ pub const USER_DATA_OFFSET: u16 = 0x20;
pub const TSS_OFFSET: u16 = 0x28; pub const TSS_OFFSET: u16 = 0x28;
/// The GDT entry table of NUMBER_OF_ENTRIES entries. /// The GDT entry table of NUMBER_OF_ENTRIES entries.
var gdt_entries: [NUMBER_OF_ENTRIES]GdtEntry = [_]GdtEntry{ var gdt_entries: [NUMBER_OF_ENTRIES]GdtEntry = init: {
var gdt_entries_temp: [NUMBER_OF_ENTRIES]GdtEntry = undefined;
// Null descriptor // Null descriptor
makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS), gdt_entries_temp[0] = makeGdtEntry(0, 0, NULL_SEGMENT, NULL_FLAGS);
// Kernel Code // Kernel code descriptor
makeEntry(0, 0xFFFFF, KERNEL_SEGMENT_CODE, PAGING_32_BIT), gdt_entries_temp[1] = makeGdtEntry(0, 0xFFFFF, KERNEL_SEGMENT_CODE, PAGING_32_BIT);
// Kernel Data // Kernel data descriptor
makeEntry(0, 0xFFFFF, KERNEL_SEGMENT_DATA, PAGING_32_BIT), gdt_entries_temp[2] = makeGdtEntry(0, 0xFFFFF, KERNEL_SEGMENT_DATA, PAGING_32_BIT);
// User Code // User code descriptor
makeEntry(0, 0xFFFFF, USER_SEGMENT_CODE, PAGING_32_BIT), gdt_entries_temp[3] = makeGdtEntry(0, 0xFFFFF, USER_SEGMENT_CODE, PAGING_32_BIT);
// User Data // User data descriptor
makeEntry(0, 0xFFFFF, USER_SEGMENT_DATA, PAGING_32_BIT), gdt_entries_temp[4] = makeGdtEntry(0, 0xFFFFF, USER_SEGMENT_DATA, PAGING_32_BIT);
// Fill in TSS at runtime // TSS descriptor, one each for each processor
makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS), // Will initialise the TSS at runtime
gdt_entries_temp[5] = makeGdtEntry(0, 0, NULL_SEGMENT, NULL_FLAGS);
break :init gdt_entries_temp;
}; };
/// The GDT pointer that the CPU is loaded with that contains the base address of the GDT and the /// The GDT pointer that the CPU is loaded with that contains the base address of the GDT and the
@ -342,35 +357,12 @@ var gdt_ptr: GdtPtr = GdtPtr{
.base = undefined, .base = undefined,
}; };
/// The task state segment entry. /// The main task state segment entry.
var tss: TtsEntry = TtsEntry{ var main_tss_entry: Tss = init: {
.prev_tss = 0, var tss_temp = std.mem.zeroes(Tss);
.esp0 = 0, tss_temp.ss0 = KERNEL_DATA_OFFSET;
.ss0 = KERNEL_DATA_OFFSET, tss_temp.io_permissions_base_offset = @sizeOf(Tss);
.esp1 = 0, break :init tss_temp;
.ss1 = 0,
.esp2 = 0,
.ss2 = 0,
.cr3 = 0,
.eip = 0,
.eflags = 0,
.eax = 0,
.ecx = 0,
.edx = 0,
.ebx = 0,
.esp = 0,
.ebp = 0,
.esi = 0,
.edi = 0,
.es = 0,
.cs = 0,
.ss = 0,
.ds = 0,
.fs = 0,
.gs = 0,
.ldtr = 0,
.trap = 0,
.io_permissions_base_offset = @sizeOf(TtsEntry),
}; };
/// ///
@ -386,11 +378,11 @@ var tss: TtsEntry = TtsEntry{
/// A new GDT entry with the give access and flag bits set with the base at 0x00000000 and /// A new GDT entry with the give access and flag bits set with the base at 0x00000000 and
/// limit at 0xFFFFF. /// limit at 0xFFFFF.
/// ///
fn makeEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntry { fn makeGdtEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntry {
return GdtEntry{ return .{
.limit_low = @truncate(u16, limit), .limit_low = @truncate(u16, limit),
.base_low = @truncate(u24, base), .base_low = @truncate(u24, base),
.access = AccessBits{ .access = .{
.accessed = access.accessed, .accessed = access.accessed,
.read_write = access.read_write, .read_write = access.read_write,
.direction_conforming = access.direction_conforming, .direction_conforming = access.direction_conforming,
@ -400,7 +392,7 @@ fn makeEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntr
.present = access.present, .present = access.present,
}, },
.limit_high = @truncate(u4, limit >> 16), .limit_high = @truncate(u4, limit >> 16),
.flags = FlagBits{ .flags = .{
.reserved_zero = flags.reserved_zero, .reserved_zero = flags.reserved_zero,
.is_64_bit = flags.is_64_bit, .is_64_bit = flags.is_64_bit,
.is_32_bit = flags.is_32_bit, .is_32_bit = flags.is_32_bit,
@ -410,16 +402,6 @@ fn makeEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntr
}; };
} }
///
/// 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.
/// ///
@ -427,7 +409,7 @@ pub fn init() void {
log.logInfo("Init gdt\n", .{}); log.logInfo("Init gdt\n", .{});
defer log.logInfo("Done gdt\n", .{}); defer log.logInfo("Done gdt\n", .{});
// Initiate TSS // Initiate TSS
gdt_entries[TSS_INDEX] = makeEntry(@ptrToInt(&tss), @sizeOf(TtsEntry) - 1, TSS_SEGMENT, NULL_FLAGS); gdt_entries[TSS_INDEX] = makeGdtEntry(@ptrToInt(&main_tss_entry), @sizeOf(Tss) - 1, TSS_SEGMENT, NULL_FLAGS);
// Set the base address where all the GDT entries are. // Set the base address where all the GDT entries are.
gdt_ptr.base = @ptrToInt(&gdt_entries[0]); gdt_ptr.base = @ptrToInt(&gdt_entries[0]);
@ -453,7 +435,7 @@ test "GDT entries" {
expectEqual(@as(u32, 1), @sizeOf(AccessBits)); expectEqual(@as(u32, 1), @sizeOf(AccessBits));
expectEqual(@as(u32, 1), @sizeOf(FlagBits)); expectEqual(@as(u32, 1), @sizeOf(FlagBits));
expectEqual(@as(u32, 8), @sizeOf(GdtEntry)); expectEqual(@as(u32, 8), @sizeOf(GdtEntry));
expectEqual(@as(u32, 104), @sizeOf(TtsEntry)); expectEqual(@as(u32, 104), @sizeOf(Tss));
expectEqual(@as(u32, 6), @sizeOf(GdtPtr)); expectEqual(@as(u32, 6), @sizeOf(GdtPtr));
const null_entry = gdt_entries[NULL_INDEX]; const null_entry = gdt_entries[NULL_INDEX];
@ -476,45 +458,45 @@ test "GDT entries" {
expectEqual(TABLE_SIZE, gdt_ptr.limit); expectEqual(TABLE_SIZE, gdt_ptr.limit);
expectEqual(@as(u32, 0), tss.prev_tss); expectEqual(@as(u32, 0), main_tss_entry.prev_tss);
expectEqual(@as(u32, 0), tss.esp0); expectEqual(@as(u32, 0), main_tss_entry.esp0);
expectEqual(@as(u32, KERNEL_DATA_OFFSET), tss.ss0); expectEqual(@as(u32, KERNEL_DATA_OFFSET), main_tss_entry.ss0);
expectEqual(@as(u32, 0), tss.esp1); expectEqual(@as(u32, 0), main_tss_entry.esp1);
expectEqual(@as(u32, 0), tss.ss1); expectEqual(@as(u32, 0), main_tss_entry.ss1);
expectEqual(@as(u32, 0), tss.esp2); expectEqual(@as(u32, 0), main_tss_entry.esp2);
expectEqual(@as(u32, 0), tss.ss2); expectEqual(@as(u32, 0), main_tss_entry.ss2);
expectEqual(@as(u32, 0), tss.cr3); expectEqual(@as(u32, 0), main_tss_entry.cr3);
expectEqual(@as(u32, 0), tss.eip); expectEqual(@as(u32, 0), main_tss_entry.eip);
expectEqual(@as(u32, 0), tss.eflags); expectEqual(@as(u32, 0), main_tss_entry.eflags);
expectEqual(@as(u32, 0), tss.eax); expectEqual(@as(u32, 0), main_tss_entry.eax);
expectEqual(@as(u32, 0), tss.ecx); expectEqual(@as(u32, 0), main_tss_entry.ecx);
expectEqual(@as(u32, 0), tss.edx); expectEqual(@as(u32, 0), main_tss_entry.edx);
expectEqual(@as(u32, 0), tss.ebx); expectEqual(@as(u32, 0), main_tss_entry.ebx);
expectEqual(@as(u32, 0), tss.esp); expectEqual(@as(u32, 0), main_tss_entry.esp);
expectEqual(@as(u32, 0), tss.ebp); expectEqual(@as(u32, 0), main_tss_entry.ebp);
expectEqual(@as(u32, 0), tss.esi); expectEqual(@as(u32, 0), main_tss_entry.esi);
expectEqual(@as(u32, 0), tss.edi); expectEqual(@as(u32, 0), main_tss_entry.edi);
expectEqual(@as(u32, 0), tss.es); expectEqual(@as(u32, 0), main_tss_entry.es);
expectEqual(@as(u32, 0), tss.cs); expectEqual(@as(u32, 0), main_tss_entry.cs);
expectEqual(@as(u32, 0), tss.ss); expectEqual(@as(u32, 0), main_tss_entry.ss);
expectEqual(@as(u32, 0), tss.ds); expectEqual(@as(u32, 0), main_tss_entry.ds);
expectEqual(@as(u32, 0), tss.fs); expectEqual(@as(u32, 0), main_tss_entry.fs);
expectEqual(@as(u32, 0), tss.gs); expectEqual(@as(u32, 0), main_tss_entry.gs);
expectEqual(@as(u32, 0), tss.ldtr); expectEqual(@as(u32, 0), main_tss_entry.ldtr);
expectEqual(@as(u16, 0), tss.trap); expectEqual(@as(u16, 0), main_tss_entry.trap);
// Size of TtsEntry will fit in a u16 as 104 < 65535 (2^16) // Size of Tss will fit in a u16 as 104 < 65535 (2^16)
expectEqual(@as(u16, @sizeOf(TtsEntry)), tss.io_permissions_base_offset); expectEqual(@as(u16, @sizeOf(Tss)), main_tss_entry.io_permissions_base_offset);
} }
test "makeEntry NULL" { test "makeGdtEntry NULL" {
const actual = makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS); const actual = makeGdtEntry(0, 0, NULL_SEGMENT, NULL_FLAGS);
const expected: u64 = 0; const expected: u64 = 0;
expectEqual(expected, @bitCast(u64, actual)); expectEqual(expected, @bitCast(u64, actual));
} }
test "makeEntry alternating bit pattern" { test "makeGdtEntry alternating bit pattern" {
const alt_access = AccessBits{ const alt_access = AccessBits{
.accessed = 1, .accessed = 1,
.read_write = 0, .read_write = 0,
@ -536,106 +518,12 @@ test "makeEntry alternating bit pattern" {
expectEqual(@as(u4, 0b0101), @bitCast(u4, alt_flag)); expectEqual(@as(u4, 0b0101), @bitCast(u4, alt_flag));
const actual = makeEntry(0b01010101010101010101010101010101, 0b01010101010101010101, alt_access, alt_flag); const actual = makeGdtEntry(0b01010101010101010101010101010101, 0b01010101010101010101, alt_access, alt_flag);
const expected: u64 = 0b0101010101010101010101010101010101010101010101010101010101010101; const expected: u64 = 0b0101010101010101010101010101010101010101010101010101010101010101;
expectEqual(expected, @bitCast(u64, actual)); expectEqual(expected, @bitCast(u64, actual));
} }
test "setTssStack" {
// Pre-testing
expectEqual(@as(u32, 0), tss.prev_tss);
expectEqual(@as(u32, 0), tss.esp0);
expectEqual(@as(u32, KERNEL_DATA_OFFSET), tss.ss0);
expectEqual(@as(u32, 0), tss.esp1);
expectEqual(@as(u32, 0), tss.ss1);
expectEqual(@as(u32, 0), tss.esp2);
expectEqual(@as(u32, 0), tss.ss2);
expectEqual(@as(u32, 0), tss.cr3);
expectEqual(@as(u32, 0), tss.eip);
expectEqual(@as(u32, 0), tss.eflags);
expectEqual(@as(u32, 0), tss.eax);
expectEqual(@as(u32, 0), tss.ecx);
expectEqual(@as(u32, 0), tss.edx);
expectEqual(@as(u32, 0), tss.ebx);
expectEqual(@as(u32, 0), tss.esp);
expectEqual(@as(u32, 0), tss.ebp);
expectEqual(@as(u32, 0), tss.esi);
expectEqual(@as(u32, 0), tss.edi);
expectEqual(@as(u32, 0), tss.es);
expectEqual(@as(u32, 0), tss.cs);
expectEqual(@as(u32, 0), tss.ss);
expectEqual(@as(u32, 0), tss.ds);
expectEqual(@as(u32, 0), tss.fs);
expectEqual(@as(u32, 0), tss.gs);
expectEqual(@as(u32, 0), tss.ldtr);
expectEqual(@as(u16, 0), tss.trap);
expectEqual(@as(u16, @sizeOf(TtsEntry)), tss.io_permissions_base_offset);
// Call function
setTssStack(100);
// Post-testing
expectEqual(@as(u32, 0), tss.prev_tss);
expectEqual(@as(u32, 100), tss.esp0);
expectEqual(@as(u32, KERNEL_DATA_OFFSET), tss.ss0);
expectEqual(@as(u32, 0), tss.esp1);
expectEqual(@as(u32, 0), tss.ss1);
expectEqual(@as(u32, 0), tss.esp2);
expectEqual(@as(u32, 0), tss.ss2);
expectEqual(@as(u32, 0), tss.cr3);
expectEqual(@as(u32, 0), tss.eip);
expectEqual(@as(u32, 0), tss.eflags);
expectEqual(@as(u32, 0), tss.eax);
expectEqual(@as(u32, 0), tss.ecx);
expectEqual(@as(u32, 0), tss.edx);
expectEqual(@as(u32, 0), tss.ebx);
expectEqual(@as(u32, 0), tss.esp);
expectEqual(@as(u32, 0), tss.ebp);
expectEqual(@as(u32, 0), tss.esi);
expectEqual(@as(u32, 0), tss.edi);
expectEqual(@as(u32, 0), tss.es);
expectEqual(@as(u32, 0), tss.cs);
expectEqual(@as(u32, 0), tss.ss);
expectEqual(@as(u32, 0), tss.ds);
expectEqual(@as(u32, 0), tss.fs);
expectEqual(@as(u32, 0), tss.gs);
expectEqual(@as(u32, 0), tss.ldtr);
expectEqual(@as(u16, 0), tss.trap);
expectEqual(@as(u16, @sizeOf(TtsEntry)), tss.io_permissions_base_offset);
// Clean up
setTssStack(0);
expectEqual(@as(u32, 0), tss.prev_tss);
expectEqual(@as(u32, 0), tss.esp0);
expectEqual(@as(u32, KERNEL_DATA_OFFSET), tss.ss0);
expectEqual(@as(u32, 0), tss.esp1);
expectEqual(@as(u32, 0), tss.ss1);
expectEqual(@as(u32, 0), tss.esp2);
expectEqual(@as(u32, 0), tss.ss2);
expectEqual(@as(u32, 0), tss.cr3);
expectEqual(@as(u32, 0), tss.eip);
expectEqual(@as(u32, 0), tss.eflags);
expectEqual(@as(u32, 0), tss.eax);
expectEqual(@as(u32, 0), tss.ecx);
expectEqual(@as(u32, 0), tss.edx);
expectEqual(@as(u32, 0), tss.ebx);
expectEqual(@as(u32, 0), tss.esp);
expectEqual(@as(u32, 0), tss.ebp);
expectEqual(@as(u32, 0), tss.esi);
expectEqual(@as(u32, 0), tss.edi);
expectEqual(@as(u32, 0), tss.es);
expectEqual(@as(u32, 0), tss.cs);
expectEqual(@as(u32, 0), tss.ss);
expectEqual(@as(u32, 0), tss.ds);
expectEqual(@as(u32, 0), tss.fs);
expectEqual(@as(u32, 0), tss.gs);
expectEqual(@as(u32, 0), tss.ldtr);
expectEqual(@as(u16, 0), tss.trap);
expectEqual(@as(u16, @sizeOf(TtsEntry)), tss.io_permissions_base_offset);
}
test "init" { test "init" {
// Set up // Set up
arch.initTest(); arch.initTest();
@ -650,8 +538,8 @@ test "init" {
// Post testing // Post testing
const tss_entry = gdt_entries[TSS_INDEX]; const tss_entry = gdt_entries[TSS_INDEX];
const tss_limit = @sizeOf(TtsEntry) - 1; const tss_limit = @sizeOf(Tss) - 1;
const tss_addr = @ptrToInt(&tss); const tss_addr = @ptrToInt(&main_tss_entry);
var expected: u64 = 0; var expected: u64 = 0;
expected |= @as(u64, @truncate(u16, tss_limit)); expected |= @as(u64, @truncate(u16, tss_limit));
@ -665,7 +553,7 @@ test "init" {
// Reset // Reset
gdt_ptr.base = 0; gdt_ptr.base = 0;
gdt_entries[TSS_INDEX] = makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS); gdt_entries[TSS_INDEX] = makeGdtEntry(0, 0, NULL_SEGMENT, NULL_FLAGS);
} }
/// ///
@ -686,6 +574,6 @@ fn rt_loadedGDTSuccess() void {
/// ///
/// Run all the runtime tests. /// Run all the runtime tests.
/// ///
fn runtimeTests() void { pub fn runtimeTests() void {
rt_loadedGDTSuccess(); rt_loadedGDTSuccess();
} }

View file

@ -340,6 +340,6 @@ fn rt_loadedIDTSuccess() void {
/// ///
/// Run all the runtime tests. /// Run all the runtime tests.
/// ///
fn runtimeTests() void { pub fn runtimeTests() void {
rt_loadedIDTSuccess(); rt_loadedIDTSuccess();
} }

View file

@ -3,22 +3,22 @@ const syscalls = @import("syscalls.zig");
const irq = @import("irq.zig"); const irq = @import("irq.zig");
const idt = @import("idt.zig"); const idt = @import("idt.zig");
extern fn irqHandler(ctx: *arch.InterruptContext) void; extern fn irqHandler(ctx: *arch.CpuState) usize;
extern fn isrHandler(ctx: *arch.InterruptContext) void; extern fn isrHandler(ctx: *arch.CpuState) usize;
/// ///
/// The main handler for all exceptions and interrupts. This will then go and call the correct /// The main handler for all exceptions and interrupts. This will then go and call the correct
/// handler for an ISR or IRQ. /// handler for an ISR or IRQ.
/// ///
/// Arguments: /// Arguments:
/// IN ctx: *arch.InterruptContext - Pointer to the exception context containing the contents /// IN ctx: *arch.CpuState - Pointer to the exception context containing the contents
/// of the registers at the time of a exception. /// of the registers at the time of a exception.
/// ///
export fn handler(ctx: *arch.InterruptContext) void { export fn handler(ctx: *arch.CpuState) usize {
if (ctx.int_num < irq.IRQ_OFFSET or ctx.int_num == syscalls.INTERRUPT) { if (ctx.int_num < irq.IRQ_OFFSET or ctx.int_num == syscalls.INTERRUPT) {
isrHandler(ctx); return isrHandler(ctx);
} else { } else {
irqHandler(ctx); return irqHandler(ctx);
} }
} }
@ -32,6 +32,7 @@ export fn commonStub() callconv(.Naked) void {
\\push %%es \\push %%es
\\push %%fs \\push %%fs
\\push %%gs \\push %%gs
\\push %%ss
\\mov $0x10, %%ax \\mov $0x10, %%ax
\\mov %%ax, %%ds \\mov %%ax, %%ds
\\mov %%ax, %%es \\mov %%ax, %%es
@ -40,7 +41,8 @@ export fn commonStub() callconv(.Naked) void {
\\mov %%esp, %%eax \\mov %%esp, %%eax
\\push %%eax \\push %%eax
\\call handler \\call handler
\\pop %%eax \\mov %%eax, %%esp
\\pop %%ss
\\pop %%gs \\pop %%gs
\\pop %%fs \\pop %%fs
\\pop %%es \\pop %%es

View file

@ -26,7 +26,7 @@ pub const IrqError = error{
const NUMBER_OF_ENTRIES: u16 = 16; const NUMBER_OF_ENTRIES: u16 = 16;
/// The type of a IRQ handler. A function that takes a interrupt context and returns void. /// The type of a IRQ handler. A function that takes a interrupt context and returns void.
const IrqHandler = fn (*arch.InterruptContext) void; const IrqHandler = fn (*arch.CpuState) usize;
// The offset from the interrupt number where the IRQs are. // The offset from the interrupt number where the IRQs are.
pub const IRQ_OFFSET: u16 = 32; pub const IRQ_OFFSET: u16 = 32;
@ -38,15 +38,17 @@ var irq_handlers: [NUMBER_OF_ENTRIES]?IrqHandler = [_]?IrqHandler{null} ** NUMBE
/// The IRQ handler that each of the IRQs will call when a interrupt happens. /// The IRQ handler that each of the IRQs will call when a interrupt happens.
/// ///
/// Arguments: /// Arguments:
/// IN ctx: *arch.InterruptContext - Pointer to the interrupt context containing the contents /// IN ctx: *arch.CpuState - Pointer to the interrupt context containing the contents
/// of the register at the time of the interrupt. /// of the register at the time of the interrupt.
/// ///
export fn irqHandler(ctx: *arch.InterruptContext) void { export fn irqHandler(ctx: *arch.CpuState) usize {
// Get the IRQ index, by getting the interrupt number and subtracting the offset. // Get the IRQ index, by getting the interrupt number and subtracting the offset.
if (ctx.int_num < IRQ_OFFSET) { if (ctx.int_num < IRQ_OFFSET) {
panic(@errorReturnTrace(), "Not an IRQ number: {}\n", .{ctx.int_num}); panic(@errorReturnTrace(), "Not an IRQ number: {}\n", .{ctx.int_num});
} }
var ret_esp = @ptrToInt(ctx);
const irq_offset = ctx.int_num - IRQ_OFFSET; const irq_offset = ctx.int_num - IRQ_OFFSET;
if (isValidIrq(irq_offset)) { if (isValidIrq(irq_offset)) {
// IRQ index is valid so can truncate // IRQ index is valid so can truncate
@ -54,7 +56,7 @@ export fn irqHandler(ctx: *arch.InterruptContext) void {
if (irq_handlers[irq_num]) |handler| { if (irq_handlers[irq_num]) |handler| {
// Make sure it isn't a spurious irq // Make sure it isn't a spurious irq
if (!pic.spuriousIrq(irq_num)) { if (!pic.spuriousIrq(irq_num)) {
handler(ctx); ret_esp = handler(ctx);
// Send the end of interrupt command // Send the end of interrupt command
pic.sendEndOfInterrupt(irq_num); pic.sendEndOfInterrupt(irq_num);
} }
@ -64,6 +66,7 @@ export fn irqHandler(ctx: *arch.InterruptContext) void {
} else { } else {
panic(@errorReturnTrace(), "Invalid IRQ index: {}", .{irq_offset}); panic(@errorReturnTrace(), "Invalid IRQ index: {}", .{irq_offset});
} }
return ret_esp;
} }
/// ///
@ -143,8 +146,12 @@ pub fn init() void {
} }
fn testFunction0() callconv(.Naked) void {} fn testFunction0() callconv(.Naked) void {}
fn testFunction1(ctx: *arch.InterruptContext) void {} fn testFunction1(ctx: *arch.CpuState) u32 {
fn testFunction2(ctx: *arch.InterruptContext) void {} return 0;
}
fn testFunction2(ctx: *arch.CpuState) u32 {
return 0;
}
test "openIrq" { test "openIrq" {
idt.initTest(); idt.initTest();
@ -264,7 +271,7 @@ fn rt_openedIdtEntries() void {
/// ///
/// Run all the runtime tests. /// Run all the runtime tests.
/// ///
fn runtimeTests() void { pub fn runtimeTests() void {
rt_unregisteredHandlers(); rt_unregisteredHandlers();
rt_openedIdtEntries(); rt_openedIdtEntries();
} }

View file

@ -23,7 +23,7 @@ pub const IsrError = error{
}; };
/// The type of a ISR handler. A function that takes a interrupt context and returns void. /// The type of a ISR handler. A function that takes a interrupt context and returns void.
const IsrHandler = fn (*arch.InterruptContext) void; const IsrHandler = fn (*arch.CpuState) usize;
/// The number of ISR entries. /// The number of ISR entries.
const NUMBER_OF_ENTRIES: u8 = 32; const NUMBER_OF_ENTRIES: u8 = 32;
@ -137,32 +137,36 @@ var syscall_handler: ?IsrHandler = null;
/// The exception handler that each of the exceptions will call when a exception happens. /// The exception handler that each of the exceptions will call when a exception happens.
/// ///
/// Arguments: /// Arguments:
/// IN ctx: *arch.InterruptContext - Pointer to the exception context containing the contents /// IN ctx: *arch.CpuState - Pointer to the exception context containing the contents
/// of the register at the time of the exception. /// of the register at the time of the exception.
/// ///
export fn isrHandler(ctx: *arch.InterruptContext) void { export fn isrHandler(ctx: *arch.CpuState) usize {
// Get the interrupt number // Get the interrupt number
const isr_num = ctx.int_num; const isr_num = ctx.int_num;
var ret_esp = @ptrToInt(ctx);
if (isValidIsr(isr_num)) { if (isValidIsr(isr_num)) {
if (isr_num == syscalls.INTERRUPT) { if (isr_num == syscalls.INTERRUPT) {
// A syscall, so use the syscall handler // A syscall, so use the syscall handler
if (syscall_handler) |handler| { if (syscall_handler) |handler| {
handler(ctx); ret_esp = handler(ctx);
} else { } else {
panic(@errorReturnTrace(), "Syscall handler not registered\n", .{}); panic(@errorReturnTrace(), "Syscall handler not registered\n", .{});
} }
} else { } else {
if (isr_handlers[isr_num]) |handler| { if (isr_handlers[isr_num]) |handler| {
// Regular ISR exception, if there is one registered. // Regular ISR exception, if there is one registered.
handler(ctx); ret_esp = handler(ctx);
} else { } else {
panic(@errorReturnTrace(), "ISR not registered to: {}-{}\n", .{ isr_num, exception_msg[isr_num] }); log.logInfo("State: {X}\n", .{ctx});
panic(@errorReturnTrace(), "ISR {} ({}) triggered with error code 0x{X} but not registered\n", .{ exception_msg[isr_num], isr_num, ctx.error_code });
} }
} }
} else { } else {
panic(@errorReturnTrace(), "Invalid ISR index: {}\n", .{isr_num}); panic(@errorReturnTrace(), "Invalid ISR index: {}\n", .{isr_num});
} }
return ret_esp;
} }
/// ///
@ -251,10 +255,18 @@ pub fn init() void {
} }
fn testFunction0() callconv(.Naked) void {} fn testFunction0() callconv(.Naked) void {}
fn testFunction1(ctx: *arch.InterruptContext) void {} fn testFunction1(ctx: *arch.CpuState) u32 {
fn testFunction2(ctx: *arch.InterruptContext) void {} return 0;
fn testFunction3(ctx: *arch.InterruptContext) void {} }
fn testFunction4(ctx: *arch.InterruptContext) void {} fn testFunction2(ctx: *arch.CpuState) u32 {
return 0;
}
fn testFunction3(ctx: *arch.CpuState) u32 {
return 0;
}
fn testFunction4(ctx: *arch.CpuState) u32 {
return 0;
}
test "openIsr" { test "openIsr" {
idt.initTest(); idt.initTest();
@ -397,7 +409,7 @@ fn rt_openedIdtEntries() void {
/// ///
/// Run all the runtime tests. /// Run all the runtime tests.
/// ///
fn runtimeTests() void { pub fn runtimeTests() void {
rt_unregisteredHandlers(); rt_unregisteredHandlers();
rt_openedIdtEntries(); rt_openedIdtEntries();
} }

View file

@ -35,6 +35,7 @@ SECTIONS {
} }
.bss.stack ALIGN(4K) : AT (ADDR(.bss.stack) - KERNEL_ADDR_OFFSET) { .bss.stack ALIGN(4K) : AT (ADDR(.bss.stack) - KERNEL_ADDR_OFFSET) {
KERNEL_STACK_START = .;
KEEP(*(.bss.stack)) KEEP(*(.bss.stack))
KERNEL_STACK_END = .; KERNEL_STACK_END = .;
} }

View file

@ -1,9 +1,13 @@
const std = @import("std"); const std = @import("std");
const expectEqual = std.testing.expectEqual; const testing = std.testing;
const expect = std.testing.expect; const expectEqual = testing.expectEqual;
const expect = testing.expect;
const builtin = @import("builtin"); const builtin = @import("builtin");
const is_test = builtin.is_test;
const panic = @import("../../panic.zig").panic; const panic = @import("../../panic.zig").panic;
const arch = @import("arch.zig"); const build_options = @import("build_options");
const mock_path = build_options.arch_mock_path;
const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig");
const isr = @import("isr.zig"); const isr = @import("isr.zig");
const MemProfile = @import("../../mem.zig").MemProfile; const MemProfile = @import("../../mem.zig").MemProfile;
const tty = @import("../../tty.zig"); const tty = @import("../../tty.zig");
@ -11,8 +15,6 @@ const log = @import("../../log.zig");
const mem = @import("../../mem.zig"); const mem = @import("../../mem.zig");
const vmm = @import("../../vmm.zig"); const vmm = @import("../../vmm.zig");
const multiboot = @import("multiboot.zig"); const multiboot = @import("multiboot.zig");
const build_options = @import("build_options");
const testing = std.testing;
/// An array of directory entries and page tables. Forms the first level of paging and covers the entire 4GB memory space. /// An array of directory entries and page tables. Forms the first level of paging and covers the entire 4GB memory space.
pub const Directory = packed struct { pub const Directory = packed struct {
@ -361,27 +363,23 @@ pub fn unmap(virtual_start: usize, virtual_end: usize, dir: *Directory) (std.mem
/// Called when a page fault occurs. This will log the CPU state and control registers. /// Called when a page fault occurs. This will log the CPU state and control registers.
/// ///
/// Arguments: /// Arguments:
/// IN state: *arch.InterruptContext - The CPU's state when the fault occurred. /// IN state: *arch.CpuState - The CPU's state when the fault occurred.
/// ///
fn pageFault(state: *arch.InterruptContext) void { fn pageFault(state: *arch.CpuState) u32 {
log.logInfo("State: {X}\n", .{state}); log.logInfo("State: {X}\n", .{state});
var cr0: u32 = 0; var cr0 = asm volatile ("mov %%cr0, %[cr0]"
var cr2: u32 = 0; : [cr0] "=r" (-> u32)
var cr3: u32 = 0;
var cr4: u32 = 0;
asm volatile ("mov %%cr0, %[cr0]"
: [cr0] "=r" (cr0)
); );
asm volatile ("mov %%cr2, %[cr2]" var cr2 = asm volatile ("mov %%cr2, %[cr2]"
: [cr2] "=r" (cr2) : [cr2] "=r" (-> u32)
); );
asm volatile ("mov %%cr3, %[cr3]" var cr3 = asm volatile ("mov %%cr3, %[cr3]"
: [cr3] "=r" (cr3) : [cr3] "=r" (-> u32)
); );
asm volatile ("mov %%cr4, %[cr4]" var cr4 = asm volatile ("mov %%cr4, %[cr4]"
: [cr4] "=r" (cr4) : [cr4] "=r" (-> u32)
); );
log.logInfo("CR0: {X}, CR2: {X}, CR3: {X}, CR4: {X}\n\n", .{ cr0, cr2, cr3, cr4 }); log.logInfo("CR0: 0x{X}, CR2: 0x{X}, CR3: 0x{X}, CR4: 0x{X}\n", .{ cr0, cr2, cr3, cr4 });
@panic("Page fault"); @panic("Page fault");
} }
@ -551,10 +549,12 @@ extern var rt_fault_callback2: *u32;
var faulted = false; var faulted = false;
var use_callback2 = false; var use_callback2 = false;
fn rt_pageFault(ctx: *arch.InterruptContext) void { fn rt_pageFault(ctx: *arch.CpuState) u32 {
faulted = true; faulted = true;
// Return to the fault callback // Return to the fault callback
ctx.eip = @ptrToInt(&if (use_callback2) rt_fault_callback2 else rt_fault_callback); ctx.eip = @ptrToInt(&if (use_callback2) rt_fault_callback2 else rt_fault_callback);
return @ptrToInt(ctx);
} }
fn rt_accessUnmappedMem(v_end: u32) void { fn rt_accessUnmappedMem(v_end: u32) void {
@ -592,7 +592,7 @@ fn rt_accessMappedMem(v_end: u32) void {
log.logInfo("Paging: Tested accessing mapped memory\n", .{}); log.logInfo("Paging: Tested accessing mapped memory\n", .{});
} }
fn runtimeTests(v_end: u32) void { pub fn runtimeTests(v_end: u32) void {
rt_accessUnmappedMem(v_end); rt_accessUnmappedMem(v_end);
rt_accessMappedMem(v_end); rt_accessMappedMem(v_end);
} }

View file

@ -830,6 +830,6 @@ fn rt_picAllMasked() void {
/// ///
/// Run all the runtime tests. /// Run all the runtime tests.
/// ///
fn runtimeTests() void { pub fn runtimeTests() void {
rt_picAllMasked(); rt_picAllMasked();
} }

View file

@ -231,11 +231,12 @@ inline fn sendDataToCounter(counter: CounterSelect, data: u8) void {
/// The interrupt handler for the PIT. This will increment a counter for now. /// The interrupt handler for the PIT. This will increment a counter for now.
/// ///
/// Arguments: /// Arguments:
/// IN ctx: *arch.InterruptContext - Pointer to the interrupt context containing the contents /// IN ctx: *arch.CpuState - Pointer to the interrupt context containing the contents
/// of the register at the time of the interrupt. /// of the register at the time of the interrupt.
/// ///
fn pitHandler(ctx: *arch.InterruptContext) void { fn pitHandler(ctx: *arch.CpuState) usize {
ticks +%= 1; ticks +%= 1;
return @ptrToInt(ctx);
} }
/// ///
@ -324,25 +325,17 @@ pub fn waitTicks(ticks_to_wait: u32) void {
const wait_ticks2 = ticks_to_wait - wait_ticks1; const wait_ticks2 = ticks_to_wait - wait_ticks1;
while (ticks > wait_ticks1) { while (ticks > wait_ticks1) {
arch.enableInterrupts();
arch.halt(); arch.halt();
arch.disableInterrupts();
} }
while (ticks < wait_ticks2) { while (ticks < wait_ticks2) {
arch.enableInterrupts();
arch.halt(); arch.halt();
arch.disableInterrupts();
} }
arch.enableInterrupts();
} else { } else {
const wait_ticks = ticks + ticks_to_wait; const wait_ticks = ticks + ticks_to_wait;
while (ticks < wait_ticks) { while (ticks < wait_ticks) {
arch.enableInterrupts();
arch.halt(); arch.halt();
arch.disableInterrupts();
} }
arch.enableInterrupts();
} }
} }
@ -635,7 +628,11 @@ fn rt_initCounter_0() void {
/// ///
/// Run all the runtime tests. /// Run all the runtime tests.
/// ///
fn runtimeTests() void { pub fn runtimeTests() void {
// Interrupts aren't enabled yet, so for the runtime tests, enable it temporary
arch.enableInterrupts();
defer arch.disableInterrupts();
rt_initCounter_0(); rt_initCounter_0();
rt_waitTicks(); rt_waitTicks();
rt_waitTicks2(); rt_waitTicks2();

View file

@ -6,13 +6,14 @@ const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError; const expectError = std.testing.expectError;
const build_options = @import("build_options"); const build_options = @import("build_options");
const mock_path = build_options.arch_mock_path; const mock_path = build_options.arch_mock_path;
const arch = @import("arch.zig"); const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const pic = @import("pic.zig"); const pic = @import("pic.zig");
const pit = @import("pit.zig"); const pit = @import("pit.zig");
const irq = @import("irq.zig"); const irq = @import("irq.zig");
const cmos = if (is_test) @import(mock_path ++ "cmos_mock.zig") else @import("cmos.zig"); const cmos = if (is_test) @import(mock_path ++ "cmos_mock.zig") else @import("cmos.zig");
const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("../../panic.zig").panic; const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("../../panic.zig").panic;
const scheduler = @import("../../scheduler.zig");
/// The Century register is unreliable. We need a APIC interface to infer if we have a century /// The Century register is unreliable. We need a APIC interface to infer if we have a century
/// register. So this is a current TODO. /// register. So this is a current TODO.
@ -44,6 +45,8 @@ const RtcError = error{
/// The number of ticks that has passed when RTC was initially set up. /// The number of ticks that has passed when RTC was initially set up.
var ticks: u32 = 0; var ticks: u32 = 0;
var schedule: bool = true;
/// ///
/// Checks if the CMOS chip isn't updating the RTC registers. Call this before reading any RTC /// Checks if the CMOS chip isn't updating the RTC registers. Call this before reading any RTC
/// registers so don't get inconsistent values. /// registers so don't get inconsistent values.
@ -206,14 +209,26 @@ fn readRtc() DateTime {
/// The interrupt handler for the RTC. /// The interrupt handler for the RTC.
/// ///
/// Arguments: /// Arguments:
/// IN ctx: *arch.InterruptContext - Pointer to the interrupt context containing the contents /// IN ctx: *arch.CpuState - Pointer to the interrupt context containing the contents
/// of the register at the time of the interrupt. /// of the register at the time of the interrupt.
/// ///
fn rtcHandler(ctx: *arch.InterruptContext) void { fn rtcHandler(ctx: *arch.CpuState) usize {
ticks +%= 1; ticks +%= 1;
var ret_esp: usize = undefined;
// Call the scheduler
if (schedule) {
ret_esp = scheduler.pickNextTask(ctx);
} else {
ret_esp = @ptrToInt(ctx);
}
// Need to read status register C // Need to read status register C
// Might need to disable the NMI bit, set to true
const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false); const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false);
return ret_esp;
} }
/// ///
@ -264,9 +279,6 @@ pub fn init() void {
}, },
}; };
// Need to disable interrupts went setting up the RTC
arch.disableInterrupts();
// Set the interrupt rate to 512Hz // Set the interrupt rate to 512Hz
setRate(7) catch |err| switch (err) { setRate(7) catch |err| switch (err) {
error.RateError => { error.RateError => {
@ -277,9 +289,6 @@ pub fn init() void {
// Enable RTC interrupts // Enable RTC interrupts
enableInterrupts(); enableInterrupts();
// Can now enable interrupts
arch.enableInterrupts();
// Read status register C to clear any interrupts that may have happened during set up // Read status register C to clear any interrupts that may have happened during set up
const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false); const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false);
@ -739,7 +748,15 @@ fn rt_interrupts() void {
/// ///
/// Run all the runtime tests. /// Run all the runtime tests.
/// ///
fn runtimeTests() void { pub fn runtimeTests() void {
rt_init(); rt_init();
// Disable the scheduler temporary
schedule = false;
// Interrupts aren't enabled yet, so for the runtime tests, enable it temporary
arch.enableInterrupts();
rt_interrupts(); rt_interrupts();
arch.disableInterrupts();
// Can enable it back
schedule = true;
} }

View file

@ -1,9 +1,13 @@
const arch = @import("arch.zig"); const std = @import("std");
const testing = @import("std").testing; const builtin = @import("builtin");
const assert = @import("std").debug.assert; const is_test = builtin.is_test;
const build_options = @import("build_options");
const mock_path = build_options.arch_mock_path;
const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig");
const testing = std.testing;
const expect = std.testing.expect;
const isr = @import("isr.zig"); const isr = @import("isr.zig");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const build_options = @import("build_options");
const panic = @import("../../panic.zig").panic; const panic = @import("../../panic.zig").panic;
/// The isr number associated with syscalls /// The isr number associated with syscalls
@ -13,7 +17,7 @@ pub const INTERRUPT: u16 = 0x80;
pub const NUM_HANDLERS: u16 = 256; pub const NUM_HANDLERS: u16 = 256;
/// A syscall handler /// A syscall handler
pub const SyscallHandler = fn (ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32; pub const SyscallHandler = fn (ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32;
/// Errors that syscall utility functions can throw /// Errors that syscall utility functions can throw
pub const SyscallError = error{ pub const SyscallError = error{
@ -44,10 +48,10 @@ pub fn isValidSyscall(syscall: u32) bool {
/// warning is logged. /// warning is logged.
/// ///
/// Arguments: /// Arguments:
/// IN ctx: *arch.InterruptContext - The cpu context when the syscall was triggered. The /// IN ctx: *arch.CpuState - The cpu context when the syscall was triggered. The
/// syscall number is stored in eax. /// syscall number is stored in eax.
/// ///
fn handle(ctx: *arch.InterruptContext) void { fn handle(ctx: *arch.CpuState) u32 {
// The syscall number is put in eax // The syscall number is put in eax
const syscall = ctx.eax; const syscall = ctx.eax;
if (isValidSyscall(syscall)) { if (isValidSyscall(syscall)) {
@ -59,6 +63,7 @@ fn handle(ctx: *arch.InterruptContext) void {
} else { } else {
log.logWarning("Syscall {} is invalid\n", .{syscall}); log.logWarning("Syscall {} is invalid\n", .{syscall});
} }
return @ptrToInt(ctx);
} }
/// ///
@ -217,13 +222,13 @@ inline fn syscall5(syscall: u32, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg
/// 3 => esi and 4 => edi. /// 3 => esi and 4 => edi.
/// ///
/// Arguments: /// Arguments:
/// IN ctx: *arch.InterruptContext - The interrupt context from which to get the argument /// IN ctx: *arch.CpuState - The interrupt context from which to get the argument
/// IN arg_idx: comptime u32 - The argument index to get. Between 0 and 4. /// IN arg_idx: comptime u32 - The argument index to get. Between 0 and 4.
/// ///
/// Return: u32 /// Return: u32
/// The syscall argument from the given index. /// The syscall argument from the given index.
/// ///
inline fn syscallArg(ctx: *arch.InterruptContext, comptime arg_idx: u32) u32 { inline fn syscallArg(ctx: *arch.CpuState, comptime arg_idx: u32) u32 {
return switch (arg_idx) { return switch (arg_idx) {
0 => ctx.ebx, 0 => ctx.ebx,
1 => ctx.ecx, 1 => ctx.ecx,
@ -252,32 +257,32 @@ pub fn init() void {
/// Tests /// Tests
var test_int: u32 = 0; var test_int: u32 = 0;
fn testHandler0(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { fn testHandler0(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
test_int += 1; test_int += 1;
return 0; return 0;
} }
fn testHandler1(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { fn testHandler1(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
test_int += arg1; test_int += arg1;
return 1; return 1;
} }
fn testHandler2(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { fn testHandler2(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
test_int += arg1 + arg2; test_int += arg1 + arg2;
return 2; return 2;
} }
fn testHandler3(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { fn testHandler3(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
test_int += arg1 + arg2 + arg3; test_int += arg1 + arg2 + arg3;
return 3; return 3;
} }
fn testHandler4(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { fn testHandler4(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
test_int += arg1 + arg2 + arg3 + arg4; test_int += arg1 + arg2 + arg3 + arg4;
return 4; return 4;
} }
fn testHandler5(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 { fn testHandler5(ctx: *arch.CpuState, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
test_int += arg1 + arg2 + arg3 + arg4 + arg5; test_int += arg1 + arg2 + arg3 + arg4 + arg5;
return 5; return 5;
} }
@ -287,7 +292,7 @@ test "registerSyscall returns SyscallExists" {
registerSyscall(123, testHandler0) catch |err| { registerSyscall(123, testHandler0) catch |err| {
return; return;
}; };
assert(false); expect(false);
} }
fn runtimeTests() void { fn runtimeTests() void {

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const testing = std.testing; const testing = std.testing;
const Allocator = std.mem.Allocator;
/// ///
/// A comptime bitmap that uses a specific type to store the entries. No allocators needed. /// A comptime bitmap that uses a specific type to store the entries. No allocators needed.
@ -613,6 +614,7 @@ test "setFirstFree multiple bitmaps" {
testing.expectEqual(bmp.bitmaps[0], Bitmap(u8).BITMAP_FULL); testing.expectEqual(bmp.bitmaps[0], Bitmap(u8).BITMAP_FULL);
testing.expectEqual(bmp.bitmaps[1], 1); testing.expectEqual(bmp.bitmaps[1], 1);
} }
test "setFirstFree" { test "setFirstFree" {
var bmp = try Bitmap(u32).init(32, std.heap.page_allocator); var bmp = try Bitmap(u32).init(32, std.heap.page_allocator);

View file

@ -6,7 +6,7 @@ const is_test = builtin.is_test;
const build_options = @import("build_options"); const build_options = @import("build_options");
const mock_path = build_options.mock_path; const mock_path = build_options.mock_path;
const vmm = if (is_test) @import(mock_path ++ "vmm_mock.zig") else @import("vmm.zig"); const vmm = if (is_test) @import(mock_path ++ "vmm_mock.zig") else @import("vmm.zig");
const log = @import("log.zig"); const log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("log.zig");
const panic = @import("panic.zig").panic; const panic = @import("panic.zig").panic;
const FreeListAllocator = struct { const FreeListAllocator = struct {

View file

@ -12,7 +12,9 @@ const serial = @import("serial.zig");
const vmm = if (is_test) @import(mock_path ++ "vmm_mock.zig") else @import("vmm.zig"); const vmm = if (is_test) @import(mock_path ++ "vmm_mock.zig") else @import("vmm.zig");
const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig"); const mem = if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig");
const panic_root = if (is_test) @import(mock_path ++ "panic_mock.zig") else @import("panic.zig"); const panic_root = if (is_test) @import(mock_path ++ "panic_mock.zig") else @import("panic.zig");
const task = if (is_test) @import(mock_path ++ "task_mock.zig") else @import("task.zig");
const heap = @import("heap.zig"); const heap = @import("heap.zig");
const scheduler = @import("scheduler.zig");
comptime { comptime {
if (!is_test) { if (!is_test) {
@ -28,7 +30,14 @@ var kernel_vmm: vmm.VirtualMemoryManager(arch.VmmPayload) = undefined;
// This is for unit testing as we need to export KERNEL_ADDR_OFFSET as it is no longer available // This is for unit testing as we need to export KERNEL_ADDR_OFFSET as it is no longer available
// from the linker script // from the linker script
// These will need to be kept up to date with the debug logs in the mem init.
export var KERNEL_ADDR_OFFSET: u32 = if (builtin.is_test) 0xC0000000 else undefined; export var KERNEL_ADDR_OFFSET: u32 = if (builtin.is_test) 0xC0000000 else undefined;
export var KERNEL_STACK_START: u32 = if (builtin.is_test) 0xC014A000 else undefined;
export var KERNEL_STACK_END: u32 = if (builtin.is_test) 0xC014E000 else undefined;
export var KERNEL_VADDR_START: u32 = if (builtin.is_test) 0xC0100000 else undefined;
export var KERNEL_VADDR_END: u32 = if (builtin.is_test) 0xC014E000 else undefined;
export var KERNEL_PHYSADDR_START: u32 = if (builtin.is_test) 0x100000 else undefined;
export var KERNEL_PHYSADDR_END: u32 = if (builtin.is_test) 0x14E000 else undefined;
// Just call the panic function, as this need to be in the root source file // 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 { pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn {
@ -64,10 +73,39 @@ export fn kmain(boot_payload: arch.BootPayload) void {
var kernel_heap = heap.init(arch.VmmPayload, &kernel_vmm, vmm.Attributes{ .kernel = true, .writable = true, .cachable = true }, heap_size) catch |e| { var kernel_heap = heap.init(arch.VmmPayload, &kernel_vmm, vmm.Attributes{ .kernel = true, .writable = true, .cachable = true }, heap_size) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise kernel heap: {}\n", .{e}); panic_root.panic(@errorReturnTrace(), "Failed to initialise kernel heap: {}\n", .{e});
}; };
tty.init(&kernel_heap.allocator, boot_payload); tty.init(&kernel_heap.allocator, boot_payload);
scheduler.init(&kernel_heap.allocator) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to initialise scheduler: {}", .{e});
};
// Initialisation is finished, now does other stuff
log.logInfo("Init done\n", .{}); log.logInfo("Init done\n", .{});
// Main initialisation finished so can enable interrupts
arch.enableInterrupts();
log.logInfo("Creating init2\n", .{});
// Create a init2 task
var idle_task = task.Task.create(initStage2, &kernel_heap.allocator) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to create init stage 2 task: {}", .{e});
};
scheduler.scheduleTask(idle_task, &kernel_heap.allocator) catch |e| {
panic_root.panic(@errorReturnTrace(), "Failed to schedule init stage 2 task: {}", .{e});
};
// Can't return for now, later this can return maybe
// TODO: Maybe make this the idle task
arch.spinWait();
}
///
/// Stage 2 initialisation. This will initialise main kernel features after the architecture
/// initialisation.
///
fn initStage2() noreturn {
tty.clear(); tty.clear();
const logo = const logo =
\\ _____ _ _ _ _______ ____ \\ _____ _ _ _ _______ ____
@ -88,5 +126,6 @@ export fn kmain(boot_payload: arch.BootPayload) void {
else => {}, else => {},
} }
// Can't return for now, later this can return maybe
arch.spinWait(); arch.spinWait();
} }

View file

@ -1,7 +1,8 @@
const build_options = @import("build_options"); const build_options = @import("build_options");
const std = @import("std"); const std = @import("std");
const Serial = @import("serial.zig").Serial;
const fmt = std.fmt; const fmt = std.fmt;
const Serial = @import("serial.zig").Serial;
const scheduler = @import("scheduler.zig");
/// The errors that can occur when logging /// The errors that can occur when logging
const LoggingError = error{}; const LoggingError = error{};
@ -49,7 +50,9 @@ fn logCallback(context: void, str: []const u8) LoggingError!usize {
/// IN args: anytype - A struct of the parameters for the format string. /// IN args: anytype - A struct of the parameters for the format string.
/// ///
pub fn log(comptime level: Level, comptime format: []const u8, args: anytype) void { pub fn log(comptime level: Level, comptime format: []const u8, args: anytype) void {
scheduler.taskSwitching(false);
fmt.format(OutStream{ .context = {} }, "[" ++ @tagName(level) ++ "] " ++ format, args) catch unreachable; fmt.format(OutStream{ .context = {} }, "[" ++ @tagName(level) ++ "] " ++ format, args) catch unreachable;
scheduler.taskSwitching(true);
} }
/// ///
@ -118,7 +121,7 @@ pub fn init(ser: Serial) void {
/// ///
/// The logging runtime tests that will test all logging levels. /// The logging runtime tests that will test all logging levels.
/// ///
pub fn runtimeTests() void { fn runtimeTests() void {
inline for (@typeInfo(Level).Enum.fields) |field| { inline for (@typeInfo(Level).Enum.fields) |field| {
const level = @field(Level, field.name); const level = @field(Level, field.name);
log(level, "Test " ++ field.name ++ " level\n", .{}); log(level, "Test " ++ field.name ++ " level\n", .{});

View file

@ -2,7 +2,7 @@ const is_test = @import("builtin").is_test;
const std = @import("std"); const std = @import("std");
const build_options = @import("build_options"); const build_options = @import("build_options");
const mock_path = build_options.mock_path; const mock_path = build_options.mock_path;
const arch = @import("arch.zig").internals; const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig").internals;
const MemProfile = (if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig")).MemProfile; const MemProfile = (if (is_test) @import(mock_path ++ "mem_mock.zig") else @import("mem.zig")).MemProfile;
const testing = std.testing; const testing = std.testing;
const panic = @import("panic.zig").panic; const panic = @import("panic.zig").panic;

360
src/kernel/scheduler.zig Normal file
View file

@ -0,0 +1,360 @@
const std = @import("std");
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
const assert = std.debug.assert;
const builtin = @import("builtin");
const is_test = builtin.is_test;
const build_options = @import("build_options");
const mock_path = build_options.mock_path;
const arch = @import("arch.zig").internals;
const log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("log.zig");
const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("panic.zig").panic;
const task = if (is_test) @import(mock_path ++ "task_mock.zig") else @import("task.zig");
const Task = task.Task;
const Allocator = std.mem.Allocator;
const TailQueue = std.TailQueue;
/// The function type for the entry point.
const EntryPointFn = fn () void;
/// The default stack size of a task. Currently this is set to a page size.
const STACK_SIZE: u32 = arch.MEMORY_BLOCK_SIZE / @sizeOf(usize);
/// Pointer to the start of the main kernel stack
extern var KERNEL_STACK_START: []u32;
/// The current task running
var current_task: *Task = undefined;
/// Array list of all runnable tasks
var tasks: TailQueue(*Task) = undefined;
/// Whether the scheduler is allowed to switch tasks.
var can_switch: bool = true;
///
/// The idle task that just halts the CPU but the CPU can still handle interrupts.
///
fn idle() noreturn {
arch.spinWait();
}
pub fn taskSwitching(enabled: bool) void {
can_switch = enabled;
}
///
/// Round robin. This will first save the the current tasks stack pointer, then will pick the next
/// task to be run from the queue. It will add the current task to the end of the queue and pop the
/// next task from the front as set this as the current task. Then will return the stack pointer
/// of the next task to be loaded into the stack register to load the next task stack to pop off
/// its state. Interrupts are assumed disabled.
///
/// Argument:
/// IN ctx: *arch.CpuState - Pointer to the exception context containing the contents
/// of the registers at the time of a exception.
///
/// Return: usize
/// The new stack pointer to the next stack of the next task.
///
pub fn pickNextTask(ctx: *arch.CpuState) usize {
// Save the stack pointer from old task
current_task.stack_pointer = @ptrToInt(ctx);
// If we can't switch, then continue with the current task
if (!can_switch) {
return current_task.stack_pointer;
}
// Pick the next task
// If there isn't one, then just return the same task
if (tasks.pop()) |new_task_node| {
// Get the next task
const next_task = new_task_node.data;
// Move some pointers to don't need to allocate memory, speeds things up
new_task_node.data = current_task;
new_task_node.prev = null;
new_task_node.next = null;
// Add the 'current_task' node to the end of the queue
tasks.prepend(new_task_node);
current_task = next_task;
}
// Context switch in the interrupt stub handler which will pop the next task state off the
// stack
return current_task.stack_pointer;
}
///
/// Create a new task and add it to the scheduling queue. No locking.
///
/// Arguments:
/// IN entry_point: EntryPointFn - The entry point into the task. This must be a function.
///
/// Error: Allocator.Error
/// OutOfMemory - If there isn't enough memory for the a task/stack. Any memory allocated will
/// be freed on return.
///
pub fn scheduleTask(new_task: *Task, allocator: *Allocator) Allocator.Error!void {
var task_node = try tasks.createNode(new_task, allocator);
tasks.prepend(task_node);
}
///
/// Initialise the scheduler. This will set up the current task to the code that is currently
/// running. So if there is a task switch before kmain can finish, can continue when switched back.
/// This will set the stack to KERNEL_STACK_START from the linker stript. This will also create the
/// idle task for when there is no more tasks to run.
///
/// Arguments:
/// IN allocator: *Allocator - The allocator to use when needing to allocate memory.
///
/// Error: Allocator.Error
/// OutOfMemory - There is no more memory. Any memory allocated will be freed on return.
///
pub fn init(allocator: *Allocator) Allocator.Error!void {
// TODO: Maybe move the task init here?
log.logInfo("Init scheduler\n", .{});
defer log.logInfo("Done scheduler\n", .{});
// Init the task list for round robin
tasks = TailQueue(*Task).init();
// Set up the init task to continue execution
current_task = try allocator.create(Task);
errdefer allocator.destroy(current_task);
// PID 0
current_task.pid = 0;
current_task.stack = @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..4096];
// ESP will be saved on next schedule
// Run the runtime tests here
switch (build_options.test_mode) {
.Scheduler => runtimeTests(allocator),
else => {},
}
// Create the idle task when there are no more tasks left
var idle_task = try Task.create(idle, allocator);
errdefer idle_task.destroy(allocator);
try scheduleTask(idle_task, allocator);
}
// For testing the errdefer
const FailingAllocator = std.testing.FailingAllocator;
const testing_allocator = &std.testing.base_allocator_instance.allocator;
fn test_fn1() void {}
fn test_fn2() void {}
var test_pid_counter: u7 = 1;
fn task_create(entry_point: EntryPointFn, allocator: *Allocator) Allocator.Error!*Task {
var t = try allocator.create(Task);
errdefer allocator.destroy(t);
t.pid = test_pid_counter;
// Just alloc something
t.stack = try allocator.alloc(u32, 1);
t.stack_pointer = 0;
test_pid_counter += 1;
return t;
}
fn task_destroy(self: *Task, allocator: *Allocator) void {
if (@ptrToInt(self.stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) {
allocator.free(self.stack);
}
allocator.destroy(self);
}
test "pickNextTask" {
task.initTest();
defer task.freeTest();
task.addConsumeFunction("Task.create", task_create);
task.addConsumeFunction("Task.create", task_create);
task.addRepeatFunction("Task.destroy", task_destroy);
var ctx: arch.CpuState = std.mem.zeroes(arch.CpuState);
var allocator = std.testing.allocator;
tasks = TailQueue(*Task).init();
// Set up a current task
current_task = try allocator.create(Task);
defer allocator.destroy(current_task);
current_task.pid = 0;
current_task.stack = @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..4096];
current_task.stack_pointer = @ptrToInt(&KERNEL_STACK_START);
// Create two tasks and schedule them
var test_fn1_task = try Task.create(test_fn1, allocator);
defer test_fn1_task.destroy(allocator);
try scheduleTask(test_fn1_task, allocator);
var test_fn2_task = try Task.create(test_fn2, allocator);
defer test_fn2_task.destroy(allocator);
try scheduleTask(test_fn2_task, allocator);
// Get the stack pointers of the created tasks
const fn1_stack_pointer = tasks.first.?.data.stack_pointer;
const fn2_stack_pointer = tasks.first.?.next.?.data.stack_pointer;
expectEqual(pickNextTask(&ctx), fn1_stack_pointer);
// The stack pointer of the re-added task should point to the context
expectEqual(tasks.first.?.data.stack_pointer, @ptrToInt(&ctx));
// Should be the PID of the next task
expectEqual(current_task.pid, 1);
expectEqual(pickNextTask(&ctx), fn2_stack_pointer);
// The stack pointer of the re-added task should point to the context
expectEqual(tasks.first.?.data.stack_pointer, @ptrToInt(&ctx));
// Should be the PID of the next task
expectEqual(current_task.pid, 2);
expectEqual(pickNextTask(&ctx), @ptrToInt(&ctx));
// The stack pointer of the re-added task should point to the context
expectEqual(tasks.first.?.data.stack_pointer, @ptrToInt(&ctx));
// Should be back tot he beginning
expectEqual(current_task.pid, 0);
// Reset the test pid
test_pid_counter = 1;
// Free the queue
while (tasks.pop()) |elem| {
tasks.destroyNode(elem, allocator);
}
}
test "createNewTask add new task" {
task.initTest();
defer task.freeTest();
task.addConsumeFunction("Task.create", task_create);
task.addConsumeFunction("Task.destroy", task_destroy);
// Set the global allocator
var allocator = std.testing.allocator;
// Init the task list
tasks = TailQueue(*Task).init();
var test_fn1_task = try Task.create(test_fn1, allocator);
defer test_fn1_task.destroy(allocator);
try scheduleTask(test_fn1_task, allocator);
expectEqual(tasks.len, 1);
// Free the memory
tasks.destroyNode(tasks.first.?, allocator);
}
test "init" {
task.initTest();
defer task.freeTest();
task.addConsumeFunction("Task.create", task_create);
task.addRepeatFunction("Task.destroy", task_destroy);
var allocator = std.testing.allocator;
try init(allocator);
expectEqual(current_task.pid, 0);
expectEqual(current_task.stack, @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..4096]);
expectEqual(tasks.len, 1);
// Free the tasks created
current_task.destroy(allocator);
while (tasks.pop()) |elem| {
elem.data.destroy(allocator);
tasks.destroyNode(elem, allocator);
}
}
/// A volatile pointer used to control a loop outside the task. This is so to ensure a task switch
/// ocurred.
var is_set: *volatile bool = undefined;
///
/// The test task function.
///
fn task_function() noreturn {
log.logInfo("Switched\n", .{});
is_set.* = false;
while (true) {}
}
///
/// This tests that variables in registers and on the stack are preserved when a task switch
/// occurs. Also tests that a global volatile can be test in one task and be reacted to in another.
///
/// Arguments:
/// IN allocator: *Allocator - The allocator to use when needing to allocate memory.
///
fn rt_variable_preserved(allocator: *Allocator) void {
// Create the memory for the boolean
is_set = allocator.create(bool) catch unreachable;
defer allocator.destroy(is_set);
is_set.* = true;
var test_task = Task.create(task_function, allocator) catch unreachable;
scheduleTask(test_task, allocator) catch unreachable;
// TODO: Need to add the ability to remove tasks
var w: u32 = 0;
var x: u32 = 1;
var y: u32 = 2;
var z: u32 = 3;
while (is_set.*) {
if (w != 0) {
panic(@errorReturnTrace(), "FAILED: w not 0, but: {}\n", .{w});
}
if (x != 1) {
panic(@errorReturnTrace(), "FAILED: x not 1, but: {}\n", .{x});
}
if (y != 2) {
panic(@errorReturnTrace(), "FAILED: y not 2, but: {}\n", .{y});
}
if (z != 3) {
panic(@errorReturnTrace(), "FAILED: z not 3, but: {}\n", .{z});
}
}
// Make sure these are the same values
if (w != 0) {
panic(@errorReturnTrace(), "FAILED: w not 0, but: {}\n", .{w});
}
if (x != 1) {
panic(@errorReturnTrace(), "FAILED: x not 1, but: {}\n", .{x});
}
if (y != 2) {
panic(@errorReturnTrace(), "FAILED: y not 2, but: {}\n", .{y});
}
if (z != 3) {
panic(@errorReturnTrace(), "FAILED: z not 3, but: {}\n", .{z});
}
log.logInfo("SUCCESS: Scheduler variables preserved\n", .{});
}
///
/// The scheduler runtime tests that will test the scheduling functionality.
///
/// Arguments:
/// IN allocator: *Allocator - The allocator to use when needing to allocate memory.
///
fn runtimeTests(allocator: *Allocator) void {
arch.enableInterrupts();
rt_variable_preserved(allocator);
while (true) {}
}

209
src/kernel/task.zig Normal file
View file

@ -0,0 +1,209 @@
const std = @import("std");
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
const builtin = @import("builtin");
const is_test = builtin.is_test;
const build_options = @import("build_options");
const mock_path = build_options.mock_path;
const arch = @import("arch.zig").internals;
const log = if (is_test) @import(mock_path ++ "log_mock.zig") else @import("log.zig");
const panic = if (is_test) @import(mock_path ++ "panic_mock.zig").panic else @import("panic.zig").panic;
const ComptimeBitmap = @import("bitmap.zig").ComptimeBitmap;
const Allocator = std.mem.Allocator;
/// The kernels main stack start as this is used to check for if the task being destroyed is this stack
/// as we cannot deallocate this.
extern var KERNEL_STACK_START: *u32;
/// The function type for the entry point.
const EntryPointFn = fn () void;
/// The bitmap type for the PIDs
const PidBitmap = if (is_test) ComptimeBitmap(u128) else ComptimeBitmap(u1024);
/// The list of PIDs that have been allocated.
var all_pids: PidBitmap = brk: {
var pids = PidBitmap.init();
// Set the first PID as this is for the current task running, init 0
_ = pids.setFirstFree() orelse unreachable;
break :brk pids;
};
/// The task control block for storing all the information needed to save and restore a task.
pub const Task = struct {
const Self = @This();
/// The unique task identifier
pid: PidBitmap.IndexType,
/// Pointer to the stack for the task. This will be allocated on initialisation.
stack: []u32,
/// The current stack pointer into the stack.
stack_pointer: usize,
///
/// Create a task. This will allocate a PID and the stack. The stack will be set up as a
/// kernel task. As this is a new task, the stack will need to be initialised with the CPU
/// state as described in arch.CpuState struct.
///
/// Arguments:
/// IN entry_point: EntryPointFn - The entry point into the task. This must be a function.
/// IN allocator: *Allocator - The allocator for allocating memory for a task.
///
/// Return: *Task
/// Pointer to an allocated task. This will then need to be added to the task queue.
///
/// Error: Allocator.Error
/// OutOfMemory - If there is no more memory to allocate. Any memory or PID allocated will
/// be freed on return.
///
pub fn create(entry_point: EntryPointFn, allocator: *Allocator) Allocator.Error!*Task {
var task = try allocator.create(Task);
errdefer allocator.destroy(task);
task.pid = allocatePid();
errdefer freePid(task.pid);
const task_stack = try arch.initTaskStack(@ptrToInt(entry_point), allocator);
task.stack = task_stack.stack;
task.stack_pointer = task_stack.pointer;
return task;
}
///
/// Destroy the task. This will release the allocated PID and free the stack and self.
///
/// Arguments:
/// IN/OUT self: *Self - The pointer to self.
///
pub fn destroy(self: *Self, allocator: *Allocator) void {
freePid(self.pid);
// We need to check that the the stack has been allocated as task 0 (init) won't have a
// stack allocated as this in the linker script
if (@ptrToInt(self.stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) {
allocator.free(self.stack);
}
allocator.destroy(self);
}
};
///
/// Allocate a process identifier. If out of PIDs, then will panic. Is this occurs, will need to
/// increase the bitmap.
///
/// Return: u32
/// A new PID.
///
fn allocatePid() PidBitmap.IndexType {
return all_pids.setFirstFree() orelse panic(@errorReturnTrace(), "Out of PIDs\n", .{});
}
///
/// Free an allocated PID. One must be allocated to be freed. If one wasn't allocated will panic.
///
/// Arguments:
/// IN pid: u32 - The PID to free.
///
fn freePid(pid: PidBitmap.IndexType) void {
if (!all_pids.isSet(pid)) {
panic(@errorReturnTrace(), "PID {} not allocated\n", .{pid});
}
all_pids.clearEntry(pid);
}
// For testing the errdefer
const FailingAllocator = std.testing.FailingAllocator;
const testing_allocator = &std.testing.base_allocator_instance.allocator;
fn test_fn1() void {}
test "create out of memory for task" {
// Set the global allocator
var fa = FailingAllocator.init(testing_allocator, 0);
expectError(error.OutOfMemory, Task.create(test_fn1, &fa.allocator));
// Make sure any memory allocated is freed
expectEqual(fa.allocated_bytes, fa.freed_bytes);
// Make sure no PIDs were allocated
expectEqual(all_pids.bitmap, 1);
}
test "create out of memory for stack" {
// Set the global allocator
var fa = FailingAllocator.init(testing_allocator, 1);
expectError(error.OutOfMemory, Task.create(test_fn1, &fa.allocator));
// Make sure any memory allocated is freed
expectEqual(fa.allocated_bytes, fa.freed_bytes);
// Make sure no PIDs were allocated
expectEqual(all_pids.bitmap, 1);
}
test "create expected setup" {
var task = try Task.create(test_fn1, std.testing.allocator);
defer task.destroy(std.testing.allocator);
// Will allocate the first PID 1, 0 will always be allocated
expectEqual(task.pid, 1);
}
test "destroy cleans up" {
// This used the leak detector allocator in testing
// So if any alloc were not freed, this will fail the test
var fa = FailingAllocator.init(testing_allocator, 2);
var task = try Task.create(test_fn1, &fa.allocator);
task.destroy(&fa.allocator);
// Make sure any memory allocated is freed
expectEqual(fa.allocated_bytes, fa.freed_bytes);
// All PIDs were freed
expectEqual(all_pids.bitmap, 1);
}
test "Multiple create" {
var task1 = try Task.create(test_fn1, std.testing.allocator);
var task2 = try Task.create(test_fn1, std.testing.allocator);
expectEqual(task1.pid, 1);
expectEqual(task2.pid, 2);
expectEqual(all_pids.bitmap, 7);
task1.destroy(std.testing.allocator);
expectEqual(all_pids.bitmap, 5);
var task3 = try Task.create(test_fn1, std.testing.allocator);
expectEqual(task3.pid, 1);
expectEqual(all_pids.bitmap, 7);
task2.destroy(std.testing.allocator);
task3.destroy(std.testing.allocator);
}
test "allocatePid and freePid" {
expectEqual(all_pids.bitmap, 1);
var i: usize = 1;
while (i < PidBitmap.NUM_ENTRIES) : (i += 1) {
expectEqual(i, allocatePid());
}
expectEqual(all_pids.bitmap, PidBitmap.BITMAP_FULL);
i = 0;
while (i < PidBitmap.NUM_ENTRIES) : (i += 1) {
freePid(@truncate(PidBitmap.IndexType, i));
}
expectEqual(all_pids.bitmap, 0);
}

View file

@ -9,6 +9,8 @@ const paging = @import("paging_mock.zig");
const Serial = @import("../../../src/kernel/serial.zig").Serial; const Serial = @import("../../../src/kernel/serial.zig").Serial;
const TTY = @import("../../../src/kernel/tty.zig").TTY; const TTY = @import("../../../src/kernel/tty.zig").TTY;
pub const task = @import("task_mock.zig");
const mock_framework = @import("mock_framework.zig"); const mock_framework = @import("mock_framework.zig");
pub const initTest = mock_framework.initTest; pub const initTest = mock_framework.initTest;
pub const freeTest = mock_framework.freeTest; pub const freeTest = mock_framework.freeTest;
@ -16,7 +18,8 @@ pub const addTestParams = mock_framework.addTestParams;
pub const addConsumeFunction = mock_framework.addConsumeFunction; pub const addConsumeFunction = mock_framework.addConsumeFunction;
pub const addRepeatFunction = mock_framework.addRepeatFunction; pub const addRepeatFunction = mock_framework.addRepeatFunction;
pub const InterruptContext = struct { pub const CpuState = struct {
ss: u32,
gs: u32, gs: u32,
fs: u32, fs: u32,
es: u32, es: u32,
@ -35,14 +38,16 @@ pub const InterruptContext = struct {
cs: u32, cs: u32,
eflags: u32, eflags: u32,
user_esp: u32, user_esp: u32,
ss: u32, user_ss: u32,
}; };
pub const VmmPayload = u8; pub const VmmPayload = u8;
pub const KERNEL_VMM_PAYLOAD: usize = 0; pub const KERNEL_VMM_PAYLOAD: usize = 0;
pub const MEMORY_BLOCK_SIZE: u32 = paging.PAGE_SIZE_4KB; pub const MEMORY_BLOCK_SIZE: u32 = paging.PAGE_SIZE_4KB;
pub const STACK_SIZE: u32 = MEMORY_BLOCK_SIZE / @sizeOf(u32);
pub const VMM_MAPPER: vmm.Mapper(VmmPayload) = undefined; pub const VMM_MAPPER: vmm.Mapper(VmmPayload) = undefined;
pub const BootPayload = u8; pub const BootPayload = u8;
pub const Task = task.Task;
// The virtual/physical start/end of the kernel code // The virtual/physical start/end of the kernel code
var KERNEL_PHYSADDR_START: u32 = 0x00100000; var KERNEL_PHYSADDR_START: u32 = 0x00100000;
@ -132,8 +137,13 @@ pub fn initMem(payload: BootPayload) std.mem.Allocator.Error!mem.MemProfile {
}; };
} }
pub fn initTaskStack(entry_point: usize, allocator: *Allocator) Allocator.Error!struct { stack: []u32, pointer: usize } {
const ret = .{ .stack = &([_]u32{}), .pointer = 0 };
return ret;
}
pub fn init(payload: BootPayload, mem_profile: *const MemProfile, allocator: *Allocator) void { pub fn init(payload: BootPayload, mem_profile: *const MemProfile, allocator: *Allocator) void {
// I'll get back to this as this doesn't effect the GDT testing. // I'll get back to this as this doesn't effect the current testing.
// When I come on to the mem.zig testing, I'll fix :) // When I come on to the mem.zig testing, I'll fix :)
//return mock_framework.performAction("init", void, mem_profile, allocator); //return mock_framework.performAction("init", void, mem_profile, allocator);
} }

View file

@ -35,7 +35,7 @@ const GdtEntry = packed struct {
base_high: u8, base_high: u8,
}; };
const TtsEntry = packed struct { const Tss = packed struct {
prev_tss: u32, prev_tss: u32,
esp0: u32, esp0: u32,
ss0: u32, ss0: u32,
@ -160,10 +160,6 @@ pub const USER_CODE_OFFSET: u16 = 0x18;
pub const USER_DATA_OFFSET: u16 = 0x20; pub const USER_DATA_OFFSET: u16 = 0x20;
pub const TSS_OFFSET: u16 = 0x28; pub const TSS_OFFSET: u16 = 0x28;
pub fn setTssStack(esp0: u32) void {
return mock_framework.performAction("setTssStack", void, esp0);
}
pub fn init() void { pub fn init() void {
return mock_framework.performAction("init", void); return mock_framework.performAction("init", void);
} }

View file

@ -7,7 +7,7 @@ pub const addTestParams = mock_framework.addTestParams;
pub const addConsumeFunction = mock_framework.addConsumeFunction; pub const addConsumeFunction = mock_framework.addConsumeFunction;
pub const addRepeatFunction = mock_framework.addRepeatFunction; pub const addRepeatFunction = mock_framework.addRepeatFunction;
const IdtEntry = packed struct { pub const IdtEntry = packed struct {
base_low: u16, base_low: u16,
selector: u16, selector: u16,
zero: u8, zero: u8,
@ -34,10 +34,14 @@ const PRIVILEGE_RING_1: u2 = 0x1;
const PRIVILEGE_RING_2: u2 = 0x2; const PRIVILEGE_RING_2: u2 = 0x2;
const PRIVILEGE_RING_3: u2 = 0x3; const PRIVILEGE_RING_3: u2 = 0x3;
const NUMBER_OF_ENTRIES: u16 = 256; pub const NUMBER_OF_ENTRIES: u16 = 256;
const TABLE_SIZE: u16 = @sizeOf(IdtEntry) * NUMBER_OF_ENTRIES - 1; const TABLE_SIZE: u16 = @sizeOf(IdtEntry) * NUMBER_OF_ENTRIES - 1;
pub fn isIdtOpen(entry: IdtEntry) bool {
return mock_framework.performAction("isIdtOpen", bool, .{entry});
}
pub fn openInterruptGate(index: u8, handler: InterruptHandler) IdtError!void { pub fn openInterruptGate(index: u8, handler: InterruptHandler) IdtError!void {
return mock_framework.performAction("openInterruptGate", IdtError!void, .{ index, handler }); return mock_framework.performAction("openInterruptGate", IdtError!void, .{ index, handler });
} }

View file

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const StringHashMap = std.StringHashMap; const StringHashMap = std.StringHashMap;
const expect = std.testing.expect; const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual; const expectEqual = std.testing.expectEqual;
@ -8,6 +9,7 @@ const warn = std.debug.warn;
const gdt = @import("gdt_mock.zig"); const gdt = @import("gdt_mock.zig");
const idt = @import("idt_mock.zig"); const idt = @import("idt_mock.zig");
const cmos = @import("cmos_mock.zig"); const cmos = @import("cmos_mock.zig");
const task = @import("task_mock.zig");
/// ///
/// The enumeration of types that the mocking framework supports. These include basic types like u8 /// The enumeration of types that the mocking framework supports. These include basic types like u8
@ -19,13 +21,18 @@ const DataElementType = enum {
U8, U8,
U16, U16,
U32, U32,
ECmosStatusRegister, USIZE,
ECmosRtcRegister, PTR_ALLOCATOR,
PTR_CONST_GdtPtr, ECMOSSTATUSREGISTER,
PTR_CONST_IdtPtr, ECMOSRTCREGISTER,
GdtPtr, GDTPTR,
IdtPtr, IDTPTR,
IDTENTRY,
PTR_CONST_GDTPTR,
PTR_CONST_IDTPTR,
ERROR_IDTERROR_VOID, ERROR_IDTERROR_VOID,
ERROR_MEM_PTRTASK,
PTR_TASK,
EFN_OVOID, EFN_OVOID,
NFN_OVOID, NFN_OVOID,
FN_OVOID, FN_OVOID,
@ -34,20 +41,26 @@ const DataElementType = enum {
FN_IU8_OBOOL, FN_IU8_OBOOL,
FN_IU8_OVOID, FN_IU8_OVOID,
FN_IU16_OVOID, FN_IU16_OVOID,
FN_IUSIZE_OVOID,
FN_IU16_OU8, FN_IU16_OU8,
FN_IU4_IU4_OU8, FN_IU4_IU4_OU8,
FN_IU8_IU8_OU16, FN_IU8_IU8_OU16,
FN_IU16_IU8_OVOID, FN_IU16_IU8_OVOID,
FN_IU16_IU16_OVOID, FN_IU16_IU16_OVOID,
FN_IECmosStatusRegister_IBOOL_OU8, FN_IECMOSSTATUSREGISTER_IBOOL_OU8,
FN_IECmosStatusRegister_IU8_IBOOL_OVOID, FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID,
FN_IECmosRtcRegister_OU8, FN_IECMOSRTCREGISTER_OU8,
FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, FN_IU8_IEFNOVOID_OERRORIDTERRORVOID,
FN_IU8_INFNOVOID_OERRORIDTERRORVOID, FN_IU8_INFNOVOID_OERRORIDTERRORVOID,
FN_IPTRCONSTGDTPTR_OVOID, FN_IPTRCONSTGDTPTR_OVOID,
FN_IPTRCONSTIDTPTR_OVOID, FN_IPTRCONSTIDTPTR_OVOID,
FN_OGDTPTR, FN_OGDTPTR,
FN_OIDTPTR, FN_OIDTPTR,
FN_IIDTENTRY_OBOOL,
FN_IPTRTask_IUSIZE_OVOID,
FN_IPTRTASK_IPTRALLOCATOR_OVOID,
FN_IFNOVOID_OMEMERRORPTRTASK,
FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK,
}; };
/// ///
@ -64,13 +77,18 @@ const DataElement = union(DataElementType) {
U8: u8, U8: u8,
U16: u16, U16: u16,
U32: u32, U32: u32,
ECmosStatusRegister: cmos.StatusRegister, USIZE: usize,
ECmosRtcRegister: cmos.RtcRegister, PTR_ALLOCATOR: *std.mem.Allocator,
PTR_CONST_GdtPtr: *const gdt.GdtPtr, ECMOSSTATUSREGISTER: cmos.StatusRegister,
IdtPtr: idt.IdtPtr, ECMOSRTCREGISTER: cmos.RtcRegister,
GdtPtr: gdt.GdtPtr, GDTPTR: gdt.GdtPtr,
PTR_CONST_IdtPtr: *const idt.IdtPtr, IDTPTR: idt.IdtPtr,
IDTENTRY: idt.IdtEntry,
PTR_CONST_GDTPTR: *const gdt.GdtPtr,
PTR_CONST_IDTPTR: *const idt.IdtPtr,
ERROR_IDTERROR_VOID: idt.IdtError!void, ERROR_IDTERROR_VOID: idt.IdtError!void,
ERROR_MEM_PTRTASK: std.mem.Allocator.Error!*task.Task,
PTR_TASK: *task.Task,
EFN_OVOID: fn () callconv(.C) void, EFN_OVOID: fn () callconv(.C) void,
NFN_OVOID: fn () callconv(.Naked) void, NFN_OVOID: fn () callconv(.Naked) void,
FN_OVOID: fn () void, FN_OVOID: fn () void,
@ -78,21 +96,27 @@ const DataElement = union(DataElementType) {
FN_OU16: fn () u16, FN_OU16: fn () u16,
FN_IU8_OBOOL: fn (u8) bool, FN_IU8_OBOOL: fn (u8) bool,
FN_IU8_OVOID: fn (u8) void, FN_IU8_OVOID: fn (u8) void,
FN_IUSIZE_OVOID: fn (usize) void,
FN_IU16_OVOID: fn (u16) void, FN_IU16_OVOID: fn (u16) void,
FN_IU16_OU8: fn (u16) u8, FN_IU16_OU8: fn (u16) u8,
FN_IU4_IU4_OU8: fn (u4, u4) u8, FN_IU4_IU4_OU8: fn (u4, u4) u8,
FN_IU8_IU8_OU16: fn (u8, u8) u16, FN_IU8_IU8_OU16: fn (u8, u8) u16,
FN_IU16_IU8_OVOID: fn (u16, u8) void, FN_IU16_IU8_OVOID: fn (u16, u8) void,
FN_IU16_IU16_OVOID: fn (u16, u16) void, FN_IU16_IU16_OVOID: fn (u16, u16) void,
FN_IECmosStatusRegister_IBOOL_OU8: fn (cmos.StatusRegister, bool) u8, FN_IECMOSSTATUSREGISTER_IBOOL_OU8: fn (cmos.StatusRegister, bool) u8,
FN_IECmosStatusRegister_IU8_IBOOL_OVOID: fn (cmos.StatusRegister, u8, bool) void, FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID: fn (cmos.StatusRegister, u8, bool) void,
FN_IECmosRtcRegister_OU8: fn (cmos.RtcRegister) u8, FN_IECMOSRTCREGISTER_OU8: fn (cmos.RtcRegister) u8,
FN_IU8_IEFNOVOID_OERRORIDTERRORVOID: fn (u8, fn () callconv(.C) void) idt.IdtError!void, FN_IU8_IEFNOVOID_OERRORIDTERRORVOID: fn (u8, fn () callconv(.C) void) idt.IdtError!void,
FN_IU8_INFNOVOID_OERRORIDTERRORVOID: fn (u8, fn () callconv(.Naked) void) idt.IdtError!void, FN_IU8_INFNOVOID_OERRORIDTERRORVOID: fn (u8, fn () callconv(.Naked) void) idt.IdtError!void,
FN_IPTRCONSTGDTPTR_OVOID: fn (*const gdt.GdtPtr) void, FN_IPTRCONSTGDTPTR_OVOID: fn (*const gdt.GdtPtr) void,
FN_IPTRCONSTIDTPTR_OVOID: fn (*const idt.IdtPtr) void, FN_IPTRCONSTIDTPTR_OVOID: fn (*const idt.IdtPtr) void,
FN_OGDTPTR: fn () gdt.GdtPtr, FN_OGDTPTR: fn () gdt.GdtPtr,
FN_OIDTPTR: fn () idt.IdtPtr, FN_OIDTPTR: fn () idt.IdtPtr,
FN_IIDTENTRY_OBOOL: fn (idt.IdtEntry) bool,
FN_IPTRTask_IUSIZE_OVOID: fn (*task.Task, usize) void,
FN_IPTRTASK_IPTRALLOCATOR_OVOID: fn (*task.Task, *std.mem.Allocator) void,
FN_IFNOVOID_OMEMERRORPTRTASK: fn (fn () void) std.mem.Allocator.Error!*task.Task,
FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK: fn (fn () void, *std.mem.Allocator) std.mem.Allocator.Error!*task.Task,
}; };
/// ///
@ -173,11 +197,18 @@ fn Mock() type {
u8 => DataElement{ .U8 = arg }, u8 => DataElement{ .U8 = arg },
u16 => DataElement{ .U16 = arg }, u16 => DataElement{ .U16 = arg },
u32 => DataElement{ .U32 = arg }, u32 => DataElement{ .U32 = arg },
cmos.StatusRegister => DataElement{ .ECmosStatusRegister = arg }, usize => DataElement{ .USIZE = arg },
cmos.RtcRegister => DataElement{ .ECmosRtcRegister = arg }, *std.mem.Allocator => DataElement{ .PTR_ALLOCATOR = arg },
*const gdt.GdtPtr => DataElement{ .PTR_CONST_GdtPtr = arg }, cmos.StatusRegister => DataElement{ .ECMOSSTATUSREGISTER = arg },
*const idt.IdtPtr => DataElement{ .PTR_CONST_IdtPtr = arg }, cmos.RtcRegister => DataElement{ .ECMOSRTCREGISTER = arg },
gdt.GdtPtr => DataElement{ .GDTPTR = arg },
idt.IdtPtr => DataElement{ .IDTPTR = arg },
idt.IdtEntry => DataElement{ .IDTENTRY = arg },
*const gdt.GdtPtr => DataElement{ .PTR_CONST_GDTPTR = arg },
*const idt.IdtPtr => DataElement{ .PTR_CONST_IDTPTR = arg },
idt.IdtError!void => DataElement{ .ERROR_IDTERROR_VOID = arg }, idt.IdtError!void => DataElement{ .ERROR_IDTERROR_VOID = arg },
std.mem.Allocator.Error!*task.Task => DataElement{ .ERROR_MEM_PTRTASK = arg },
*task.Task => DataElement{ .PTR_TASK = arg },
fn () callconv(.C) void => DataElement{ .EFN_OVOID = arg }, fn () callconv(.C) void => DataElement{ .EFN_OVOID = arg },
fn () callconv(.Naked) void => DataElement{ .NFN_OVOID = arg }, fn () callconv(.Naked) void => DataElement{ .NFN_OVOID = arg },
fn () void => DataElement{ .FN_OVOID = arg }, fn () void => DataElement{ .FN_OVOID = arg },
@ -185,19 +216,27 @@ fn Mock() type {
fn () u16 => DataElement{ .FN_OU16 = arg }, fn () u16 => DataElement{ .FN_OU16 = arg },
fn (u8) bool => DataElement{ .FN_IU8_OBOOL = arg }, fn (u8) bool => DataElement{ .FN_IU8_OBOOL = arg },
fn (u8) void => DataElement{ .FN_IU8_OVOID = arg }, fn (u8) void => DataElement{ .FN_IU8_OVOID = arg },
fn (usize) void => DataElement{ .FN_IUSIZE_OVOID = arg },
fn (u16) void => DataElement{ .FN_IU16_OVOID = arg }, fn (u16) void => DataElement{ .FN_IU16_OVOID = arg },
fn (u16) u8 => DataElement{ .FN_IU16_OU8 = arg }, fn (u16) u8 => DataElement{ .FN_IU16_OU8 = arg },
fn (u4, u4) u8 => DataElement{ .FN_IU4_IU4_OU8 = arg }, fn (u4, u4) u8 => DataElement{ .FN_IU4_IU4_OU8 = arg },
fn (u8, u8) u16 => DataElement{ .FN_IU8_IU8_OU16 = arg }, fn (u8, u8) u16 => DataElement{ .FN_IU8_IU8_OU16 = arg },
fn (u16, u8) void => DataElement{ .FN_IU16_IU8_OVOID = arg }, fn (u16, u8) void => DataElement{ .FN_IU16_IU8_OVOID = arg },
fn (u16, u16) void => DataElement{ .FN_IU16_IU16_OVOID = arg }, fn (u16, u16) void => DataElement{ .FN_IU16_IU16_OVOID = arg },
fn (cmos.StatusRegister, bool) u8 => DataElement{ .FN_IECmosStatusRegister_IBOOL_OU8 = arg }, fn (cmos.StatusRegister, bool) u8 => DataElement{ .FN_IECMOSSTATUSREGISTER_IBOOL_OU8 = arg },
fn (cmos.StatusRegister, u8, bool) void => DataElement{ .FN_IECmosStatusRegister_IU8_IBOOL_OVOID = arg }, fn (cmos.StatusRegister, u8, bool) void => DataElement{ .FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID = arg },
fn (cmos.RtcRegister) u8 => DataElement{ .FN_IECmosRtcRegister_OU8 = arg }, fn (cmos.RtcRegister) u8 => DataElement{ .FN_IECMOSRTCREGISTER_OU8 = arg },
fn (*const gdt.GdtPtr) void => DataElement{ .FN_IPTRCONSTGDTPTR_OVOID = arg }, fn (*const gdt.GdtPtr) void => DataElement{ .FN_IPTRCONSTGDTPTR_OVOID = arg },
fn () gdt.GdtPtr => DataElement{ .FN_OGDTPTR = arg },
fn (*const idt.IdtPtr) void => DataElement{ .FN_IPTRCONSTIDTPTR_OVOID = arg }, fn (*const idt.IdtPtr) void => DataElement{ .FN_IPTRCONSTIDTPTR_OVOID = arg },
fn () idt.IdtPtr => DataElement{ .FN_OIDTPTR = arg },
fn (u8, fn () callconv(.C) void) idt.IdtError!void => DataElement{ .FN_IU8_IEFNOVOID_OERRORIDTERRORVOID = arg }, fn (u8, fn () callconv(.C) void) idt.IdtError!void => DataElement{ .FN_IU8_IEFNOVOID_OERRORIDTERRORVOID = arg },
fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => DataElement{ .FN_IU8_INFNOVOID_OERRORIDTERRORVOID = arg }, fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => DataElement{ .FN_IU8_INFNOVOID_OERRORIDTERRORVOID = arg },
fn (idt.IdtEntry) bool => DataElement{ .FN_IIDTENTRY_OBOOL = arg },
fn (*task.Task, usize) void => DataElement{ .FN_IPTRTask_IUSIZE_OVOID = arg },
fn (*task.Task, *std.mem.Allocator) void => DataElement{ .FN_IPTRTASK_IPTRALLOCATOR_OVOID = arg },
fn (fn () void) std.mem.Allocator.Error!*task.Task => DataElement{ .FN_IFNOVOID_OMEMERRORPTRTASK = arg },
fn (fn () void, *std.mem.Allocator) std.mem.Allocator.Error!*task.Task => DataElement{ .FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK = arg },
else => @compileError("Type not supported: " ++ @typeName(@TypeOf(arg))), else => @compileError("Type not supported: " ++ @typeName(@TypeOf(arg))),
}; };
} }
@ -218,34 +257,46 @@ fn Mock() type {
u8 => DataElementType.U8, u8 => DataElementType.U8,
u16 => DataElementType.U16, u16 => DataElementType.U16,
u32 => DataElementType.U32, u32 => DataElementType.U32,
cmos.StatusRegister => DataElementType.ECmosStatusRegister, usize => DataElementType.USIZE,
cmos.RtcRegister => DataElementType.ECmosRtcRegister, *std.mem.Allocator => DataElementType.PTR_ALLOCATOR,
*const gdt.GdtPtr => DataElement.PTR_CONST_GdtPtr, cmos.StatusRegister => DataElementType.ECMOSSTATUSREGISTER,
*const idt.IdtPtr => DataElement.PTR_CONST_IdtPtr, cmos.RtcRegister => DataElementType.ECMOSRTCREGISTER,
gdt.GdtPtr => DataElement.GdtPtr, gdt.GdtPtr => DataElementType.GDTPTR,
idt.IdtPtr => DataElement.IdtPtr, idt.IdtPtr => DataElementType.IDTPTR,
idt.IdtError!void => DataElement.ERROR_IDTERROR_VOID, idt.IdtEntry => DataElementType.IDTENTRY,
*const gdt.GdtPtr => DataElementType.PTR_CONST_GDTPTR,
*const idt.IdtPtr => DataElementType.PTR_CONST_IDTPTR,
idt.IdtError!void => DataElementType.ERROR_IDTERROR_VOID,
std.mem.Allocator.Error!*task.Task => DataElementType.ERROR_MEM_PTRTASK,
*task.Task => DataElementType.PTR_TASK,
fn () callconv(.C) void => DataElementType.EFN_OVOID, fn () callconv(.C) void => DataElementType.EFN_OVOID,
fn () callconv(.Naked) void => DataElementType.NFN_OVOID, fn () callconv(.Naked) void => DataElementType.NFN_OVOID,
fn () void => DataElementType.FN_OVOID, fn () void => DataElementType.FN_OVOID,
fn () usize => DataElementType.FN_OUSIZE,
fn () u16 => DataElementType.FN_OU16, fn () u16 => DataElementType.FN_OU16,
fn (u8) bool => DataElementType.FN_IU8_OBOOL, fn (u8) bool => DataElementType.FN_IU8_OBOOL,
fn (u8) void => DataElementType.FN_IU8_OVOID, fn (u8) void => DataElementType.FN_IU8_OVOID,
fn (u16) void => DataElementType.FN_IU16_OVOID, fn (u16) void => DataElementType.FN_IU16_OVOID,
fn (usize) void => DataElementType.FN_IUSIZE_OVOID,
fn (u16) u8 => DataElementType.FN_IU16_OU8, fn (u16) u8 => DataElementType.FN_IU16_OU8,
fn (u4, u4) u8 => DataElementType.FN_IU4_IU4_OU8, fn (u4, u4) u8 => DataElementType.FN_IU4_IU4_OU8,
fn (u8, u8) u16 => DataElementType.FN_IU8_IU8_OU16, fn (u8, u8) u16 => DataElementType.FN_IU8_IU8_OU16,
fn (u16, u8) void => DataElementType.FN_IU16_IU8_OVOID, fn (u16, u8) void => DataElementType.FN_IU16_IU8_OVOID,
fn (u16, u16) void => DataElementType.FN_IU16_IU16_OVOID, fn (u16, u16) void => DataElementType.FN_IU16_IU16_OVOID,
fn (cmos.StatusRegister, bool) u8 => DataElementType.FN_IECmosStatusRegister_IBOOL_OU8, fn (cmos.StatusRegister, bool) u8 => DataElementType.FN_IECMOSSTATUSREGISTER_IBOOL_OU8,
fn (cmos.StatusRegister, u8, bool) void => DataElementType.FN_IECmosStatusRegister_IU8_IBOOL_OVOID, fn (cmos.StatusRegister, u8, bool) void => DataElementType.FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID,
fn (cmos.RtcRegister) u8 => DataElementType.FN_IECmosRtcRegister_OU8, fn (cmos.RtcRegister) u8 => DataElementType.FN_IECMOSRTCREGISTER_OU8,
fn (*const gdt.GdtPtr) void => DataElementType.FN_IPTRCONSTGDTPTR_OVOID, fn (*const gdt.GdtPtr) void => DataElementType.FN_IPTRCONSTGDTPTR_OVOID,
fn (*const idt.IdtPtr) void => DataElementType.FN_IPTRCONSTIDTPTR_OVOID, fn (*const idt.IdtPtr) void => DataElementType.FN_IPTRCONSTIDTPTR_OVOID,
fn () gdt.GdtPtr => DataElementType.FN_OGDTPTR, fn () gdt.GdtPtr => DataElementType.FN_OGDTPTR,
fn () idt.IdtPtr => DataElementType.FN_OIDTPTR, fn () idt.IdtPtr => DataElementType.FN_OIDTPTR,
fn (u8, fn () callconv(.C) void) idt.IdtError!void => DataElementType.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, fn (u8, fn () callconv(.C) void) idt.IdtError!void => DataElementType.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID,
fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => DataElementType.FN_IU8_INFNOVOID_OERRORIDTERRORVOID, fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => DataElementType.FN_IU8_INFNOVOID_OERRORIDTERRORVOID,
fn (idt.IdtEntry) bool => DataElementType.FN_IIDTENTRY_OBOOL,
fn (*task.Task, usize) void => DataElementType.FN_IPTRTask_IUSIZE_OVOID,
fn (*task.Task, *std.mem.Allocator) void => DataElementType.FN_IPTRTASK_IPTRALLOCATOR_OVOID,
fn (fn () void) std.mem.Allocator.Error!*task.Task => DataElementType.FN_IFNOVOID_OMEMERRORPTRTASK,
fn (fn () void, *std.mem.Allocator) std.mem.Allocator.Error!*task.Task => DataElementType.FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK,
else => @compileError("Type not supported: " ++ @typeName(T)), else => @compileError("Type not supported: " ++ @typeName(T)),
}; };
} }
@ -268,34 +319,46 @@ fn Mock() type {
u8 => element.U8, u8 => element.U8,
u16 => element.U16, u16 => element.U16,
u32 => element.U32, u32 => element.U32,
cmos.StatusRegister => element.ECmosStatusRegister, usize => element.USIZE,
cmos.RtcRegister => element.ECmosRtcRegister, *std.mem.Allocator => element.PTR_ALLOCATOR,
*const gdt.GdtPtr => element.PTR_CONST_GdtPtr, cmos.StatusRegister => element.ECMOSSTATUSREGISTER,
*const idt.IdtPtr => element.PTR_CONST_IdtPtr, gdt.GdtPtr => element.GDTPTR,
gdt.GdtPtr => element.GdtPtr, idt.IdtPtr => element.IDTPTR,
idt.IdtPtr => element.IdtPtr, idt.IdtEntry => element.IDTENTRY,
cmos.RtcRegister => element.ECMOSRTCREGISTER,
*const gdt.GdtPtr => element.PTR_CONST_GDTPTR,
*const idt.IdtPtr => element.PTR_CONST_IDTPTR,
idt.IdtError!void => element.ERROR_IDTERROR_VOID, idt.IdtError!void => element.ERROR_IDTERROR_VOID,
std.mem.Allocator.Error!*task.Task => element.ERROR_MEM_PTRTASK,
*task.Task => element.PTR_TASK,
fn () callconv(.C) void => element.EFN_OVOID, fn () callconv(.C) void => element.EFN_OVOID,
fn () callconv(.Naked) void => element.NFN_OVOID, fn () callconv(.Naked) void => element.NFN_OVOID,
fn () void => element.FN_OVOID, fn () void => element.FN_OVOID,
fn () usize => element.FN_OUSIZE,
fn () u16 => element.FN_OU16, fn () u16 => element.FN_OU16,
fn (u8) bool => element.FN_IU8_OBOOL, fn (u8) bool => element.FN_IU8_OBOOL,
fn (u8) void => element.FN_IU8_OVOID, fn (u8) void => element.FN_IU8_OVOID,
fn (u16) void => element.FN_IU16_OVOID, fn (u16) void => element.FN_IU16_OVOID,
fn (usize) void => element.FN_IUSIZE_OVOID,
fn (u16) u8 => element.FN_IU16_OU8, fn (u16) u8 => element.FN_IU16_OU8,
fn (u4, u4) u8 => element.FN_IU4_IU4_OU8, fn (u4, u4) u8 => element.FN_IU4_IU4_OU8,
fn (u8, u8) u16 => element.FN_IU8_IU8_OU16, fn (u8, u8) u16 => element.FN_IU8_IU8_OU16,
fn (u16, u8) void => element.FN_IU16_IU8_OVOID, fn (u16, u8) void => element.FN_IU16_IU8_OVOID,
fn (u16, u16) void => element.FN_IU16_IU16_OVOID, fn (u16, u16) void => element.FN_IU16_IU16_OVOID,
fn (cmos.StatusRegister, bool) u8 => element.FN_IECmosStatusRegister_IBOOL_OU8, fn (cmos.StatusRegister, bool) u8 => element.FN_IECMOSSTATUSREGISTER_IBOOL_OU8,
fn (cmos.StatusRegister, u8, bool) void => element.FN_IECmosStatusRegister_IU8_IBOOL_OVOID, fn (cmos.StatusRegister, u8, bool) void => element.FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID,
fn (cmos.RtcRegister) u8 => element.FN_IECmosRtcRegister_OU8, fn (cmos.RtcRegister) u8 => element.FN_IECMOSRTCREGISTER_OU8,
fn (*const gdt.GdtPtr) void => element.FN_IPTRCONSTGDTPTR_OVOID, fn (*const gdt.GdtPtr) void => element.FN_IPTRCONSTGDTPTR_OVOID,
fn (*const idt.IdtPtr) void => element.FN_IPTRCONSTIDTPTR_OVOID, fn (*const idt.IdtPtr) void => element.FN_IPTRCONSTIDTPTR_OVOID,
fn (u8, fn () callconv(.C) void) idt.IdtError!void => element.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, fn (u8, fn () callconv(.C) void) idt.IdtError!void => element.FN_IU8_IEFNOVOID_OERRORIDTERRORVOID,
fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => element.FN_IU8_INFNOVOID_OERRORIDTERRORVOID, fn (u8, fn () callconv(.Naked) void) idt.IdtError!void => element.FN_IU8_INFNOVOID_OERRORIDTERRORVOID,
fn () gdt.GdtPtr => element.FN_OGDTPTR, fn () gdt.GdtPtr => element.FN_OGDTPTR,
fn () idt.IdtPtr => element.FN_OIDTPTR, fn () idt.IdtPtr => element.FN_OIDTPTR,
fn (idt.IdtEntry) bool => element.FN_IIDTENTRY_OBOOL,
fn (*task.Task, usize) void => element.FN_IPTRTask_IUSIZE_OVOID,
fn (*task.Task, *std.mem.Allocator) void => element.FN_IPTRTASK_IPTRALLOCATOR_OVOID,
fn (fn () void) std.mem.Allocator.Error!*task.Task => element.FN_IFNOVOID_OMEMERRORPTRTASK,
fn (fn () void, *std.mem.Allocator) std.mem.Allocator.Error!*task.Task => element.FN_IFNOVOID_IPTRALLOCATOR_OMEMERRORPTRTASK,
else => @compileError("Type not supported: " ++ @typeName(T)), else => @compileError("Type not supported: " ++ @typeName(T)),
}; };
} }
@ -614,7 +677,7 @@ fn getMockObject() *Mock() {
if (mock) |*m| { if (mock) |*m| {
return m; return m;
} else { } else {
warn("MOCK object doesn't exists, please initiate this test\n", .{}); warn("MOCK object doesn't exists, please initialise this test\n", .{});
expect(false); expect(false);
unreachable; unreachable;
} }

View file

@ -0,0 +1,27 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
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 EntryPointFn = fn () void;
pub const Task = struct {
const Self = @This();
pid: u32,
stack: []u32,
stack_pointer: usize,
pub fn create(entry_point: EntryPointFn, allocator: *Allocator) Allocator.Error!*Task {
return mock_framework.performAction("Task.create", Allocator.Error!*Task, .{ entry_point, allocator });
}
pub fn destroy(self: *Self, allocator: *Allocator) void {
return mock_framework.performAction("Task.destroy", void, .{ self, allocator });
}
};

View file

@ -27,6 +27,9 @@ pub const TestMode = enum {
/// Run the panic runtime test. /// Run the panic runtime test.
Panic, Panic,
/// Run the scheduler runtime test.
Scheduler,
/// ///
/// Return a string description for the test mode provided. /// Return a string description for the test mode provided.
/// ///
@ -41,6 +44,7 @@ pub const TestMode = enum {
.None => "Runs the OS normally (Default)", .None => "Runs the OS normally (Default)",
.Initialisation => "Initialisation runtime tests", .Initialisation => "Initialisation runtime tests",
.Panic => "Panic runtime tests", .Panic => "Panic runtime tests",
.Scheduler => "Scheduler runtime tests",
}; };
} }
}; };
@ -143,6 +147,35 @@ pub const RuntimeStep = struct {
} }
} }
///
/// This tests the OS's scheduling by checking that we schedule a task that prints the success.
///
/// Arguments:
/// IN/OUT self: *RuntimeStep - Self.
///
/// Return: bool
/// Whether the test has passed or failed.
///
fn test_scheduler(self: *RuntimeStep) bool {
var state: usize = 0;
while (true) {
const msg = self.get_msg() catch return false;
defer self.builder.allocator.free(msg);
std.debug.warn("{}\n", .{msg});
// Make sure `[INFO] Switched` then `[INFO] SUCCESS: Scheduler variables preserved` are logged in this order
if (std.mem.eql(u8, msg, "[INFO] Switched") and state == 0) {
state = 1;
} else if (std.mem.eql(u8, msg, "[INFO] SUCCESS: Scheduler variables preserved") and state == 1) {
state = 2;
}
if (state == 2) {
return true;
}
}
}
/// ///
/// The make function that is called by the builder. This will create the qemu process with the /// The make function that is called by the builder. This will create the qemu process with the
/// stdout as a Pipe. Then create the read thread to read the logs from the qemu stdout. Then /// stdout as a Pipe. Then create the read thread to read the logs from the qemu stdout. Then
@ -204,7 +237,7 @@ pub const RuntimeStep = struct {
fn read_logs(self: *RuntimeStep) void { fn read_logs(self: *RuntimeStep) void {
const stream = self.os_proc.stdout.?.reader(); const stream = self.os_proc.stdout.?.reader();
// Line shouldn't be longer than this // Line shouldn't be longer than this
const max_line_length: usize = 128; const max_line_length: usize = 1024;
while (true) { while (true) {
const line = stream.readUntilDelimiterAlloc(self.builder.allocator, '\n', max_line_length) catch |e| switch (e) { const line = stream.readUntilDelimiterAlloc(self.builder.allocator, '\n', max_line_length) catch |e| switch (e) {
error.EndOfStream => { error.EndOfStream => {
@ -212,7 +245,10 @@ pub const RuntimeStep = struct {
// join the thread to exit nicely :) // join the thread to exit nicely :)
return; return;
}, },
else => unreachable, else => {
std.debug.warn("Unexpected error: {}\n", .{e});
unreachable;
},
}; };
// put line in the queue // put line in the queue
@ -270,6 +306,7 @@ pub const RuntimeStep = struct {
.None => print_logs, .None => print_logs,
.Initialisation => test_init, .Initialisation => test_init,
.Panic => test_panic, .Panic => test_panic,
.Scheduler => test_scheduler,
}, },
}; };
return runtime_step; return runtime_step;