diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9c03a20..a3f2422 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,3 +37,5 @@ jobs: run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Initialisation ${{ matrix.build_mode }} - name: Run runtime test - Panic 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 }} diff --git a/.gitignore b/.gitignore index 6b6d674..e65d384 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ # Intellij .idea/ +# VSCode +.vscode/ + # Zig zig-cache diff --git a/build.zig b/build.zig index abd3a53..2ebb6c4 100644 --- a/build.zig +++ b/build.zig @@ -3,8 +3,6 @@ const builtin = @import("builtin"); const rt = @import("test/runtime_test.zig"); const RuntimeStep = rt.RuntimeStep; const Builder = std.build.Builder; -const LibExeObjStep = std.build.LibExeObjStep; -const Step = std.build.Step; const Target = std.Target; const CrossTarget = std.zig.CrossTarget; const fs = std.fs; diff --git a/src/kernel/arch.zig b/src/kernel/arch.zig index b96300e..8cf9be0 100644 --- a/src/kernel/arch.zig +++ b/src/kernel/arch.zig @@ -1,8 +1,10 @@ +const std = @import("std"); const builtin = @import("builtin"); const is_test = builtin.is_test; 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"), else => unreachable, }; diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index 6866309..498ccce 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -1,47 +1,54 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const builtin = @import("builtin"); +const cmos = @import("cmos.zig"); const gdt = @import("gdt.zig"); const idt = @import("idt.zig"); -const pic = @import("pic.zig"); const irq = @import("irq.zig"); const isr = @import("isr.zig"); +const paging = @import("paging.zig"); +const pic = @import("pic.zig"); const pit = @import("pit.zig"); const rtc = @import("rtc.zig"); const serial = @import("serial.zig"); -const paging = @import("paging.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 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 panic = @import("../../panic.zig").panic; const TTY = @import("../../tty.zig").TTY; const MemProfile = mem.MemProfile; -/// The virtual end of the kernel code +/// The virtual end of the kernel code. 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; -/// The physical end of the kernel code +/// The physical end of the kernel code. 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; -/// 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; +/// 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 /// and the interrupt number and error code (if there is one). -pub const InterruptContext = struct { +pub const CpuState = packed struct { // Extra segments + ss: u32, gs: u32, fs: u32, es: u32, @@ -68,7 +75,7 @@ pub const InterruptContext = struct { cs: u32, eflags: u32, user_esp: u32, - ss: u32, + user_ss: u32, }; /// 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. 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. /// @@ -239,10 +249,9 @@ pub fn halt() void { /// Wait the kernel but still can handle interrupts. /// pub fn spinWait() noreturn { + enableInterrupts(); while (true) { - enableInterrupts(); halt(); - disableInterrupts(); } } @@ -312,13 +321,21 @@ pub fn initTTY(boot_payload: BootPayload) TTY { /// Return: mem.MemProfile /// The constructed memory profile /// -/// Error: std.mem.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 +/// Error: Allocator.Error +/// 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", .{}); 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; mem.ADDR_OFFSET = @ptrToInt(&KERNEL_ADDR_OFFSET); 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 }); } } + // Map the multiboot info struct itself const mb_region = mem.Range{ .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 /// /// 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 /// paging. /// IN allocator: *Allocator - The allocator use to handle memory. -/// IN comptime options: type - The build options that is passed to the kernel to be -/// used for run time testing. /// -pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile, allocator: *Allocator) void { - disableInterrupts(); - +pub fn init(boot_payload: BootPayload, mem_profile: *const MemProfile, allocator: *Allocator) void { gdt.init(); idt.init(); @@ -404,15 +466,13 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile isr.init(); irq.init(); - paging.init(mb_info, mem_profile, allocator); + paging.init(boot_payload, mem_profile, allocator); pit.init(); rtc.init(); syscalls.init(); - enableInterrupts(); - // Initialise the VGA and TTY here since their tests belong the architecture and so should be a part of the // arch init test messages vga.init(); @@ -420,17 +480,5 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile } test "" { - _ = @import("gdt.zig"); - _ = @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"); + std.meta.refAllDecls(@This()); } diff --git a/src/kernel/arch/x86/boot.zig b/src/kernel/arch/x86/boot.zig index 8cc075a..22d36a7 100644 --- a/src/kernel/arch/x86/boot.zig +++ b/src/kernel/arch/x86/boot.zig @@ -1,5 +1,5 @@ const constants = @import("constants"); -const multiboot_info = @import("multiboot.zig").multiboot_info_t; +const arch = @import("arch.zig"); /// The multiboot header 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; 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 { // Set the page directory to the boot directory @@ -109,6 +109,6 @@ export fn start_higher_half() callconv(.Naked) noreturn { \\mov %%ebx, %[res] : [res] "=r" (-> usize) ) + @ptrToInt(&KERNEL_ADDR_OFFSET); - kmain(@intToPtr(*multiboot_info, mb_info_addr)); + kmain(@intToPtr(arch.BootPayload, mb_info_addr)); while (true) {} } diff --git a/src/kernel/arch/x86/gdt.zig b/src/kernel/arch/x86/gdt.zig index a11a7c8..4429ee5 100644 --- a/src/kernel/arch/x86/gdt.zig +++ b/src/kernel/arch/x86/gdt.zig @@ -84,27 +84,31 @@ const GdtEntry = packed struct { }; /// The TSS entry structure -const TtsEntry = packed struct { +const Tss = packed struct { /// Pointer to the previous TSS entry - prev_tss: u32, + prev_tss: u16, + reserved1: u16, /// Ring 0 32 bit stack pointer. esp0: u32, /// Ring 0 32 bit stack pointer. - ss0: u32, + ss0: u16, + reserved2: u16, /// Ring 1 32 bit stack pointer. esp1: u32, /// Ring 1 32 bit stack pointer. - ss1: u32, + ss1: u16, + reserved3: u16, /// Ring 2 32 bit stack pointer. esp2: u32, /// Ring 2 32 bit stack pointer. - ss2: u32, + ss2: u16, + reserved4: u16, /// The CR3 control register 3. cr3: u32, @@ -140,25 +144,32 @@ const TtsEntry = packed struct { edi: u32, /// The extra segment. - es: u32, + es: u16, + reserved5: u16, /// The code segment. - cs: u32, + cs: u16, + reserved6: u16, /// The stack segment. - ss: u32, + ss: u16, + reserved7: u16, /// The data segment. - ds: u32, + ds: u16, + reserved8: u16, /// A extra segment FS. - fs: u32, + fs: u16, + reserved9: u16, /// A extra segment GS. - gs: u32, + gs: u16, + reserved10: u16, /// The local descriptor table register. - ldtr: u32, + ldtr: u16, + reserved11: u16, /// ? trap: u16, @@ -177,8 +188,8 @@ pub const GdtPtr = packed struct { base: u32, }; -/// The total number of entries in the GTD: null, kernel code, kernel data, user code, user data -/// and TSS +/// The total number of entries in the GDT including: null, kernel code, kernel data, user code, +/// user data and the TSS. const NUMBER_OF_ENTRIES: u16 = 0x06; /// 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; /// 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 - makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS), + gdt_entries_temp[0] = makeGdtEntry(0, 0, NULL_SEGMENT, NULL_FLAGS); - // Kernel Code - makeEntry(0, 0xFFFFF, KERNEL_SEGMENT_CODE, PAGING_32_BIT), + // Kernel code descriptor + gdt_entries_temp[1] = makeGdtEntry(0, 0xFFFFF, KERNEL_SEGMENT_CODE, PAGING_32_BIT); - // Kernel Data - makeEntry(0, 0xFFFFF, KERNEL_SEGMENT_DATA, PAGING_32_BIT), + // Kernel data descriptor + gdt_entries_temp[2] = makeGdtEntry(0, 0xFFFFF, KERNEL_SEGMENT_DATA, PAGING_32_BIT); - // User Code - makeEntry(0, 0xFFFFF, USER_SEGMENT_CODE, PAGING_32_BIT), + // User code descriptor + gdt_entries_temp[3] = makeGdtEntry(0, 0xFFFFF, USER_SEGMENT_CODE, PAGING_32_BIT); - // User Data - makeEntry(0, 0xFFFFF, USER_SEGMENT_DATA, PAGING_32_BIT), + // User data descriptor + gdt_entries_temp[4] = makeGdtEntry(0, 0xFFFFF, USER_SEGMENT_DATA, PAGING_32_BIT); - // Fill in TSS at runtime - makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS), + // TSS descriptor, one each for each processor + // 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 @@ -342,35 +357,12 @@ var gdt_ptr: GdtPtr = GdtPtr{ .base = undefined, }; -/// The task state segment entry. -var tss: TtsEntry = TtsEntry{ - .prev_tss = 0, - .esp0 = 0, - .ss0 = KERNEL_DATA_OFFSET, - .esp1 = 0, - .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), +/// The main task state segment entry. +var main_tss_entry: Tss = init: { + var tss_temp = std.mem.zeroes(Tss); + tss_temp.ss0 = KERNEL_DATA_OFFSET; + tss_temp.io_permissions_base_offset = @sizeOf(Tss); + break :init tss_temp; }; /// @@ -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 /// limit at 0xFFFFF. /// -fn makeEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntry { - return GdtEntry{ +fn makeGdtEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntry { + return .{ .limit_low = @truncate(u16, limit), .base_low = @truncate(u24, base), - .access = AccessBits{ + .access = .{ .accessed = access.accessed, .read_write = access.read_write, .direction_conforming = access.direction_conforming, @@ -400,7 +392,7 @@ fn makeEntry(base: u32, limit: u20, access: AccessBits, flags: FlagBits) GdtEntr .present = access.present, }, .limit_high = @truncate(u4, limit >> 16), - .flags = FlagBits{ + .flags = .{ .reserved_zero = flags.reserved_zero, .is_64_bit = flags.is_64_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. /// @@ -427,7 +409,7 @@ pub fn init() void { log.logInfo("Init gdt\n", .{}); defer log.logInfo("Done gdt\n", .{}); // 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. 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(FlagBits)); expectEqual(@as(u32, 8), @sizeOf(GdtEntry)); - expectEqual(@as(u32, 104), @sizeOf(TtsEntry)); + expectEqual(@as(u32, 104), @sizeOf(Tss)); expectEqual(@as(u32, 6), @sizeOf(GdtPtr)); const null_entry = gdt_entries[NULL_INDEX]; @@ -476,45 +458,45 @@ test "GDT entries" { expectEqual(TABLE_SIZE, gdt_ptr.limit); - 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(u32, 0), main_tss_entry.prev_tss); + expectEqual(@as(u32, 0), main_tss_entry.esp0); + expectEqual(@as(u32, KERNEL_DATA_OFFSET), main_tss_entry.ss0); + expectEqual(@as(u32, 0), main_tss_entry.esp1); + expectEqual(@as(u32, 0), main_tss_entry.ss1); + expectEqual(@as(u32, 0), main_tss_entry.esp2); + expectEqual(@as(u32, 0), main_tss_entry.ss2); + expectEqual(@as(u32, 0), main_tss_entry.cr3); + expectEqual(@as(u32, 0), main_tss_entry.eip); + expectEqual(@as(u32, 0), main_tss_entry.eflags); + expectEqual(@as(u32, 0), main_tss_entry.eax); + expectEqual(@as(u32, 0), main_tss_entry.ecx); + expectEqual(@as(u32, 0), main_tss_entry.edx); + expectEqual(@as(u32, 0), main_tss_entry.ebx); + expectEqual(@as(u32, 0), main_tss_entry.esp); + expectEqual(@as(u32, 0), main_tss_entry.ebp); + expectEqual(@as(u32, 0), main_tss_entry.esi); + expectEqual(@as(u32, 0), main_tss_entry.edi); + expectEqual(@as(u32, 0), main_tss_entry.es); + expectEqual(@as(u32, 0), main_tss_entry.cs); + expectEqual(@as(u32, 0), main_tss_entry.ss); + expectEqual(@as(u32, 0), main_tss_entry.ds); + expectEqual(@as(u32, 0), main_tss_entry.fs); + expectEqual(@as(u32, 0), main_tss_entry.gs); + expectEqual(@as(u32, 0), main_tss_entry.ldtr); + expectEqual(@as(u16, 0), main_tss_entry.trap); - // Size of TtsEntry will fit in a u16 as 104 < 65535 (2^16) - expectEqual(@as(u16, @sizeOf(TtsEntry)), tss.io_permissions_base_offset); + // Size of Tss will fit in a u16 as 104 < 65535 (2^16) + expectEqual(@as(u16, @sizeOf(Tss)), main_tss_entry.io_permissions_base_offset); } -test "makeEntry NULL" { - const actual = makeEntry(0, 0, NULL_SEGMENT, NULL_FLAGS); +test "makeGdtEntry NULL" { + const actual = makeGdtEntry(0, 0, NULL_SEGMENT, NULL_FLAGS); const expected: u64 = 0; expectEqual(expected, @bitCast(u64, actual)); } -test "makeEntry alternating bit pattern" { +test "makeGdtEntry alternating bit pattern" { const alt_access = AccessBits{ .accessed = 1, .read_write = 0, @@ -536,106 +518,12 @@ test "makeEntry alternating bit pattern" { 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; 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" { // Set up arch.initTest(); @@ -650,8 +538,8 @@ test "init" { // Post testing const tss_entry = gdt_entries[TSS_INDEX]; - const tss_limit = @sizeOf(TtsEntry) - 1; - const tss_addr = @ptrToInt(&tss); + const tss_limit = @sizeOf(Tss) - 1; + const tss_addr = @ptrToInt(&main_tss_entry); var expected: u64 = 0; expected |= @as(u64, @truncate(u16, tss_limit)); @@ -665,7 +553,7 @@ test "init" { // Reset 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. /// -fn runtimeTests() void { +pub fn runtimeTests() void { rt_loadedGDTSuccess(); } diff --git a/src/kernel/arch/x86/idt.zig b/src/kernel/arch/x86/idt.zig index 3e311f8..9758d0f 100644 --- a/src/kernel/arch/x86/idt.zig +++ b/src/kernel/arch/x86/idt.zig @@ -340,6 +340,6 @@ fn rt_loadedIDTSuccess() void { /// /// Run all the runtime tests. /// -fn runtimeTests() void { +pub fn runtimeTests() void { rt_loadedIDTSuccess(); } diff --git a/src/kernel/arch/x86/interrupts.zig b/src/kernel/arch/x86/interrupts.zig index d02741c..8cb2c92 100644 --- a/src/kernel/arch/x86/interrupts.zig +++ b/src/kernel/arch/x86/interrupts.zig @@ -3,22 +3,22 @@ const syscalls = @import("syscalls.zig"); const irq = @import("irq.zig"); const idt = @import("idt.zig"); -extern fn irqHandler(ctx: *arch.InterruptContext) void; -extern fn isrHandler(ctx: *arch.InterruptContext) void; +extern fn irqHandler(ctx: *arch.CpuState) usize; +extern fn isrHandler(ctx: *arch.CpuState) usize; /// /// The main handler for all exceptions and interrupts. This will then go and call the correct /// handler for an ISR or IRQ. /// /// Arguments: -/// IN ctx: *arch.InterruptContext - Pointer to the exception context containing the contents -/// of the registers at the time of a exception. +/// IN ctx: *arch.CpuState - Pointer to the exception context containing the contents +/// 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) { - isrHandler(ctx); + return isrHandler(ctx); } else { - irqHandler(ctx); + return irqHandler(ctx); } } @@ -32,6 +32,7 @@ export fn commonStub() callconv(.Naked) void { \\push %%es \\push %%fs \\push %%gs + \\push %%ss \\mov $0x10, %%ax \\mov %%ax, %%ds \\mov %%ax, %%es @@ -40,7 +41,8 @@ export fn commonStub() callconv(.Naked) void { \\mov %%esp, %%eax \\push %%eax \\call handler - \\pop %%eax + \\mov %%eax, %%esp + \\pop %%ss \\pop %%gs \\pop %%fs \\pop %%es diff --git a/src/kernel/arch/x86/irq.zig b/src/kernel/arch/x86/irq.zig index 7f1defe..76dbaaf 100644 --- a/src/kernel/arch/x86/irq.zig +++ b/src/kernel/arch/x86/irq.zig @@ -26,7 +26,7 @@ pub const IrqError = error{ const NUMBER_OF_ENTRIES: u16 = 16; /// 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. 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. /// /// 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. /// -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. if (ctx.int_num < IRQ_OFFSET) { panic(@errorReturnTrace(), "Not an IRQ number: {}\n", .{ctx.int_num}); } + var ret_esp = @ptrToInt(ctx); + const irq_offset = ctx.int_num - IRQ_OFFSET; if (isValidIrq(irq_offset)) { // IRQ index is valid so can truncate @@ -54,7 +56,7 @@ export fn irqHandler(ctx: *arch.InterruptContext) void { if (irq_handlers[irq_num]) |handler| { // Make sure it isn't a spurious irq if (!pic.spuriousIrq(irq_num)) { - handler(ctx); + ret_esp = handler(ctx); // Send the end of interrupt command pic.sendEndOfInterrupt(irq_num); } @@ -64,6 +66,7 @@ export fn irqHandler(ctx: *arch.InterruptContext) void { } else { panic(@errorReturnTrace(), "Invalid IRQ index: {}", .{irq_offset}); } + return ret_esp; } /// @@ -143,8 +146,12 @@ pub fn init() void { } fn testFunction0() callconv(.Naked) void {} -fn testFunction1(ctx: *arch.InterruptContext) void {} -fn testFunction2(ctx: *arch.InterruptContext) void {} +fn testFunction1(ctx: *arch.CpuState) u32 { + return 0; +} +fn testFunction2(ctx: *arch.CpuState) u32 { + return 0; +} test "openIrq" { idt.initTest(); @@ -264,7 +271,7 @@ fn rt_openedIdtEntries() void { /// /// Run all the runtime tests. /// -fn runtimeTests() void { +pub fn runtimeTests() void { rt_unregisteredHandlers(); rt_openedIdtEntries(); } diff --git a/src/kernel/arch/x86/isr.zig b/src/kernel/arch/x86/isr.zig index 2292fd0..414a490 100644 --- a/src/kernel/arch/x86/isr.zig +++ b/src/kernel/arch/x86/isr.zig @@ -23,7 +23,7 @@ pub const IsrError = error{ }; /// 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. 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. /// /// 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. /// -export fn isrHandler(ctx: *arch.InterruptContext) void { +export fn isrHandler(ctx: *arch.CpuState) usize { // Get the interrupt number const isr_num = ctx.int_num; + var ret_esp = @ptrToInt(ctx); + if (isValidIsr(isr_num)) { if (isr_num == syscalls.INTERRUPT) { // A syscall, so use the syscall handler if (syscall_handler) |handler| { - handler(ctx); + ret_esp = handler(ctx); } else { panic(@errorReturnTrace(), "Syscall handler not registered\n", .{}); } } else { if (isr_handlers[isr_num]) |handler| { // Regular ISR exception, if there is one registered. - handler(ctx); + ret_esp = handler(ctx); } 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 { 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 testFunction1(ctx: *arch.InterruptContext) void {} -fn testFunction2(ctx: *arch.InterruptContext) void {} -fn testFunction3(ctx: *arch.InterruptContext) void {} -fn testFunction4(ctx: *arch.InterruptContext) void {} +fn testFunction1(ctx: *arch.CpuState) u32 { + return 0; +} +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" { idt.initTest(); @@ -397,7 +409,7 @@ fn rt_openedIdtEntries() void { /// /// Run all the runtime tests. /// -fn runtimeTests() void { +pub fn runtimeTests() void { rt_unregisteredHandlers(); rt_openedIdtEntries(); } diff --git a/src/kernel/arch/x86/link.ld b/src/kernel/arch/x86/link.ld index e797bd1..3729ebd 100644 --- a/src/kernel/arch/x86/link.ld +++ b/src/kernel/arch/x86/link.ld @@ -35,6 +35,7 @@ SECTIONS { } .bss.stack ALIGN(4K) : AT (ADDR(.bss.stack) - KERNEL_ADDR_OFFSET) { + KERNEL_STACK_START = .; KEEP(*(.bss.stack)) KERNEL_STACK_END = .; } diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index 54b94d9..27020e8 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -1,9 +1,13 @@ const std = @import("std"); -const expectEqual = std.testing.expectEqual; -const expect = std.testing.expect; +const testing = std.testing; +const expectEqual = testing.expectEqual; +const expect = testing.expect; const builtin = @import("builtin"); +const is_test = builtin.is_test; 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 MemProfile = @import("../../mem.zig").MemProfile; const tty = @import("../../tty.zig"); @@ -11,8 +15,6 @@ const log = @import("../../log.zig"); const mem = @import("../../mem.zig"); const vmm = @import("../../vmm.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. 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. /// /// 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}); - var cr0: u32 = 0; - var cr2: u32 = 0; - var cr3: u32 = 0; - var cr4: u32 = 0; - asm volatile ("mov %%cr0, %[cr0]" - : [cr0] "=r" (cr0) + var cr0 = asm volatile ("mov %%cr0, %[cr0]" + : [cr0] "=r" (-> u32) ); - asm volatile ("mov %%cr2, %[cr2]" - : [cr2] "=r" (cr2) + var cr2 = asm volatile ("mov %%cr2, %[cr2]" + : [cr2] "=r" (-> u32) ); - asm volatile ("mov %%cr3, %[cr3]" - : [cr3] "=r" (cr3) + var cr3 = asm volatile ("mov %%cr3, %[cr3]" + : [cr3] "=r" (-> u32) ); - asm volatile ("mov %%cr4, %[cr4]" - : [cr4] "=r" (cr4) + var cr4 = asm volatile ("mov %%cr4, %[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"); } @@ -551,10 +549,12 @@ extern var rt_fault_callback2: *u32; var faulted = false; var use_callback2 = false; -fn rt_pageFault(ctx: *arch.InterruptContext) void { +fn rt_pageFault(ctx: *arch.CpuState) u32 { faulted = true; // Return to the 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 { @@ -592,7 +592,7 @@ fn rt_accessMappedMem(v_end: u32) void { 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_accessMappedMem(v_end); } diff --git a/src/kernel/arch/x86/pic.zig b/src/kernel/arch/x86/pic.zig index d8aad23..6d5d93e 100644 --- a/src/kernel/arch/x86/pic.zig +++ b/src/kernel/arch/x86/pic.zig @@ -830,6 +830,6 @@ fn rt_picAllMasked() void { /// /// Run all the runtime tests. /// -fn runtimeTests() void { +pub fn runtimeTests() void { rt_picAllMasked(); } diff --git a/src/kernel/arch/x86/pit.zig b/src/kernel/arch/x86/pit.zig index d9f733f..48efd42 100644 --- a/src/kernel/arch/x86/pit.zig +++ b/src/kernel/arch/x86/pit.zig @@ -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. /// /// 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. /// -fn pitHandler(ctx: *arch.InterruptContext) void { +fn pitHandler(ctx: *arch.CpuState) usize { 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; while (ticks > wait_ticks1) { - arch.enableInterrupts(); arch.halt(); - arch.disableInterrupts(); } while (ticks < wait_ticks2) { - arch.enableInterrupts(); arch.halt(); - arch.disableInterrupts(); } - arch.enableInterrupts(); } else { const wait_ticks = ticks + ticks_to_wait; while (ticks < wait_ticks) { - arch.enableInterrupts(); arch.halt(); - arch.disableInterrupts(); } - arch.enableInterrupts(); } } @@ -635,7 +628,11 @@ fn rt_initCounter_0() void { /// /// 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_waitTicks(); rt_waitTicks2(); diff --git a/src/kernel/arch/x86/rtc.zig b/src/kernel/arch/x86/rtc.zig index 3500885..a82c65d 100644 --- a/src/kernel/arch/x86/rtc.zig +++ b/src/kernel/arch/x86/rtc.zig @@ -6,13 +6,14 @@ const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; const build_options = @import("build_options"); 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 pic = @import("pic.zig"); const pit = @import("pit.zig"); const irq = @import("irq.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 scheduler = @import("../../scheduler.zig"); /// The Century register is unreliable. We need a APIC interface to infer if we have a century /// 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. 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 /// registers so don't get inconsistent values. @@ -206,14 +209,26 @@ fn readRtc() DateTime { /// The interrupt handler for the RTC. /// /// 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. /// -fn rtcHandler(ctx: *arch.InterruptContext) void { +fn rtcHandler(ctx: *arch.CpuState) usize { 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 + // Might need to disable the NMI bit, set to true 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 setRate(7) catch |err| switch (err) { error.RateError => { @@ -277,9 +289,6 @@ pub fn init() void { // Enable RTC interrupts enableInterrupts(); - // Can now enable interrupts - arch.enableInterrupts(); - // Read status register C to clear any interrupts that may have happened during set up const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false); @@ -739,7 +748,15 @@ fn rt_interrupts() void { /// /// Run all the runtime tests. /// -fn runtimeTests() void { +pub fn runtimeTests() void { 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(); + arch.disableInterrupts(); + // Can enable it back + schedule = true; } diff --git a/src/kernel/arch/x86/syscalls.zig b/src/kernel/arch/x86/syscalls.zig index c314518..5c77720 100644 --- a/src/kernel/arch/x86/syscalls.zig +++ b/src/kernel/arch/x86/syscalls.zig @@ -1,9 +1,13 @@ -const arch = @import("arch.zig"); -const testing = @import("std").testing; -const assert = @import("std").debug.assert; +const std = @import("std"); +const builtin = @import("builtin"); +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 log = @import("../../log.zig"); -const build_options = @import("build_options"); const panic = @import("../../panic.zig").panic; /// The isr number associated with syscalls @@ -13,7 +17,7 @@ pub const INTERRUPT: u16 = 0x80; pub const NUM_HANDLERS: u16 = 256; /// 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 pub const SyscallError = error{ @@ -44,10 +48,10 @@ pub fn isValidSyscall(syscall: u32) bool { /// warning is logged. /// /// 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. /// -fn handle(ctx: *arch.InterruptContext) void { +fn handle(ctx: *arch.CpuState) u32 { // The syscall number is put in eax const syscall = ctx.eax; if (isValidSyscall(syscall)) { @@ -59,6 +63,7 @@ fn handle(ctx: *arch.InterruptContext) void { } else { 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. /// /// 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. /// /// Return: u32 /// 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) { 0 => ctx.ebx, 1 => ctx.ecx, @@ -252,32 +257,32 @@ pub fn init() void { /// Tests 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; 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; 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; 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; 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; 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; return 5; } @@ -287,7 +292,7 @@ test "registerSyscall returns SyscallExists" { registerSyscall(123, testHandler0) catch |err| { return; }; - assert(false); + expect(false); } fn runtimeTests() void { diff --git a/src/kernel/bitmap.zig b/src/kernel/bitmap.zig index 4e34b78..b5c4ab7 100644 --- a/src/kernel/bitmap.zig +++ b/src/kernel/bitmap.zig @@ -1,6 +1,7 @@ const std = @import("std"); const builtin = @import("builtin"); const testing = std.testing; +const Allocator = std.mem.Allocator; /// /// 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[1], 1); } + test "setFirstFree" { var bmp = try Bitmap(u32).init(32, std.heap.page_allocator); diff --git a/src/kernel/heap.zig b/src/kernel/heap.zig index f1a699c..ce40203 100644 --- a/src/kernel/heap.zig +++ b/src/kernel/heap.zig @@ -6,7 +6,7 @@ const is_test = builtin.is_test; const build_options = @import("build_options"); const mock_path = build_options.mock_path; 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 FreeListAllocator = struct { diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 0cebd74..1a9e1df 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -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 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 task = if (is_test) @import(mock_path ++ "task_mock.zig") else @import("task.zig"); const heap = @import("heap.zig"); +const scheduler = @import("scheduler.zig"); comptime { 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 // 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_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 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| { panic_root.panic(@errorReturnTrace(), "Failed to initialise kernel heap: {}\n", .{e}); }; + 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", .{}); + // 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(); const logo = \\ _____ _ _ _ _______ ____ @@ -88,5 +126,6 @@ export fn kmain(boot_payload: arch.BootPayload) void { else => {}, } + // Can't return for now, later this can return maybe arch.spinWait(); } diff --git a/src/kernel/log.zig b/src/kernel/log.zig index 625b8b0..ae187f5 100644 --- a/src/kernel/log.zig +++ b/src/kernel/log.zig @@ -1,7 +1,8 @@ const build_options = @import("build_options"); const std = @import("std"); -const Serial = @import("serial.zig").Serial; const fmt = std.fmt; +const Serial = @import("serial.zig").Serial; +const scheduler = @import("scheduler.zig"); /// The errors that can occur when logging 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. /// 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; + scheduler.taskSwitching(true); } /// @@ -118,7 +121,7 @@ pub fn init(ser: Serial) void { /// /// The logging runtime tests that will test all logging levels. /// -pub fn runtimeTests() void { +fn runtimeTests() void { inline for (@typeInfo(Level).Enum.fields) |field| { const level = @field(Level, field.name); log(level, "Test " ++ field.name ++ " level\n", .{}); diff --git a/src/kernel/pmm.zig b/src/kernel/pmm.zig index 732d1f3..62478c2 100644 --- a/src/kernel/pmm.zig +++ b/src/kernel/pmm.zig @@ -2,7 +2,7 @@ const is_test = @import("builtin").is_test; const std = @import("std"); const build_options = @import("build_options"); 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 testing = std.testing; const panic = @import("panic.zig").panic; diff --git a/src/kernel/scheduler.zig b/src/kernel/scheduler.zig new file mode 100644 index 0000000..e696199 --- /dev/null +++ b/src/kernel/scheduler.zig @@ -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) {} +} diff --git a/src/kernel/task.zig b/src/kernel/task.zig new file mode 100644 index 0000000..68e479a --- /dev/null +++ b/src/kernel/task.zig @@ -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); +} diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index 554ef9b..47e7740 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -9,6 +9,8 @@ const paging = @import("paging_mock.zig"); const Serial = @import("../../../src/kernel/serial.zig").Serial; const TTY = @import("../../../src/kernel/tty.zig").TTY; +pub const task = @import("task_mock.zig"); + const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; pub const freeTest = mock_framework.freeTest; @@ -16,7 +18,8 @@ pub const addTestParams = mock_framework.addTestParams; pub const addConsumeFunction = mock_framework.addConsumeFunction; pub const addRepeatFunction = mock_framework.addRepeatFunction; -pub const InterruptContext = struct { +pub const CpuState = struct { + ss: u32, gs: u32, fs: u32, es: u32, @@ -35,14 +38,16 @@ pub const InterruptContext = struct { cs: u32, eflags: u32, user_esp: u32, - ss: u32, + user_ss: u32, }; pub const VmmPayload = u8; pub const KERNEL_VMM_PAYLOAD: usize = 0; 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 BootPayload = u8; +pub const Task = task.Task; // The virtual/physical start/end of the kernel code 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 { - // 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 :) //return mock_framework.performAction("init", void, mem_profile, allocator); } diff --git a/test/mock/kernel/gdt_mock.zig b/test/mock/kernel/gdt_mock.zig index 031d697..2c65f8b 100644 --- a/test/mock/kernel/gdt_mock.zig +++ b/test/mock/kernel/gdt_mock.zig @@ -35,7 +35,7 @@ const GdtEntry = packed struct { base_high: u8, }; -const TtsEntry = packed struct { +const Tss = packed struct { prev_tss: u32, esp0: u32, ss0: u32, @@ -160,10 +160,6 @@ pub const USER_CODE_OFFSET: u16 = 0x18; pub const USER_DATA_OFFSET: u16 = 0x20; pub const TSS_OFFSET: u16 = 0x28; -pub fn setTssStack(esp0: u32) void { - return mock_framework.performAction("setTssStack", void, esp0); -} - pub fn init() void { return mock_framework.performAction("init", void); } diff --git a/test/mock/kernel/idt_mock.zig b/test/mock/kernel/idt_mock.zig index ee5e297..ed50fd2 100644 --- a/test/mock/kernel/idt_mock.zig +++ b/test/mock/kernel/idt_mock.zig @@ -7,7 +7,7 @@ pub const addTestParams = mock_framework.addTestParams; pub const addConsumeFunction = mock_framework.addConsumeFunction; pub const addRepeatFunction = mock_framework.addRepeatFunction; -const IdtEntry = packed struct { +pub const IdtEntry = packed struct { base_low: u16, selector: u16, zero: u8, @@ -34,10 +34,14 @@ const PRIVILEGE_RING_1: u2 = 0x1; const PRIVILEGE_RING_2: u2 = 0x2; 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; +pub fn isIdtOpen(entry: IdtEntry) bool { + return mock_framework.performAction("isIdtOpen", bool, .{entry}); +} + pub fn openInterruptGate(index: u8, handler: InterruptHandler) IdtError!void { return mock_framework.performAction("openInterruptGate", IdtError!void, .{ index, handler }); } diff --git a/test/mock/kernel/mock_framework.zig b/test/mock/kernel/mock_framework.zig index 642b8d7..2508674 100644 --- a/test/mock/kernel/mock_framework.zig +++ b/test/mock/kernel/mock_framework.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const StringHashMap = std.StringHashMap; const expect = std.testing.expect; const expectEqual = std.testing.expectEqual; @@ -8,6 +9,7 @@ const warn = std.debug.warn; const gdt = @import("gdt_mock.zig"); const idt = @import("idt_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 @@ -19,13 +21,18 @@ const DataElementType = enum { U8, U16, U32, - ECmosStatusRegister, - ECmosRtcRegister, - PTR_CONST_GdtPtr, - PTR_CONST_IdtPtr, - GdtPtr, - IdtPtr, + USIZE, + PTR_ALLOCATOR, + ECMOSSTATUSREGISTER, + ECMOSRTCREGISTER, + GDTPTR, + IDTPTR, + IDTENTRY, + PTR_CONST_GDTPTR, + PTR_CONST_IDTPTR, ERROR_IDTERROR_VOID, + ERROR_MEM_PTRTASK, + PTR_TASK, EFN_OVOID, NFN_OVOID, FN_OVOID, @@ -34,20 +41,26 @@ const DataElementType = enum { FN_IU8_OBOOL, FN_IU8_OVOID, FN_IU16_OVOID, + FN_IUSIZE_OVOID, FN_IU16_OU8, FN_IU4_IU4_OU8, FN_IU8_IU8_OU16, FN_IU16_IU8_OVOID, FN_IU16_IU16_OVOID, - FN_IECmosStatusRegister_IBOOL_OU8, - FN_IECmosStatusRegister_IU8_IBOOL_OVOID, - FN_IECmosRtcRegister_OU8, + FN_IECMOSSTATUSREGISTER_IBOOL_OU8, + FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID, + FN_IECMOSRTCREGISTER_OU8, FN_IU8_IEFNOVOID_OERRORIDTERRORVOID, FN_IU8_INFNOVOID_OERRORIDTERRORVOID, FN_IPTRCONSTGDTPTR_OVOID, FN_IPTRCONSTIDTPTR_OVOID, FN_OGDTPTR, 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, U16: u16, U32: u32, - ECmosStatusRegister: cmos.StatusRegister, - ECmosRtcRegister: cmos.RtcRegister, - PTR_CONST_GdtPtr: *const gdt.GdtPtr, - IdtPtr: idt.IdtPtr, - GdtPtr: gdt.GdtPtr, - PTR_CONST_IdtPtr: *const idt.IdtPtr, + USIZE: usize, + PTR_ALLOCATOR: *std.mem.Allocator, + ECMOSSTATUSREGISTER: cmos.StatusRegister, + ECMOSRTCREGISTER: cmos.RtcRegister, + GDTPTR: gdt.GdtPtr, + 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_MEM_PTRTASK: std.mem.Allocator.Error!*task.Task, + PTR_TASK: *task.Task, EFN_OVOID: fn () callconv(.C) void, NFN_OVOID: fn () callconv(.Naked) void, FN_OVOID: fn () void, @@ -78,21 +96,27 @@ const DataElement = union(DataElementType) { FN_OU16: fn () u16, FN_IU8_OBOOL: fn (u8) bool, FN_IU8_OVOID: fn (u8) void, + FN_IUSIZE_OVOID: fn (usize) void, FN_IU16_OVOID: fn (u16) void, FN_IU16_OU8: fn (u16) u8, FN_IU4_IU4_OU8: fn (u4, u4) u8, FN_IU8_IU8_OU16: fn (u8, u8) u16, FN_IU16_IU8_OVOID: fn (u16, u8) void, FN_IU16_IU16_OVOID: fn (u16, u16) void, - FN_IECmosStatusRegister_IBOOL_OU8: fn (cmos.StatusRegister, bool) u8, - FN_IECmosStatusRegister_IU8_IBOOL_OVOID: fn (cmos.StatusRegister, u8, bool) void, - FN_IECmosRtcRegister_OU8: fn (cmos.RtcRegister) u8, + FN_IECMOSSTATUSREGISTER_IBOOL_OU8: fn (cmos.StatusRegister, bool) u8, + FN_IECMOSSTATUSREGISTER_IU8_IBOOL_OVOID: fn (cmos.StatusRegister, u8, bool) void, + FN_IECMOSRTCREGISTER_OU8: fn (cmos.RtcRegister) u8, 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_IPTRCONSTGDTPTR_OVOID: fn (*const gdt.GdtPtr) void, FN_IPTRCONSTIDTPTR_OVOID: fn (*const idt.IdtPtr) void, FN_OGDTPTR: fn () gdt.GdtPtr, 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 }, u16 => DataElement{ .U16 = arg }, u32 => DataElement{ .U32 = arg }, - cmos.StatusRegister => DataElement{ .ECmosStatusRegister = arg }, - cmos.RtcRegister => DataElement{ .ECmosRtcRegister = arg }, - *const gdt.GdtPtr => DataElement{ .PTR_CONST_GdtPtr = arg }, - *const idt.IdtPtr => DataElement{ .PTR_CONST_IdtPtr = arg }, + usize => DataElement{ .USIZE = arg }, + *std.mem.Allocator => DataElement{ .PTR_ALLOCATOR = arg }, + cmos.StatusRegister => DataElement{ .ECMOSSTATUSREGISTER = 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 }, + 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(.Naked) void => DataElement{ .NFN_OVOID = arg }, fn () void => DataElement{ .FN_OVOID = arg }, @@ -185,19 +216,27 @@ fn Mock() type { fn () u16 => DataElement{ .FN_OU16 = arg }, fn (u8) bool => DataElement{ .FN_IU8_OBOOL = 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) u8 => DataElement{ .FN_IU16_OU8 = arg }, fn (u4, u4) u8 => DataElement{ .FN_IU4_IU4_OU8 = arg }, fn (u8, u8) u16 => DataElement{ .FN_IU8_IU8_OU16 = arg }, fn (u16, u8) void => DataElement{ .FN_IU16_IU8_OVOID = arg }, fn (u16, u16) void => DataElement{ .FN_IU16_IU16_OVOID = arg }, - fn (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.RtcRegister) u8 => DataElement{ .FN_IECmosRtcRegister_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.RtcRegister) u8 => DataElement{ .FN_IECMOSRTCREGISTER_OU8 = 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 () 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(.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))), }; } @@ -218,34 +257,46 @@ fn Mock() type { u8 => DataElementType.U8, u16 => DataElementType.U16, u32 => DataElementType.U32, - cmos.StatusRegister => DataElementType.ECmosStatusRegister, - cmos.RtcRegister => DataElementType.ECmosRtcRegister, - *const gdt.GdtPtr => DataElement.PTR_CONST_GdtPtr, - *const idt.IdtPtr => DataElement.PTR_CONST_IdtPtr, - gdt.GdtPtr => DataElement.GdtPtr, - idt.IdtPtr => DataElement.IdtPtr, - idt.IdtError!void => DataElement.ERROR_IDTERROR_VOID, + usize => DataElementType.USIZE, + *std.mem.Allocator => DataElementType.PTR_ALLOCATOR, + cmos.StatusRegister => DataElementType.ECMOSSTATUSREGISTER, + cmos.RtcRegister => DataElementType.ECMOSRTCREGISTER, + gdt.GdtPtr => DataElementType.GDTPTR, + idt.IdtPtr => DataElementType.IDTPTR, + 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(.Naked) void => DataElementType.NFN_OVOID, fn () void => DataElementType.FN_OVOID, + fn () usize => DataElementType.FN_OUSIZE, fn () u16 => DataElementType.FN_OU16, fn (u8) bool => DataElementType.FN_IU8_OBOOL, fn (u8) void => DataElementType.FN_IU8_OVOID, fn (u16) void => DataElementType.FN_IU16_OVOID, + fn (usize) void => DataElementType.FN_IUSIZE_OVOID, fn (u16) u8 => DataElementType.FN_IU16_OU8, fn (u4, u4) u8 => DataElementType.FN_IU4_IU4_OU8, fn (u8, u8) u16 => DataElementType.FN_IU8_IU8_OU16, fn (u16, u8) void => DataElementType.FN_IU16_IU8_OVOID, fn (u16, u16) void => DataElementType.FN_IU16_IU16_OVOID, - fn (cmos.StatusRegister, bool) u8 => DataElementType.FN_IECmosStatusRegister_IBOOL_OU8, - fn (cmos.StatusRegister, u8, bool) void => DataElementType.FN_IECmosStatusRegister_IU8_IBOOL_OVOID, - fn (cmos.RtcRegister) u8 => DataElementType.FN_IECmosRtcRegister_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.RtcRegister) u8 => DataElementType.FN_IECMOSRTCREGISTER_OU8, fn (*const gdt.GdtPtr) void => DataElementType.FN_IPTRCONSTGDTPTR_OVOID, fn (*const idt.IdtPtr) void => DataElementType.FN_IPTRCONSTIDTPTR_OVOID, fn () gdt.GdtPtr => DataElementType.FN_OGDTPTR, fn () idt.IdtPtr => DataElementType.FN_OIDTPTR, 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 (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)), }; } @@ -268,34 +319,46 @@ fn Mock() type { u8 => element.U8, u16 => element.U16, u32 => element.U32, - cmos.StatusRegister => element.ECmosStatusRegister, - cmos.RtcRegister => element.ECmosRtcRegister, - *const gdt.GdtPtr => element.PTR_CONST_GdtPtr, - *const idt.IdtPtr => element.PTR_CONST_IdtPtr, - gdt.GdtPtr => element.GdtPtr, - idt.IdtPtr => element.IdtPtr, + usize => element.USIZE, + *std.mem.Allocator => element.PTR_ALLOCATOR, + cmos.StatusRegister => element.ECMOSSTATUSREGISTER, + gdt.GdtPtr => element.GDTPTR, + 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, + std.mem.Allocator.Error!*task.Task => element.ERROR_MEM_PTRTASK, + *task.Task => element.PTR_TASK, fn () callconv(.C) void => element.EFN_OVOID, fn () callconv(.Naked) void => element.NFN_OVOID, fn () void => element.FN_OVOID, + fn () usize => element.FN_OUSIZE, fn () u16 => element.FN_OU16, fn (u8) bool => element.FN_IU8_OBOOL, fn (u8) void => element.FN_IU8_OVOID, fn (u16) void => element.FN_IU16_OVOID, + fn (usize) void => element.FN_IUSIZE_OVOID, fn (u16) u8 => element.FN_IU16_OU8, fn (u4, u4) u8 => element.FN_IU4_IU4_OU8, fn (u8, u8) u16 => element.FN_IU8_IU8_OU16, fn (u16, u8) void => element.FN_IU16_IU8_OVOID, fn (u16, u16) void => element.FN_IU16_IU16_OVOID, - fn (cmos.StatusRegister, bool) u8 => element.FN_IECmosStatusRegister_IBOOL_OU8, - fn (cmos.StatusRegister, u8, bool) void => element.FN_IECmosStatusRegister_IU8_IBOOL_OVOID, - fn (cmos.RtcRegister) u8 => element.FN_IECmosRtcRegister_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.RtcRegister) u8 => element.FN_IECMOSRTCREGISTER_OU8, fn (*const gdt.GdtPtr) void => element.FN_IPTRCONSTGDTPTR_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(.Naked) void) idt.IdtError!void => element.FN_IU8_INFNOVOID_OERRORIDTERRORVOID, fn () gdt.GdtPtr => element.FN_OGDTPTR, 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)), }; } @@ -614,7 +677,7 @@ fn getMockObject() *Mock() { if (mock) |*m| { return m; } 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); unreachable; } diff --git a/test/mock/kernel/task_mock.zig b/test/mock/kernel/task_mock.zig new file mode 100644 index 0000000..390bfad --- /dev/null +++ b/test/mock/kernel/task_mock.zig @@ -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 }); + } +}; diff --git a/test/runtime_test.zig b/test/runtime_test.zig index bef9918..38536f4 100644 --- a/test/runtime_test.zig +++ b/test/runtime_test.zig @@ -27,6 +27,9 @@ pub const TestMode = enum { /// Run the panic runtime test. Panic, + /// Run the scheduler runtime test. + Scheduler, + /// /// Return a string description for the test mode provided. /// @@ -41,6 +44,7 @@ pub const TestMode = enum { .None => "Runs the OS normally (Default)", .Initialisation => "Initialisation 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 /// 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 { const stream = self.os_proc.stdout.?.reader(); // Line shouldn't be longer than this - const max_line_length: usize = 128; + const max_line_length: usize = 1024; while (true) { const line = stream.readUntilDelimiterAlloc(self.builder.allocator, '\n', max_line_length) catch |e| switch (e) { error.EndOfStream => { @@ -212,7 +245,10 @@ pub const RuntimeStep = struct { // join the thread to exit nicely :) return; }, - else => unreachable, + else => { + std.debug.warn("Unexpected error: {}\n", .{e}); + unreachable; + }, }; // put line in the queue @@ -270,6 +306,7 @@ pub const RuntimeStep = struct { .None => print_logs, .Initialisation => test_init, .Panic => test_panic, + .Scheduler => test_scheduler, }, }; return runtime_step;