Initial scheduler

Fix TSS

Also change to .{} syntax where appropriate.
Added the SS segment
Fixed spelling

Refactoring GDT


Multitasking working for now


WIP scheduler

Refactored Bitmap a bit

WIP still


Task switching working

Handlers return the stack pointer that will be used to restore the tasks stack, normal handlers will return the same stack pointer it was called with where task switching will return the stack pointer of the next task and restore its state using the interrupt stub.

Initial scheduler done


Created a stage 2 init task


Change u32 to usize


Move Task to arch specific


WIP


WIP2


Removed esp from task, replaced with stack_pointer


Removed the debug logs


Fixed init task stack


Change pickNextTask to pointer manipulation

This allows less allocations so faster switching

Temporary enable interrupts for some runtime tests

PIT and RTC need interrupts enabled to run their runtime tests

Renamed schedule => pickNextTask, comptime bitmap for pids not task init

And some other stuff: No pub for the task anymore
Use the leak detector allocator

Fmt


Fix unit tests

And some other stuff :P

PR review

Moved Task out of arch and have the stack init in the arch file
Mocking clean up
Removed commented code
Renamed createTask to scheduleTask where the user will have to provide a task to schedule
Removed redundant pub in log runtime test
Removed global allocator for scheduler
Cleaner assembly in paging

Fmt


Added new Scheduler test mode


Added new test mode to CI


Removed one of the prints


Added doc comment, task test for i386


Removed test


WIP


Runtime tests work

Have a global set in one task and reacted to in another. Also test that local variables are preserved after a task switch.

Removed new lines


Increased line length


Move the allocation of the bool above the task creation
This commit is contained in:
DrDeano 2020-07-18 22:46:24 +01:00
parent 20826548e8
commit d600be874c
No known key found for this signature in database
GPG key ID: 96188600582B9ED7
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 {
enableInterrupts();
while (true) { while (true) {
enableInterrupts();
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;