Merge pull request #138 from SamTebbs33/feature/initial_multitasking

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

View file

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

3
.gitignore vendored
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -830,6 +830,6 @@ fn rt_picAllMasked() void {
///
/// Run all the runtime tests.
///
fn runtimeTests() void {
pub fn runtimeTests() void {
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.
///
/// Arguments:
/// IN ctx: *arch.InterruptContext - Pointer to the interrupt context containing the contents
/// IN ctx: *arch.CpuState - Pointer to the interrupt context containing the contents
/// of the register at the time of the interrupt.
///
fn pitHandler(ctx: *arch.InterruptContext) void {
fn pitHandler(ctx: *arch.CpuState) usize {
ticks +%= 1;
return @ptrToInt(ctx);
}
///
@ -324,25 +325,17 @@ pub fn waitTicks(ticks_to_wait: u32) void {
const wait_ticks2 = ticks_to_wait - wait_ticks1;
while (ticks > wait_ticks1) {
arch.enableInterrupts();
arch.halt();
arch.disableInterrupts();
}
while (ticks < wait_ticks2) {
arch.enableInterrupts();
arch.halt();
arch.disableInterrupts();
}
arch.enableInterrupts();
} else {
const wait_ticks = ticks + ticks_to_wait;
while (ticks < wait_ticks) {
arch.enableInterrupts();
arch.halt();
arch.disableInterrupts();
}
arch.enableInterrupts();
}
}
@ -635,7 +628,11 @@ fn rt_initCounter_0() void {
///
/// Run all the runtime tests.
///
fn runtimeTests() void {
pub fn runtimeTests() void {
// Interrupts aren't enabled yet, so for the runtime tests, enable it temporary
arch.enableInterrupts();
defer arch.disableInterrupts();
rt_initCounter_0();
rt_waitTicks();
rt_waitTicks2();

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

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