Merge pull request #164 from SamTebbs33/feature/replace-python-rt-with-zig
Replace python rt with zig
This commit is contained in:
commit
10bf2439d9
28 changed files with 667 additions and 405 deletions
12
.github/workflows/main.yml
vendored
12
.github/workflows/main.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
build_mode: ["", -Drelease-fast=true, -Drelease-safe=true, -Drelease-small=true]
|
||||
build_mode: ["", -Drelease-fast, -Drelease-safe, -Drelease-small]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
@ -27,11 +27,13 @@ jobs:
|
|||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install qemu qemu-system --fix-missing
|
||||
- name: Check formatting
|
||||
run: zig*/zig fmt --check src
|
||||
- name: Build kernel
|
||||
run: zig*/zig build ${{ matrix.build_mode }}
|
||||
- name: Run unit tests
|
||||
run: zig*/zig build test ${{ matrix.build_mode }}
|
||||
- name: Run runtime tests
|
||||
run: zig*/zig build test -Drt-test=true ${{ matrix.build_mode }}
|
||||
- name: Check formatting
|
||||
run: zig*/zig fmt --check src
|
||||
- name: Run runtime test - Initialisation
|
||||
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 }}
|
||||
|
|
23
README.md
23
README.md
|
@ -41,25 +41,38 @@ Launch a gdb-multiarch instance and connect to qemu.
|
|||
zig build debug
|
||||
```
|
||||
|
||||
## Test
|
||||
## Unit testing
|
||||
|
||||
Run the unit tests or runtime tests.
|
||||
Run the unit tests.
|
||||
|
||||
```Shell
|
||||
zig build test
|
||||
```
|
||||
|
||||
## Runtime testing
|
||||
|
||||
Run the runtime tests.
|
||||
|
||||
```Shell
|
||||
zig build rt-test -Dtest-mode=<MODE>
|
||||
```
|
||||
|
||||
Available test modes:
|
||||
|
||||
* `None`: This is the default, this will run the OS normally.
|
||||
* `Initialisation`: Run the OS's initialisation runtime tests to ensure the OS is properly set up.
|
||||
* `Panic`: Run the panic runtime test.
|
||||
|
||||
## Options
|
||||
|
||||
* `-Drt-test=`: Boolean (default `false`).
|
||||
* **build**: Build with runtime testing enabled. Makes the kernel bigger and slower but tests important functionality.
|
||||
* **test**: Run the runtime testing script instead of the unit tests. Checks for the expected log statements and fails if any are missing.
|
||||
* `-D[build-mode]=`: Boolean (default `false`).
|
||||
* **build**: Build a certain build mode (*release-safe*, *release-fast*, *release-small*). Don't set in order to use the *debug* build mode.
|
||||
* **test**: Test a certain build mode (*release-safe*, *release-fast*, *release-small*). Don't set in order to use the *debug* build mode.
|
||||
* `-Darch=`: String (default `x86`). Currently the only supported value is `x86`.
|
||||
* **build**: Build for a certain architecture.
|
||||
* **test**: Test a certain architecture.
|
||||
* `-Ddisable-display`: Boolean (default `false`)
|
||||
* This disables the display output of QEMU.
|
||||
|
||||
## Contribution
|
||||
|
||||
|
|
134
build.zig
134
build.zig
|
@ -1,5 +1,7 @@
|
|||
const std = @import("std");
|
||||
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;
|
||||
|
@ -7,6 +9,8 @@ const Target = std.Target;
|
|||
const CrossTarget = std.zig.CrossTarget;
|
||||
const fs = std.fs;
|
||||
const Mode = builtin.Mode;
|
||||
const TestMode = rt.TestMode;
|
||||
const ArrayList = std.ArrayList;
|
||||
|
||||
const x86_i686 = CrossTarget{
|
||||
.cpu_arch = .i386,
|
||||
|
@ -33,88 +37,100 @@ pub fn build(b: *Builder) !void {
|
|||
const main_src = "src/kernel/kmain.zig";
|
||||
const arch_root = "src/kernel/arch";
|
||||
const constants_path = try fs.path.join(b.allocator, &[_][]const u8{ arch_root, arch, "constants.zig" });
|
||||
|
||||
const build_mode = b.standardReleaseOptions();
|
||||
const rt_test = b.option(bool, "rt-test", "enable/disable runtime testing") orelse false;
|
||||
|
||||
const exec = b.addExecutable("pluto.elf", main_src);
|
||||
exec.addPackagePath("constants", constants_path);
|
||||
exec.setOutputDir(b.cache_root);
|
||||
exec.addBuildOption(bool, "rt_test", rt_test);
|
||||
exec.setBuildMode(build_mode);
|
||||
const linker_script_path = try fs.path.join(b.allocator, &[_][]const u8{ arch_root, arch, "link.ld" });
|
||||
exec.setLinkerScriptPath(linker_script_path);
|
||||
exec.setTarget(target);
|
||||
|
||||
const output_iso = try fs.path.join(b.allocator, &[_][]const u8{ b.exe_dir, "pluto.iso" });
|
||||
const iso_dir_path = try fs.path.join(b.allocator, &[_][]const u8{ b.exe_dir, "iso" });
|
||||
const boot_path = try fs.path.join(b.allocator, &[_][]const u8{ b.exe_dir, "iso", "boot" });
|
||||
const modules_path = try fs.path.join(b.allocator, &[_][]const u8{ b.exe_dir, "iso", "modules" });
|
||||
|
||||
const build_mode = b.standardReleaseOptions();
|
||||
comptime var test_mode_desc: []const u8 = "\n ";
|
||||
inline for (@typeInfo(TestMode).Enum.fields) |field| {
|
||||
const tm = @field(TestMode, field.name);
|
||||
test_mode_desc = test_mode_desc ++ field.name ++ " (" ++ TestMode.getDescription(tm) ++ ")";
|
||||
test_mode_desc = test_mode_desc ++ "\n ";
|
||||
}
|
||||
|
||||
const test_mode = b.option(TestMode, "test-mode", "Run a specific runtime test. This option is for the rt-test step. Available options: " ++ test_mode_desc) orelse .None;
|
||||
const disable_display = b.option(bool, "disable-display", "Disable the qemu window") orelse false;
|
||||
|
||||
const exec = b.addExecutable("pluto.elf", main_src);
|
||||
exec.addPackagePath("constants", constants_path);
|
||||
exec.setOutputDir(b.cache_root);
|
||||
exec.addBuildOption(TestMode, "test_mode", test_mode);
|
||||
exec.setBuildMode(build_mode);
|
||||
exec.setLinkerScriptPath(linker_script_path);
|
||||
exec.setTarget(target);
|
||||
|
||||
const make_iso = switch (target.getCpuArch()) {
|
||||
.i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec.getOutputPath(), output_iso }),
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
make_iso.step.dependOn(&exec.step);
|
||||
|
||||
b.default_step.dependOn(&make_iso.step);
|
||||
|
||||
const test_step = b.step("test", "Run tests");
|
||||
if (rt_test) {
|
||||
const script = b.addSystemCommand(&[_][]const u8{ "python3", "test/rt-test.py", arch, b.zig_exe });
|
||||
test_step.dependOn(&script.step);
|
||||
} else {
|
||||
const mock_path = "\"../../test/mock/kernel/\"";
|
||||
const arch_mock_path = "\"../../../../test/mock/kernel/\"";
|
||||
const unit_tests = b.addTest(main_src);
|
||||
unit_tests.setBuildMode(build_mode);
|
||||
unit_tests.setMainPkgPath(".");
|
||||
unit_tests.addPackagePath("constants", constants_path);
|
||||
unit_tests.addBuildOption(bool, "rt_test", rt_test);
|
||||
unit_tests.addBuildOption([]const u8, "mock_path", mock_path);
|
||||
unit_tests.addBuildOption([]const u8, "arch_mock_path", arch_mock_path);
|
||||
const mock_path = "\"../../test/mock/kernel/\"";
|
||||
const arch_mock_path = "\"../../../../test/mock/kernel/\"";
|
||||
const unit_tests = b.addTest(main_src);
|
||||
unit_tests.setBuildMode(build_mode);
|
||||
unit_tests.setMainPkgPath(".");
|
||||
unit_tests.addPackagePath("constants", constants_path);
|
||||
unit_tests.addBuildOption(TestMode, "test_mode", test_mode);
|
||||
unit_tests.addBuildOption([]const u8, "mock_path", mock_path);
|
||||
unit_tests.addBuildOption([]const u8, "arch_mock_path", arch_mock_path);
|
||||
|
||||
if (builtin.os.tag != .windows) unit_tests.enable_qemu = true;
|
||||
|
||||
unit_tests.setTarget(.{ .cpu_arch = target.cpu_arch });
|
||||
|
||||
test_step.dependOn(&unit_tests.step);
|
||||
if (builtin.os.tag != .windows) {
|
||||
unit_tests.enable_qemu = true;
|
||||
}
|
||||
|
||||
unit_tests.setTarget(.{ .cpu_arch = target.cpu_arch });
|
||||
test_step.dependOn(&unit_tests.step);
|
||||
|
||||
const rt_test_step = b.step("rt-test", "Run runtime tests");
|
||||
const build_mode_str = switch (build_mode) {
|
||||
.Debug => "",
|
||||
.ReleaseSafe => "-Drelease-safe",
|
||||
.ReleaseFast => "-Drelease-fast",
|
||||
.ReleaseSmall => "-Drelease-small",
|
||||
};
|
||||
|
||||
var qemu_args_al = ArrayList([]const u8).init(b.allocator);
|
||||
defer qemu_args_al.deinit();
|
||||
|
||||
switch (target.getCpuArch()) {
|
||||
.i386 => try qemu_args_al.append("qemu-system-i386"),
|
||||
else => unreachable,
|
||||
}
|
||||
try qemu_args_al.append("-serial");
|
||||
try qemu_args_al.append("stdio");
|
||||
switch (target.getCpuArch()) {
|
||||
.i386 => {
|
||||
try qemu_args_al.append("-boot");
|
||||
try qemu_args_al.append("d");
|
||||
try qemu_args_al.append("-cdrom");
|
||||
try qemu_args_al.append(output_iso);
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
if (disable_display) {
|
||||
try qemu_args_al.append("-display");
|
||||
try qemu_args_al.append("none");
|
||||
}
|
||||
|
||||
var qemu_args = qemu_args_al.toOwnedSlice();
|
||||
|
||||
const rt_step = RuntimeStep.create(b, test_mode, qemu_args);
|
||||
rt_step.step.dependOn(&make_iso.step);
|
||||
rt_test_step.dependOn(&rt_step.step);
|
||||
|
||||
const run_step = b.step("run", "Run with qemu");
|
||||
const run_debug_step = b.step("debug-run", "Run with qemu and wait for a gdb connection");
|
||||
|
||||
const qemu_bin = switch (target.getCpuArch()) {
|
||||
.i386 => "qemu-system-i386",
|
||||
else => unreachable,
|
||||
};
|
||||
const qemu_args = &[_][]const u8{
|
||||
qemu_bin,
|
||||
"-serial",
|
||||
"stdio",
|
||||
};
|
||||
const qemu_machine_args = switch (target.getCpuArch()) {
|
||||
.i386 => &[_][]const u8{
|
||||
"-boot",
|
||||
"d",
|
||||
"-cdrom",
|
||||
output_iso,
|
||||
},
|
||||
else => null,
|
||||
};
|
||||
const qemu_cmd = b.addSystemCommand(qemu_args);
|
||||
const qemu_debug_cmd = b.addSystemCommand(qemu_args);
|
||||
qemu_debug_cmd.addArgs(&[_][]const u8{ "-s", "-S" });
|
||||
if (qemu_machine_args) |machine_args| {
|
||||
qemu_cmd.addArgs(machine_args);
|
||||
qemu_debug_cmd.addArgs(machine_args);
|
||||
}
|
||||
|
||||
if (rt_test) {
|
||||
const qemu_rt_test_args = &[_][]const u8{ "-display", "none" };
|
||||
qemu_cmd.addArgs(qemu_rt_test_args);
|
||||
qemu_debug_cmd.addArgs(qemu_rt_test_args);
|
||||
}
|
||||
|
||||
qemu_cmd.step.dependOn(&make_iso.step);
|
||||
qemu_debug_cmd.step.dependOn(&make_iso.step);
|
||||
|
|
|
@ -3,7 +3,7 @@ const expect = std.testing.expect;
|
|||
const expectEqual = std.testing.expectEqual;
|
||||
const builtin = @import("builtin");
|
||||
const is_test = builtin.is_test;
|
||||
|
||||
const panic = @import("../../panic.zig").panic;
|
||||
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");
|
||||
|
@ -438,7 +438,10 @@ pub fn init() void {
|
|||
// Load the TSS
|
||||
arch.ltr(TSS_OFFSET);
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn mock_lgdt(ptr: *const GdtPtr) void {
|
||||
|
@ -671,8 +674,13 @@ test "init" {
|
|||
///
|
||||
fn rt_loadedGDTSuccess() void {
|
||||
const loaded_gdt = arch.sgdt();
|
||||
expect(gdt_ptr.limit == loaded_gdt.limit);
|
||||
expect(gdt_ptr.base == loaded_gdt.base);
|
||||
if (gdt_ptr.limit != loaded_gdt.limit) {
|
||||
panic(@errorReturnTrace(), "FAILURE: GDT not loaded properly: 0x{X} != 0x{X}\n", .{ gdt_ptr.limit, loaded_gdt.limit });
|
||||
}
|
||||
if (gdt_ptr.base != loaded_gdt.base) {
|
||||
panic(@errorReturnTrace(), "FAILURE: GDT not loaded properly: 0x{X} != {X}\n", .{ gdt_ptr.base, loaded_gdt.base });
|
||||
}
|
||||
log.logInfo("GDT: Tested loading GDT\n", .{});
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -680,5 +688,4 @@ fn rt_loadedGDTSuccess() void {
|
|||
///
|
||||
fn runtimeTests() void {
|
||||
rt_loadedGDTSuccess();
|
||||
log.logInfo("GDT: Tested loading GDT\n", .{});
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ const expectEqual = std.testing.expectEqual;
|
|||
const expectError = std.testing.expectError;
|
||||
const builtin = @import("builtin");
|
||||
const is_test = builtin.is_test;
|
||||
|
||||
const panic = @import("../../panic.zig").panic;
|
||||
const build_options = @import("build_options");
|
||||
const mock_path = build_options.arch_mock_path;
|
||||
const gdt = if (is_test) @import(mock_path ++ "gdt_mock.zig") else @import("gdt.zig");
|
||||
|
@ -101,7 +101,7 @@ var idt_ptr: IdtPtr = IdtPtr{
|
|||
.base = 0,
|
||||
};
|
||||
|
||||
/// The IDT entry table of NUMBER_OF_ENTRIES entries. Initially all zero'ed.
|
||||
/// The IDT entry table of NUMBER_OF_ENTRIES entries. Initially all zeroed.
|
||||
var idt_entries: [NUMBER_OF_ENTRIES]IdtEntry = [_]IdtEntry{IdtEntry{
|
||||
.base_low = 0,
|
||||
.selector = 0,
|
||||
|
@ -186,7 +186,10 @@ pub fn init() void {
|
|||
|
||||
arch.lidt(&idt_ptr);
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn testHandler0() callconv(.Naked) void {}
|
||||
|
@ -325,8 +328,12 @@ test "init" {
|
|||
///
|
||||
fn rt_loadedIDTSuccess() void {
|
||||
const loaded_idt = arch.sidt();
|
||||
expect(idt_ptr.limit == loaded_idt.limit);
|
||||
expect(idt_ptr.base == loaded_idt.base);
|
||||
if (idt_ptr.limit != loaded_idt.limit) {
|
||||
panic(@errorReturnTrace(), "FAILURE: IDT not loaded properly: 0x{X} != 0x{X}\n", .{ idt_ptr.limit, loaded_idt.limit });
|
||||
}
|
||||
if (idt_ptr.base != loaded_idt.base) {
|
||||
panic(@errorReturnTrace(), "FAILURE: IDT not loaded properly: 0x{X} != {X}\n", .{ idt_ptr.base, loaded_idt.base });
|
||||
}
|
||||
log.logInfo("IDT: Tested loading IDT\n", .{});
|
||||
}
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ fn openIrq(index: u8, handler: idt.InterruptHandler) void {
|
|||
/// IN irq_num: u8 - The IRQ index to test.
|
||||
///
|
||||
/// Return: bool
|
||||
/// Whether the IRQ index if valid.
|
||||
/// Whether the IRQ index is valid.
|
||||
///
|
||||
pub fn isValidIrq(irq_num: u32) bool {
|
||||
return irq_num < NUMBER_OF_ENTRIES;
|
||||
|
@ -136,7 +136,10 @@ pub fn init() void {
|
|||
openIrq(i, interrupts.getInterruptStub(i));
|
||||
}
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn testFunction0() callconv(.Naked) void {}
|
||||
|
@ -227,13 +230,13 @@ test "registerIrq invalid irq index" {
|
|||
}
|
||||
|
||||
///
|
||||
/// Test that all handers are null at initialisation.
|
||||
/// Test that all handlers are null at initialisation.
|
||||
///
|
||||
fn rt_unregisteredHandlers() void {
|
||||
// Ensure all ISR are not registered yet
|
||||
for (irq_handlers) |h, i| {
|
||||
if (h) |_| {
|
||||
panic(@errorReturnTrace(), "Handler found for IRQ: {}-{}\n", .{ i, h });
|
||||
panic(@errorReturnTrace(), "FAILURE: Handler found for IRQ: {}-{}\n", .{ i, h });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -250,7 +253,7 @@ fn rt_openedIdtEntries() void {
|
|||
for (idt_entries) |entry, i| {
|
||||
if (i >= IRQ_OFFSET and isValidIrq(i - IRQ_OFFSET)) {
|
||||
if (!idt.isIdtOpen(entry)) {
|
||||
panic(@errorReturnTrace(), "IDT entry for {} is not open\n", .{i});
|
||||
panic(@errorReturnTrace(), "FAILURE: IDT entry for {} is not open\n", .{i});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ pub const SECURITY: u8 = 30;
|
|||
/// The of exception handlers initialised to null. Need to open a ISR for these to be valid.
|
||||
var isr_handlers: [NUMBER_OF_ENTRIES]?IsrHandler = [_]?IsrHandler{null} ** NUMBER_OF_ENTRIES;
|
||||
|
||||
/// The syscall hander.
|
||||
/// The syscall handler.
|
||||
var syscall_handler: ?IsrHandler = null;
|
||||
|
||||
///
|
||||
|
@ -188,7 +188,7 @@ fn openIsr(index: u8, handler: idt.InterruptHandler) void {
|
|||
/// IN isr_num: u16 - The isr number to check
|
||||
///
|
||||
/// Return: bool
|
||||
/// Whether a ISR hander index if valid.
|
||||
/// Whether a ISR handler index is valid.
|
||||
///
|
||||
pub fn isValidIsr(isr_num: u32) bool {
|
||||
return isr_num < NUMBER_OF_ENTRIES or isr_num == syscalls.INTERRUPT;
|
||||
|
@ -244,7 +244,10 @@ pub fn init() void {
|
|||
|
||||
openIsr(syscalls.INTERRUPT, interrupts.getInterruptStub(syscalls.INTERRUPT));
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn testFunction0() callconv(.Naked) void {}
|
||||
|
@ -356,18 +359,18 @@ test "registerIsr invalid isr index" {
|
|||
}
|
||||
|
||||
///
|
||||
/// Test that all handers are null at initialisation.
|
||||
/// Test that all handlers are null at initialisation.
|
||||
///
|
||||
fn rt_unregisteredHandlers() void {
|
||||
// Ensure all ISR are not registered yet
|
||||
for (isr_handlers) |h, i| {
|
||||
if (h) |_| {
|
||||
panic(@errorReturnTrace(), "Handler found for ISR: {}-{}\n", .{ i, h });
|
||||
panic(@errorReturnTrace(), "FAILURE: Handler found for ISR: {}-{}\n", .{ i, h });
|
||||
}
|
||||
}
|
||||
|
||||
if (syscall_handler) |h| {
|
||||
panic(@errorReturnTrace(), "Pre-testing failed for syscall: {}\n", .{h});
|
||||
panic(@errorReturnTrace(), "FAILURE: Pre-testing failed for syscall: {}\n", .{h});
|
||||
}
|
||||
|
||||
log.logInfo("ISR: Tested registered handlers\n", .{});
|
||||
|
@ -383,7 +386,7 @@ fn rt_openedIdtEntries() void {
|
|||
for (idt_entries) |entry, i| {
|
||||
if (isValidIsr(i)) {
|
||||
if (!idt.isIdtOpen(entry)) {
|
||||
panic(@errorReturnTrace(), "IDT entry for {} is not open\n", .{i});
|
||||
panic(@errorReturnTrace(), "FAILURE: IDT entry for {} is not open\n", .{i});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ const log = @import("../../log.zig");
|
|||
const mem = @import("../../mem.zig");
|
||||
const vmm = @import("../../vmm.zig");
|
||||
const multiboot = @import("multiboot.zig");
|
||||
const options = @import("build_options");
|
||||
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.
|
||||
|
@ -138,7 +138,7 @@ inline fn virtToTableEntryIdx(virt: usize) usize {
|
|||
///
|
||||
/// Arguments:
|
||||
/// val: *align(1) u32 - The entry to modify
|
||||
/// attr: u32 - The bits corresponding to the atttribute to set
|
||||
/// attr: u32 - The bits corresponding to the attribute to set
|
||||
///
|
||||
inline fn setAttribute(val: *align(1) u32, attr: u32) void {
|
||||
val.* |= attr;
|
||||
|
@ -149,7 +149,7 @@ inline fn setAttribute(val: *align(1) u32, attr: u32) void {
|
|||
///
|
||||
/// Arguments:
|
||||
/// val: *align(1) u32 - The entry to modify
|
||||
/// attr: u32 - The bits corresponding to the atttribute to clear
|
||||
/// attr: u32 - The bits corresponding to the attribute to clear
|
||||
///
|
||||
inline fn clearAttribute(val: *align(1) u32, attr: u32) void {
|
||||
val.* &= ~attr;
|
||||
|
@ -157,7 +157,7 @@ inline fn clearAttribute(val: *align(1) u32, attr: u32) void {
|
|||
|
||||
///
|
||||
/// Map a page directory entry, setting the present, size, writable, write-through and physical address bits.
|
||||
/// Clears the user and cache disabled bits. Entry should be zero'ed.
|
||||
/// Clears the user and cache disabled bits. Entry should be zeroed.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN virt_addr: usize - The start of the virtual space to map
|
||||
|
@ -297,11 +297,11 @@ fn mapTableEntry(entry: *align(1) TableEntry, phys_addr: usize, attrs: vmm.Attri
|
|||
/// Arguments:
|
||||
/// IN virtual_start: usize - The start of the virtual region to map
|
||||
/// IN virtual_end: usize - The end (exclusive) of the virtual region to map
|
||||
/// IN physical_start: usize - The start of the physical region to mape to
|
||||
/// IN physical_start: usize - The start of the physical region to map to
|
||||
/// IN physical_end: usize - The end (exclusive) of the physical region to map to
|
||||
/// IN attrs: vmm.Attributes - The attributes to apply to this mapping
|
||||
/// INOUT allocator: *std.mem.Allocator - The allocator to use to allocate any intermediate data structures required to map this region
|
||||
/// INOUT dir: *Directory - The page directory to map within
|
||||
/// IN/OUT allocator: *std.mem.Allocator - The allocator to use to allocate any intermediate data structures required to map this region
|
||||
/// IN/OUT dir: *Directory - The page directory to map within
|
||||
///
|
||||
/// Error: vmm.MapperError || std.mem.Allocator.Error
|
||||
/// * - See mapDirEntry
|
||||
|
@ -326,7 +326,7 @@ pub fn map(virt_start: usize, virt_end: usize, phys_start: usize, phys_end: usiz
|
|||
/// Arguments:
|
||||
/// IN virtual_start: usize - The start of the virtual region to unmap
|
||||
/// IN virtual_end: usize - The end (exclusive) of the virtual region to unmap
|
||||
/// INOUT dir: *Directory - The page directory to unmap within
|
||||
/// IN/OUT dir: *Directory - The page directory to unmap within
|
||||
///
|
||||
/// Error: std.mem.Allocator.Error || vmm.MapperError
|
||||
/// vmm.MapperError.NotMapped - If the region being unmapped wasn't mapped in the first place
|
||||
|
@ -396,7 +396,7 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile
|
|||
log.logInfo("Init paging\n", .{});
|
||||
defer log.logInfo("Done paging\n", .{});
|
||||
|
||||
isr.registerIsr(isr.PAGE_FAULT, if (options.rt_test) rt_pageFault else pageFault) catch |e| {
|
||||
isr.registerIsr(isr.PAGE_FAULT, if (build_options.test_mode == .Initialisation) rt_pageFault else pageFault) catch |e| {
|
||||
panic(@errorReturnTrace(), "Failed to register page fault ISR: {}\n", .{e});
|
||||
};
|
||||
const dir_physaddr = @ptrToInt(mem.virtToPhys(&kernel_directory));
|
||||
|
@ -405,7 +405,10 @@ pub fn init(mb_info: *multiboot.multiboot_info_t, mem_profile: *const MemProfile
|
|||
: [addr] "{eax}" (dir_physaddr)
|
||||
);
|
||||
const v_end = std.mem.alignForward(@ptrToInt(mem_profile.vaddr_end) + mem.FIXED_ALLOC_SIZE, PAGE_SIZE_4KB);
|
||||
if (options.rt_test) runtimeTests(v_end);
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(v_end),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
fn checkDirEntry(entry: DirectoryEntry, virt_start: usize, virt_end: usize, phys_start: usize, attrs: vmm.Attributes, table: *Table, present: bool) void {
|
||||
|
@ -541,7 +544,7 @@ test "map and unmap" {
|
|||
}
|
||||
|
||||
// The labels to jump to after attempting to cause a page fault. This is needed as we don't want to cause an
|
||||
// infinite loop by jummping to the same instruction that caused the fault.
|
||||
// infinite loop by jumping to the same instruction that caused the fault.
|
||||
extern var rt_fault_callback: *u32;
|
||||
extern var rt_fault_callback2: *u32;
|
||||
|
||||
|
@ -560,26 +563,32 @@ fn rt_accessUnmappedMem(v_end: u32) void {
|
|||
// Accessing unmapped mem causes a page fault
|
||||
var ptr = @intToPtr(*u8, v_end);
|
||||
var value = ptr.*;
|
||||
// Need this as in release builds the above is optimised out so it needs to be use
|
||||
log.logError("FAILURE: Value: {}\n", .{value});
|
||||
// This is the label that we return to after processing the page fault
|
||||
asm volatile (
|
||||
\\.global rt_fault_callback
|
||||
\\rt_fault_callback:
|
||||
);
|
||||
testing.expect(faulted);
|
||||
if (!faulted) {
|
||||
panic(@errorReturnTrace(), "FAILURE: Paging should have faulted\n", .{});
|
||||
}
|
||||
log.logInfo("Paging: Tested accessing unmapped memory\n", .{});
|
||||
}
|
||||
|
||||
fn rt_accessMappedMem(v_end: u32) void {
|
||||
use_callback2 = true;
|
||||
faulted = false;
|
||||
// Accessing mapped memory does't cause a page fault
|
||||
// Accessing mapped memory doesn't cause a page fault
|
||||
var ptr = @intToPtr(*u8, v_end - PAGE_SIZE_4KB);
|
||||
var value = ptr.*;
|
||||
asm volatile (
|
||||
\\.global rt_fault_callback2
|
||||
\\rt_fault_callback2:
|
||||
);
|
||||
testing.expect(!faulted);
|
||||
if (faulted) {
|
||||
panic(@errorReturnTrace(), "FAILURE: Paging shouldn't have faulted\n", .{});
|
||||
}
|
||||
log.logInfo("Paging: Tested accessing mapped memory\n", .{});
|
||||
}
|
||||
|
||||
|
|
|
@ -468,7 +468,10 @@ pub fn init() void {
|
|||
// Clear the IRQ for the slave
|
||||
clearMask(IRQ_CASCADE_FOR_SLAVE);
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
test "sendCommandMaster" {
|
||||
|
@ -814,11 +817,11 @@ test "init" {
|
|||
fn rt_picAllMasked() void {
|
||||
// The master will have interrupt 2 clear because this is the link to the slave (third bit)
|
||||
if (readDataMaster() != 0xFB) {
|
||||
panic(@errorReturnTrace(), "Master masks are not set, found: {}\n", .{readDataMaster()});
|
||||
panic(@errorReturnTrace(), "FAILURE: Master masks are not set, found: {}\n", .{readDataMaster()});
|
||||
}
|
||||
|
||||
if (readDataSlave() != 0xFF) {
|
||||
panic(@errorReturnTrace(), "Slave masks are not set, found: {}\n", .{readDataSlave()});
|
||||
panic(@errorReturnTrace(), "FAILURE: Slave masks are not set, found: {}\n", .{readDataSlave()});
|
||||
}
|
||||
|
||||
log.logInfo("PIC: Tested masking\n", .{});
|
||||
|
|
|
@ -391,7 +391,10 @@ pub fn init() void {
|
|||
},
|
||||
};
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
test "sendCommand" {
|
||||
|
@ -551,7 +554,7 @@ fn rt_waitTicks() void {
|
|||
const difference = getTicks() - waiting;
|
||||
|
||||
if (previous_count + epsilon < difference or previous_count > difference + epsilon) {
|
||||
panic(@errorReturnTrace(), "Waiting failed. difference: {}, previous_count: {}. Epsilon: {}\n", .{ difference, previous_count, epsilon });
|
||||
panic(@errorReturnTrace(), "FAILURE: Waiting failed. difference: {}, previous_count: {}. Epsilon: {}\n", .{ difference, previous_count, epsilon });
|
||||
}
|
||||
|
||||
log.logInfo("PIT: Tested wait ticks\n", .{});
|
||||
|
@ -575,13 +578,13 @@ fn rt_waitTicks2() void {
|
|||
const difference = getTicks() + 15 - waiting;
|
||||
|
||||
if (previous_count + epsilon < difference or previous_count > difference + epsilon) {
|
||||
panic(@errorReturnTrace(), "Waiting failed. difference: {}, previous_count: {}. Epsilon: {}\n", .{ difference, previous_count, epsilon });
|
||||
panic(@errorReturnTrace(), "FAILURE: Waiting failed. difference: {}, previous_count: {}. Epsilon: {}\n", .{ difference, previous_count, epsilon });
|
||||
}
|
||||
|
||||
log.logInfo("PIT: Tested wait ticks 2\n", .{});
|
||||
|
||||
// Reset ticks
|
||||
ticks = 0;
|
||||
|
||||
log.logInfo("PIT: Tested wait ticks 2\n", .{});
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -593,7 +596,7 @@ fn rt_initCounter_0() void {
|
|||
const expected_hz: u32 = 10027;
|
||||
|
||||
if (time_ns != expected_ns or time_under_1_ns != expected_ps or getFrequency() != expected_hz) {
|
||||
panic(@errorReturnTrace(), "Frequency not set properly. Hz: {}!={}, ns: {}!={}, ps: {}!= {}\n", .{
|
||||
panic(@errorReturnTrace(), "FAILURE: Frequency not set properly. Hz: {}!={}, ns: {}!={}, ps: {}!= {}\n", .{
|
||||
getFrequency(),
|
||||
expected_hz,
|
||||
time_ns,
|
||||
|
@ -611,19 +614,19 @@ fn rt_initCounter_0() void {
|
|||
irq_exists = true;
|
||||
},
|
||||
error.InvalidIrq => {
|
||||
panic(@errorReturnTrace(), "IRQ for PIT, IRQ number: {} is invalid", .{pic.IRQ_PIT});
|
||||
panic(@errorReturnTrace(), "FAILURE: IRQ for PIT, IRQ number: {} is invalid", .{pic.IRQ_PIT});
|
||||
},
|
||||
};
|
||||
|
||||
if (!irq_exists) {
|
||||
panic(@errorReturnTrace(), "IRQ for PIT doesn't exists\n", .{});
|
||||
panic(@errorReturnTrace(), "FAILURE: IRQ for PIT doesn't exists\n", .{});
|
||||
}
|
||||
|
||||
const expected_mode = OCW_READ_LOAD_DATA | OCW_MODE_SQUARE_WAVE_GENERATOR | OCW_SELECT_COUNTER_0 | OCW_BINARY_COUNT_BINARY;
|
||||
const actual_mode = readBackCommand(CounterSelect.Counter0);
|
||||
|
||||
if (expected_mode != actual_mode) {
|
||||
panic(@errorReturnTrace(), "Operating mode don't not set properly. Found: {}, expecting: {}\n", .{ actual_mode, expected_mode });
|
||||
panic(@errorReturnTrace(), "FAILURE: Operating mode don't not set properly. Found: {}, expecting: {}\n", .{ actual_mode, expected_mode });
|
||||
}
|
||||
|
||||
log.logInfo("PIT: Tested init\n", .{});
|
||||
|
|
|
@ -49,9 +49,9 @@ var ticks: u32 = 0;
|
|||
/// registers so don't get inconsistent values.
|
||||
///
|
||||
/// Return: bool
|
||||
/// Whether the CMOS chip is buzzy and a update is in progress.
|
||||
/// Whether the CMOS chip is busy and a update is in progress.
|
||||
///
|
||||
fn isBuzzy() bool {
|
||||
fn isBusy() bool {
|
||||
return (cmos.readStatusRegister(cmos.StatusRegister.A, false) & 0x80) != 0;
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ fn bcdToBinary(bcd: u32) u32 {
|
|||
///
|
||||
fn readRtcRegisters() DateTime {
|
||||
// Make sure there isn't a update in progress
|
||||
while (isBuzzy()) {}
|
||||
while (isBusy()) {}
|
||||
|
||||
var date_time = DateTime{
|
||||
.second = cmos.readRtcRegister(cmos.RtcRegister.SECOND),
|
||||
|
@ -283,10 +283,13 @@ pub fn init() void {
|
|||
// Read status register C to clear any interrupts that may have happened during set up
|
||||
const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false);
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
test "isBuzzy not buzzy" {
|
||||
test "isBusy not busy" {
|
||||
cmos.initTest();
|
||||
defer cmos.freeTest();
|
||||
|
||||
|
@ -295,10 +298,10 @@ test "isBuzzy not buzzy" {
|
|||
.{ cmos.StatusRegister.A, false, @as(u8, 0x60) },
|
||||
);
|
||||
|
||||
expect(!isBuzzy());
|
||||
expect(!isBusy());
|
||||
}
|
||||
|
||||
test "isBuzzy buzzy" {
|
||||
test "isBusy busy" {
|
||||
cmos.initTest();
|
||||
defer cmos.freeTest();
|
||||
|
||||
|
@ -307,7 +310,7 @@ test "isBuzzy buzzy" {
|
|||
.{ cmos.StatusRegister.A, false, @as(u8, 0x80) },
|
||||
);
|
||||
|
||||
expect(isBuzzy());
|
||||
expect(isBusy());
|
||||
}
|
||||
|
||||
test "calcDayOfWeek" {
|
||||
|
@ -446,7 +449,7 @@ test "readRtcRegisters" {
|
|||
cmos.initTest();
|
||||
defer cmos.freeTest();
|
||||
|
||||
// Have 2 buzzy loops
|
||||
// Have 2 busy loops
|
||||
cmos.addTestParams(
|
||||
"readStatusRegister",
|
||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x80), cmos.StatusRegister.A, false, @as(u8, 0x80), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
||||
|
@ -481,7 +484,7 @@ test "readRtc unstable read" {
|
|||
cmos.initTest();
|
||||
defer cmos.freeTest();
|
||||
|
||||
// No buzzy loop
|
||||
// No busy loop
|
||||
cmos.addTestParams(
|
||||
"readStatusRegister",
|
||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
||||
|
@ -504,7 +507,7 @@ test "readRtc unstable read" {
|
|||
});
|
||||
|
||||
// Will try again, and now stable
|
||||
// No buzzy loop
|
||||
// No busy loop
|
||||
cmos.addTestParams(
|
||||
"readStatusRegister",
|
||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
||||
|
@ -555,7 +558,7 @@ test "readRtc is BCD" {
|
|||
cmos.initTest();
|
||||
defer cmos.freeTest();
|
||||
|
||||
// No buzzy loop
|
||||
// No busy loop
|
||||
cmos.addTestParams(
|
||||
"readStatusRegister",
|
||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
||||
|
@ -608,7 +611,7 @@ test "readRtc is 12 hours" {
|
|||
cmos.initTest();
|
||||
defer cmos.freeTest();
|
||||
|
||||
// No buzzy loop
|
||||
// No busy loop
|
||||
cmos.addTestParams(
|
||||
"readStatusRegister",
|
||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
||||
|
@ -695,24 +698,24 @@ fn rt_init() void {
|
|||
irq_exists = true;
|
||||
},
|
||||
error.InvalidIrq => {
|
||||
panic(@errorReturnTrace(), "IRQ for RTC, IRQ number: {} is invalid", .{pic.IRQ_REAL_TIME_CLOCK});
|
||||
panic(@errorReturnTrace(), "FAILURE: IRQ for RTC, IRQ number: {} is invalid\n", .{pic.IRQ_REAL_TIME_CLOCK});
|
||||
},
|
||||
};
|
||||
|
||||
if (!irq_exists) {
|
||||
panic(@errorReturnTrace(), "IRQ for RTC doesn't exists\n", .{});
|
||||
panic(@errorReturnTrace(), "FAILURE: IRQ for RTC doesn't exists\n", .{});
|
||||
}
|
||||
|
||||
// Check the rate
|
||||
const status_a = cmos.readStatusRegister(cmos.StatusRegister.A, false);
|
||||
if (status_a & @as(u8, 0x0F) != 7) {
|
||||
panic(@errorReturnTrace(), "Rate not set properly, got: {}\n", .{status_a & @as(u8, 0x0F)});
|
||||
panic(@errorReturnTrace(), "FAILURE: Rate not set properly, got: {}\n", .{status_a & @as(u8, 0x0F)});
|
||||
}
|
||||
|
||||
// Check if interrupts are enabled
|
||||
const status_b = cmos.readStatusRegister(cmos.StatusRegister.B, true);
|
||||
if (status_b & ~@as(u8, 0x40) == 0) {
|
||||
panic(@errorReturnTrace(), "Interrupts not enabled\n", .{});
|
||||
panic(@errorReturnTrace(), "FAILURE: Interrupts not enabled\n", .{});
|
||||
}
|
||||
|
||||
log.logInfo("RTC: Tested init\n", .{});
|
||||
|
@ -727,7 +730,7 @@ fn rt_interrupts() void {
|
|||
pit.waitTicks(100);
|
||||
|
||||
if (prev_ticks == ticks) {
|
||||
panic(@errorReturnTrace(), "No interrupt happened\n", .{});
|
||||
panic(@errorReturnTrace(), "FAILURE: No interrupt happened\n", .{});
|
||||
}
|
||||
|
||||
log.logInfo("RTC: Tested interrupts\n", .{});
|
||||
|
|
|
@ -65,7 +65,7 @@ fn lcrValue(char_len: u8, stop_bit: bool, parity_bit: bool, msb: u1) SerialError
|
|||
}
|
||||
|
||||
///
|
||||
/// The serial controller accepts a divisor rather than a raw badrate, as that is more space efficient.
|
||||
/// The serial controller accepts a divisor rather than a raw baudrate, as that is more space efficient.
|
||||
/// This function computes the divisor for a desired baudrate. Note that multiple baudrates can have the same divisor.
|
||||
///
|
||||
/// Arguments:
|
||||
|
|
|
@ -3,7 +3,8 @@ const testing = @import("std").testing;
|
|||
const assert = @import("std").debug.assert;
|
||||
const isr = @import("isr.zig");
|
||||
const log = @import("../../log.zig");
|
||||
const options = @import("build_options");
|
||||
const build_options = @import("build_options");
|
||||
const panic = @import("../../panic.zig").panic;
|
||||
|
||||
/// The isr number associated with syscalls
|
||||
pub const INTERRUPT: u16 = 0x80;
|
||||
|
@ -241,39 +242,43 @@ pub fn init() void {
|
|||
defer log.logInfo("Done syscalls\n", .{});
|
||||
|
||||
isr.registerIsr(INTERRUPT, handle) catch unreachable;
|
||||
if (options.rt_test) runtimeTests();
|
||||
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests
|
||||
var testInt: u32 = 0;
|
||||
var test_int: u32 = 0;
|
||||
|
||||
fn testHandler0(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||
testInt += 1;
|
||||
test_int += 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
fn testHandler1(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||
testInt += arg1;
|
||||
test_int += arg1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
fn testHandler2(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||
testInt += arg1 + arg2;
|
||||
test_int += arg1 + arg2;
|
||||
return 2;
|
||||
}
|
||||
|
||||
fn testHandler3(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||
testInt += arg1 + arg2 + arg3;
|
||||
test_int += arg1 + arg2 + arg3;
|
||||
return 3;
|
||||
}
|
||||
|
||||
fn testHandler4(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||
testInt += arg1 + arg2 + arg3 + arg4;
|
||||
test_int += arg1 + arg2 + arg3 + arg4;
|
||||
return 4;
|
||||
}
|
||||
|
||||
fn testHandler5(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||
testInt += arg1 + arg2 + arg3 + arg4 + arg5;
|
||||
test_int += arg1 + arg2 + arg3 + arg4 + arg5;
|
||||
return 5;
|
||||
}
|
||||
|
||||
|
@ -286,29 +291,40 @@ test "registerSyscall returns SyscallExists" {
|
|||
}
|
||||
|
||||
fn runtimeTests() void {
|
||||
registerSyscall(123, testHandler0) catch unreachable;
|
||||
registerSyscall(124, testHandler1) catch unreachable;
|
||||
registerSyscall(125, testHandler2) catch unreachable;
|
||||
registerSyscall(126, testHandler3) catch unreachable;
|
||||
registerSyscall(127, testHandler4) catch unreachable;
|
||||
registerSyscall(128, testHandler5) catch unreachable;
|
||||
assert(testInt == 0);
|
||||
registerSyscall(123, testHandler0) catch panic(@errorReturnTrace(), "FAILURE registering handler 0\n", .{});
|
||||
registerSyscall(124, testHandler1) catch panic(@errorReturnTrace(), "FAILURE registering handler 1\n", .{});
|
||||
registerSyscall(125, testHandler2) catch panic(@errorReturnTrace(), "FAILURE registering handler 2\n", .{});
|
||||
registerSyscall(126, testHandler3) catch panic(@errorReturnTrace(), "FAILURE registering handler 3\n", .{});
|
||||
registerSyscall(127, testHandler4) catch panic(@errorReturnTrace(), "FAILURE registering handler 4\n", .{});
|
||||
registerSyscall(128, testHandler5) catch panic(@errorReturnTrace(), "FAILURE registering handler 5\n", .{});
|
||||
|
||||
if (syscall0(123) == 0 and testInt == 1)
|
||||
log.logInfo("Syscalls: Tested no args\n", .{});
|
||||
if (test_int != 0) {
|
||||
panic(@errorReturnTrace(), "FAILURE initial test_int not 0: {}\n", .{test_int});
|
||||
}
|
||||
|
||||
if (syscall1(124, 2) == 1 and testInt == 3)
|
||||
log.logInfo("Syscalls: Tested 1 arg\n", .{});
|
||||
if (syscall0(123) != 0 or test_int != 1) {
|
||||
panic(@errorReturnTrace(), "FAILURE syscall0\n", .{});
|
||||
}
|
||||
|
||||
if (syscall2(125, 2, 3) == 2 and testInt == 8)
|
||||
log.logInfo("Syscalls: Tested 2 args\n", .{});
|
||||
if (syscall1(124, 2) != 1 or test_int != 3) {
|
||||
panic(@errorReturnTrace(), "FAILURE syscall2\n", .{});
|
||||
}
|
||||
|
||||
if (syscall3(126, 2, 3, 4) == 3 and testInt == 17)
|
||||
log.logInfo("Syscalls: Tested 3 args\n", .{});
|
||||
if (syscall2(125, 2, 3) != 2 or test_int != 8) {
|
||||
panic(@errorReturnTrace(), "FAILURE syscall2\n", .{});
|
||||
}
|
||||
|
||||
if (syscall4(127, 2, 3, 4, 5) == 4 and testInt == 31)
|
||||
log.logInfo("Syscalls: Tested 4 args\n", .{});
|
||||
if (syscall3(126, 2, 3, 4) != 3 or test_int != 17) {
|
||||
panic(@errorReturnTrace(), "FAILURE syscall3\n", .{});
|
||||
}
|
||||
|
||||
if (syscall5(128, 2, 3, 4, 5, 6) == 5 and testInt == 51)
|
||||
log.logInfo("Syscalls: Tested 5 args\n", .{});
|
||||
if (syscall4(127, 2, 3, 4, 5) != 4 or test_int != 31) {
|
||||
panic(@errorReturnTrace(), "FAILURE syscall4\n", .{});
|
||||
}
|
||||
|
||||
if (syscall5(128, 2, 3, 4, 5, 6) != 5 or test_int != 51) {
|
||||
panic(@errorReturnTrace(), "FAILURE syscall5\n", .{});
|
||||
}
|
||||
|
||||
log.logInfo("Syscall: Tested all args\n", .{});
|
||||
}
|
||||
|
|
|
@ -548,7 +548,10 @@ pub fn init() void {
|
|||
row = ROW_MIN;
|
||||
}
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
const test_colour: u8 = vga.orig_entryColour(vga.COLOUR_LIGHT_GREY, vga.COLOUR_BLACK);
|
||||
|
|
|
@ -293,7 +293,10 @@ pub fn init() void {
|
|||
// Set by default the underline cursor
|
||||
setCursorShape(CursorShape.UNDERLINE);
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
test "entryColour" {
|
||||
|
@ -531,7 +534,7 @@ fn rt_correctMaxScanLine() void {
|
|||
const max_scan_line = getPortData(REG_MAXIMUM_SCAN_LINE);
|
||||
|
||||
if (max_scan_line != CURSOR_SCANLINE_END) {
|
||||
panic(@errorReturnTrace(), "Max scan line not {}, found {}\n", .{ CURSOR_SCANLINE_END, max_scan_line });
|
||||
panic(@errorReturnTrace(), "FAILURE: Max scan line not {}, found {}\n", .{ CURSOR_SCANLINE_END, max_scan_line });
|
||||
}
|
||||
|
||||
log.logInfo("VGA: Tested max scan line\n", .{});
|
||||
|
@ -543,14 +546,14 @@ fn rt_correctMaxScanLine() void {
|
|||
fn rt_correctCursorShape() void {
|
||||
// Check the global variables are correct
|
||||
if (cursor_scanline_start != CURSOR_SCANLINE_MIDDLE or cursor_scanline_end != CURSOR_SCANLINE_END) {
|
||||
panic(@errorReturnTrace(), "Global cursor scanline incorrect. Start: {}, end: {}\n", .{ cursor_scanline_start, cursor_scanline_end });
|
||||
panic(@errorReturnTrace(), "FAILURE: Global cursor scanline incorrect. Start: {}, end: {}\n", .{ cursor_scanline_start, cursor_scanline_end });
|
||||
}
|
||||
|
||||
const cursor_start = getPortData(REG_CURSOR_START);
|
||||
const cursor_end = getPortData(REG_CURSOR_END);
|
||||
|
||||
if (cursor_start != CURSOR_SCANLINE_MIDDLE or cursor_end != CURSOR_SCANLINE_END) {
|
||||
panic(@errorReturnTrace(), "Cursor scanline are incorrect. Start: {}, end: {}\n", .{ cursor_start, cursor_end });
|
||||
panic(@errorReturnTrace(), "FAILURE: Cursor scanline are incorrect. Start: {}, end: {}\n", .{ cursor_start, cursor_end });
|
||||
}
|
||||
|
||||
log.logInfo("VGA: Tested cursor shape\n", .{});
|
||||
|
@ -579,7 +582,7 @@ fn rt_setCursorGetCursor() void {
|
|||
const actual_y_loc = @truncate(u8, actual_linear_loc / WIDTH);
|
||||
|
||||
if (x != actual_x_loc or y != actual_y_loc) {
|
||||
panic(@errorReturnTrace(), "VGA cursor not the same: a_x: {}, a_y: {}, e_x: {}, e_y: {}\n", .{ x, y, actual_x_loc, actual_y_loc });
|
||||
panic(@errorReturnTrace(), "FAILURE: VGA cursor not the same: a_x: {}, a_y: {}, e_x: {}, e_y: {}\n", .{ x, y, actual_x_loc, actual_y_loc });
|
||||
}
|
||||
|
||||
// Restore the previous x and y
|
||||
|
|
|
@ -67,7 +67,7 @@ pub fn Bitmap(comptime BitmapType: type) type {
|
|||
/// Set an entry within a bitmap as occupied.
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT self: *Self - The bitmap to modify.
|
||||
/// IN/OUT self: *Self - The bitmap to modify.
|
||||
/// IN idx: usize - The index within the bitmap to set.
|
||||
///
|
||||
/// Error: BitmapError.
|
||||
|
@ -86,7 +86,7 @@ pub fn Bitmap(comptime BitmapType: type) type {
|
|||
/// Set an entry within a bitmap as unoccupied.
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT self: *Self - The bitmap to modify.
|
||||
/// IN/OUT self: *Self - The bitmap to modify.
|
||||
/// IN idx: usize - The index within the bitmap to clear.
|
||||
///
|
||||
/// Error: BitmapError.
|
||||
|
@ -106,7 +106,7 @@ pub fn Bitmap(comptime BitmapType: type) type {
|
|||
///
|
||||
/// Arguments:
|
||||
/// IN self: *const Self - The bitmap to use.
|
||||
/// IN idx: usize - The index into all of the bitmap's entries.
|
||||
/// IN idx: usize - The index into all of the bitmaps entries.
|
||||
///
|
||||
/// Return: BitmapType.
|
||||
/// The bit corresponding to that index but within a single BitmapType.
|
||||
|
@ -119,7 +119,7 @@ pub fn Bitmap(comptime BitmapType: type) type {
|
|||
/// Find a number of contiguous free entries and set them.
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT self: *Self - The bitmap to modify.
|
||||
/// IN/OUT self: *Self - The bitmap to modify.
|
||||
/// IN num: usize - The number of entries to set.
|
||||
///
|
||||
/// Return: ?usize
|
||||
|
|
|
@ -158,7 +158,7 @@ const Heap = struct {
|
|||
/// Attempt to allocate a portion of memory within a heap. It is recommended to not call this directly and instead use the Allocator interface.
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT self: *Heap - The heap to allocate within
|
||||
/// IN/OUT self: *Heap - The heap to allocate within
|
||||
/// IN size: usize - The size of the allocation
|
||||
/// IN alignment: ?u29 - The alignment that the returned address should have, else null if no alignment is required
|
||||
///
|
||||
|
@ -187,7 +187,7 @@ const Heap = struct {
|
|||
/// Free previously allocated memory. It is recommended to not call this directly and instead use the Allocator interface.
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT self: *Heap - The heap to free within
|
||||
/// IN/OUT self: *Heap - The heap to free within
|
||||
/// IN ptr: usize - The address of the allocation to free. Should have been returned from a prior call to alloc.
|
||||
/// IN len: usize - The size of the allocated region.
|
||||
///
|
||||
|
|
|
@ -81,6 +81,12 @@ export fn kmain(boot_payload: arch.BootPayload) void {
|
|||
|
||||
tty.print("Hello Pluto from kernel :)\n", .{});
|
||||
|
||||
// The panic runtime tests must run last as they never return
|
||||
if (build_options.rt_test) panic_root.runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => {
|
||||
log.logInfo("SUCCESS\n", .{});
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
arch.spinWait();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ const LoggingError = error{};
|
|||
/// The OutStream for the format function
|
||||
const OutStream = std.io.OutStream(void, LoggingError, logCallback);
|
||||
|
||||
/// The different levels of logging that can be outputted.
|
||||
pub const Level = enum {
|
||||
INFO,
|
||||
DEBUG,
|
||||
|
@ -18,6 +19,20 @@ pub const Level = enum {
|
|||
|
||||
var serial: Serial = undefined;
|
||||
|
||||
///
|
||||
/// The call back function for the std library format function.
|
||||
///
|
||||
/// Arguments:
|
||||
/// context: void - The context of the printing. There isn't a need for a context for this
|
||||
/// so is void.
|
||||
/// str: []const u8 - The string to print to the serial terminal.
|
||||
///
|
||||
/// Return: usize
|
||||
/// The number of bytes written. This will always be the length of the string to print.
|
||||
///
|
||||
/// Error: LoggingError
|
||||
/// {} - No error as LoggingError is empty.
|
||||
///
|
||||
fn logCallback(context: void, str: []const u8) LoggingError!usize {
|
||||
serial.writeBytes(str);
|
||||
return str.len;
|
||||
|
@ -27,8 +42,10 @@ fn logCallback(context: void, str: []const u8) LoggingError!usize {
|
|||
/// Write a message to the log output stream with a certain logging level.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN comptime level: Level - The logging level to use. Determines the message prefix and whether it is filtered.
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification options.
|
||||
/// IN comptime level: Level - The logging level to use. Determines the message prefix and
|
||||
/// whether it is filtered.
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification
|
||||
/// options.
|
||||
/// IN args: var - A struct of the parameters for the format string.
|
||||
///
|
||||
pub fn log(comptime level: Level, comptime format: []const u8, args: var) void {
|
||||
|
@ -39,7 +56,8 @@ pub fn log(comptime level: Level, comptime format: []const u8, args: var) void {
|
|||
/// Write a message to the log output stream with the INFO level.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification options.
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification
|
||||
/// options.
|
||||
/// IN args: var - A struct of the parameters for the format string.
|
||||
///
|
||||
pub fn logInfo(comptime format: []const u8, args: var) void {
|
||||
|
@ -50,7 +68,8 @@ pub fn logInfo(comptime format: []const u8, args: var) void {
|
|||
/// Write a message to the log output stream with the DEBUG level.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification options.
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification
|
||||
/// options.
|
||||
/// IN args: var - A struct of the parameters for the format string.
|
||||
///
|
||||
pub fn logDebug(comptime format: []const u8, args: var) void {
|
||||
|
@ -61,7 +80,8 @@ pub fn logDebug(comptime format: []const u8, args: var) void {
|
|||
/// Write a message to the log output stream with the WARNING level.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification options.
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification
|
||||
/// options.
|
||||
/// IN args: var - A struct of the parameters for the format string.
|
||||
///
|
||||
pub fn logWarning(comptime format: []const u8, args: var) void {
|
||||
|
@ -72,7 +92,8 @@ pub fn logWarning(comptime format: []const u8, args: var) void {
|
|||
/// Write a message to the log output stream with the ERROR level.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification options.
|
||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification
|
||||
/// options.
|
||||
/// IN args: var - A struct of the parameters for the format string.
|
||||
///
|
||||
pub fn logError(comptime format: []const u8, args: var) void {
|
||||
|
@ -88,9 +109,15 @@ pub fn logError(comptime format: []const u8, args: var) void {
|
|||
pub fn init(ser: Serial) void {
|
||||
serial = ser;
|
||||
|
||||
if (build_options.rt_test) runtimeTests();
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// The logging runtime tests that will test all logging levels.
|
||||
///
|
||||
pub fn runtimeTests() void {
|
||||
inline for (@typeInfo(Level).Enum.fields) |field| {
|
||||
const level = @field(Level, field.name);
|
||||
|
|
|
@ -46,7 +46,7 @@ pub const MemProfile = struct {
|
|||
/// The virtual regions of reserved memory. Should not include what is tracked by the vaddr_* fields but should include the regions occupied by the modules. These are reserved and mapped by the VMM
|
||||
virtual_reserved: []Map,
|
||||
|
||||
/// The phsyical regions of reserved memory. Should not include what is tracked by the physaddr_* fields but should include the regions occupied by the modules. These are reserved by the PMM
|
||||
/// The physical regions of reserved memory. Should not include what is tracked by the physaddr_* fields but should include the regions occupied by the modules. These are reserved by the PMM
|
||||
physical_reserved: []Range,
|
||||
|
||||
/// The allocator to use before a heap can be set up.
|
||||
|
|
|
@ -5,6 +5,7 @@ const arch = @import("arch.zig").internals;
|
|||
const log = @import("log.zig");
|
||||
const multiboot = @import("multiboot.zig");
|
||||
const mem = @import("mem.zig");
|
||||
const build_options = @import("build_options");
|
||||
const ArrayList = std.ArrayList;
|
||||
const testing = std.testing;
|
||||
|
||||
|
@ -15,7 +16,7 @@ const PanicError = error{
|
|||
InvalidSymbolFile,
|
||||
};
|
||||
|
||||
/// An entry within a symbol map. Corresponds to one entry in a symbole file
|
||||
/// An entry within a symbol map. Corresponds to one entry in a symbol file
|
||||
const MapEntry = struct {
|
||||
/// The address that the entry corresponds to
|
||||
addr: usize,
|
||||
|
@ -59,7 +60,7 @@ const SymbolMap = struct {
|
|||
/// Error: std.mem.Allocator.Error
|
||||
/// * - See ArrayList.append
|
||||
///
|
||||
pub fn add(self: *SymbolMap, name: []const u8, addr: u32) !void {
|
||||
pub fn add(self: *SymbolMap, name: []const u8, addr: usize) !void {
|
||||
try self.addEntry(MapEntry{ .addr = addr, .func_name = name });
|
||||
}
|
||||
|
||||
|
@ -130,7 +131,7 @@ pub fn panic(trace: ?*builtin.StackTrace, comptime format: []const u8, args: var
|
|||
/// whitespace character.
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT ptr: *[*]const u8 - The address at which to start looking, updated after all
|
||||
/// IN/OUT ptr: *[*]const u8 - The address at which to start looking, updated after all
|
||||
/// characters have been consumed.
|
||||
/// IN end: *const u8 - The end address at which to start looking. A whitespace character must
|
||||
/// be found before this.
|
||||
|
@ -166,7 +167,9 @@ fn parseAddr(ptr: *[*]const u8, end: *const u8) !usize {
|
|||
/// PanicError.InvalidSymbolFile: The address given is greater than or equal to the end address.
|
||||
///
|
||||
fn parseChar(ptr: [*]const u8, end: *const u8) PanicError!u8 {
|
||||
if (@ptrToInt(ptr) >= @ptrToInt(end)) return PanicError.InvalidSymbolFile;
|
||||
if (@ptrToInt(ptr) >= @ptrToInt(end)) {
|
||||
return PanicError.InvalidSymbolFile;
|
||||
}
|
||||
return ptr[0];
|
||||
}
|
||||
|
||||
|
@ -219,7 +222,7 @@ fn parseNonWhitespace(ptr: [*]const u8, end: *const u8) PanicError![*]const u8 {
|
|||
/// character.
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT ptr: *[*]const u8 - The address at which to start looking, updated after all
|
||||
/// IN/OUT ptr: *[*]const u8 - The address at which to start looking, updated after all
|
||||
/// characters have been consumed.
|
||||
/// IN end: *const u8 - The end address at which to start looking. A whitespace character must
|
||||
/// be found before this.
|
||||
|
@ -242,7 +245,7 @@ fn parseName(ptr: *[*]const u8, end: *const u8) PanicError![]const u8 {
|
|||
/// in the format of '\d+\w+[a-zA-Z0-9]+'. Must be terminated by a whitespace character.
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT ptr: *[*]const u8 - The address at which to start looking, updated once after the
|
||||
/// IN/OUT ptr: *[*]const u8 - The address at which to start looking, updated once after the
|
||||
/// address has been consumed and once again after the name has been consumed.
|
||||
/// IN end: *const u8 - The end address at which to start looking. A whitespace character must
|
||||
/// be found before this.
|
||||
|
@ -281,8 +284,9 @@ pub fn init(mem_profile: *const mem.MemProfile, allocator: *std.mem.Allocator) !
|
|||
defer log.logInfo("Done panic\n", .{});
|
||||
|
||||
// Exit if we haven't loaded all debug modules
|
||||
if (mem_profile.modules.len < 1)
|
||||
if (mem_profile.modules.len < 1) {
|
||||
return;
|
||||
}
|
||||
var kmap_start: usize = 0;
|
||||
var kmap_end: usize = 0;
|
||||
for (mem_profile.modules) |module| {
|
||||
|
@ -296,8 +300,9 @@ pub fn init(mem_profile: *const mem.MemProfile, allocator: *std.mem.Allocator) !
|
|||
}
|
||||
// Don't try to load the symbols if there was no symbol map file. This is a valid state so just
|
||||
// exit early
|
||||
if (kmap_start == 0 or kmap_end == 0)
|
||||
if (kmap_start == 0 or kmap_end == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var syms = SymbolMap.init(allocator);
|
||||
errdefer syms.deinit();
|
||||
|
@ -307,6 +312,11 @@ pub fn init(mem_profile: *const mem.MemProfile, allocator: *std.mem.Allocator) !
|
|||
try syms.addEntry(entry);
|
||||
}
|
||||
symbol_map = syms;
|
||||
|
||||
switch (build_options.test_mode) {
|
||||
.Panic => runtimeTests(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
test "parseChar" {
|
||||
|
@ -441,7 +451,13 @@ test "SymbolMap" {
|
|||
testing.expectEqual(map.search(2345), "jkl");
|
||||
}
|
||||
|
||||
///
|
||||
/// Runtime test for panic. This will trigger a integer overflow.
|
||||
///
|
||||
pub fn runtimeTests() void {
|
||||
@setRuntimeSafety(true);
|
||||
var x: u8 = 255;
|
||||
x += 1;
|
||||
// If we get here, then a panic was not triggered so fail
|
||||
panic(@errorReturnTrace(), "FAILURE: No integer overflow\n", .{});
|
||||
}
|
||||
|
|
|
@ -120,43 +120,12 @@ pub fn init(mem: *const MemProfile, allocator: *std.mem.Allocator) void {
|
|||
}
|
||||
}
|
||||
|
||||
if (build_options.rt_test) {
|
||||
runtimeTests(mem, allocator);
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(mem, allocator),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Allocate all blocks and make sure they don't overlap with any reserved addresses.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN mem: *const MemProfile - The memory profile to check for reserved memory regions.
|
||||
/// INOUT allocator: *std.mem.Allocator - The allocator to use when needing to create intermediate structures used for testing
|
||||
///
|
||||
fn runtimeTests(mem: *const MemProfile, allocator: *std.mem.Allocator) void {
|
||||
// Make sure that occupied memory can't be allocated
|
||||
var prev_alloc: usize = std.math.maxInt(usize);
|
||||
var alloc_list = std.ArrayList(usize).init(allocator);
|
||||
defer alloc_list.deinit();
|
||||
while (alloc()) |alloced| {
|
||||
if (prev_alloc == alloced) {
|
||||
panic(null, "PMM allocated the same address twice: 0x{x}", .{alloced});
|
||||
}
|
||||
prev_alloc = alloced;
|
||||
for (mem.physical_reserved) |entry| {
|
||||
var addr = std.mem.alignBackward(entry.start, BLOCK_SIZE);
|
||||
if (addr == alloced) {
|
||||
panic(null, "PMM allocated an address that should be reserved by the memory map: 0x{x}", .{addr});
|
||||
}
|
||||
}
|
||||
alloc_list.append(alloced) catch |e| panic(@errorReturnTrace(), "Failed to add PMM allocation to list: {}", .{e});
|
||||
}
|
||||
// Clean up
|
||||
for (alloc_list.items) |alloced| {
|
||||
free(alloced) catch |e| panic(@errorReturnTrace(), "Failed freeing allocation in PMM rt test: {}", .{e});
|
||||
}
|
||||
log.logInfo("PMM: Tested allocation\n", .{});
|
||||
}
|
||||
|
||||
test "alloc" {
|
||||
bitmap = try Bitmap(u32).init(32, std.heap.page_allocator);
|
||||
comptime var addr = 0;
|
||||
|
@ -227,3 +196,35 @@ test "setAddr and isSet" {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Allocate all blocks and make sure they don't overlap with any reserved addresses.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN mem: *const MemProfile - The memory profile to check for reserved memory regions.
|
||||
/// IN/OUT allocator: *std.mem.Allocator - The allocator to use when needing to create intermediate structures used for testing
|
||||
///
|
||||
fn runtimeTests(mem: *const MemProfile, allocator: *std.mem.Allocator) void {
|
||||
// Make sure that occupied memory can't be allocated
|
||||
var prev_alloc: usize = std.math.maxInt(usize);
|
||||
var alloc_list = std.ArrayList(usize).init(allocator);
|
||||
defer alloc_list.deinit();
|
||||
while (alloc()) |alloced| {
|
||||
if (prev_alloc == alloced) {
|
||||
panic(null, "FAILURE: PMM allocated the same address twice: 0x{x}", .{alloced});
|
||||
}
|
||||
prev_alloc = alloced;
|
||||
for (mem.physical_reserved) |entry| {
|
||||
var addr = std.mem.alignBackward(@intCast(usize, entry.start), BLOCK_SIZE);
|
||||
if (addr == alloced) {
|
||||
panic(null, "FAILURE: PMM allocated an address that should be reserved by the memory map: 0x{x}", .{addr});
|
||||
}
|
||||
}
|
||||
alloc_list.append(alloced) catch |e| panic(@errorReturnTrace(), "FAILURE: Failed to add PMM allocation to list: {}", .{e});
|
||||
}
|
||||
// Clean up
|
||||
for (alloc_list.items) |alloced| {
|
||||
free(alloced) catch |e| panic(@errorReturnTrace(), "FAILURE: Failed freeing allocation in PMM rt test: {}", .{e});
|
||||
}
|
||||
log.logInfo("PMM: Tested allocation\n", .{});
|
||||
}
|
||||
|
|
|
@ -31,7 +31,10 @@ pub const Serial = struct {
|
|||
///
|
||||
pub fn init(boot_payload: arch.BootPayload) Serial {
|
||||
const serial = arch.initSerial(boot_payload);
|
||||
if (build_options.rt_test) runtimeTests(serial);
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(serial),
|
||||
else => {},
|
||||
}
|
||||
return serial;
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ pub fn Mapper(comptime Payload: type) type {
|
|||
/// IN physical_start: usize - The start of the physical memory to map to
|
||||
/// IN physical_end: usize - The end of the physical memory to map to
|
||||
/// IN attrs: Attributes - The attributes to apply to this region of memory
|
||||
/// INOUT allocator: std.mem.Allocator - The allocator to use when mapping, if required
|
||||
/// IN/OUT allocator: std.mem.Allocator - The allocator to use when mapping, if required
|
||||
/// IN spec: Payload - The payload to pass to the mapper
|
||||
///
|
||||
/// Error: std.mem.AllocatorError || MapperError
|
||||
|
@ -73,7 +73,7 @@ pub fn Mapper(comptime Payload: type) type {
|
|||
mapFn: fn (virtual_start: usize, virtual_end: usize, physical_start: usize, physical_end: usize, attrs: Attributes, allocator: *std.mem.Allocator, spec: Payload) (std.mem.Allocator.Error || MapperError)!void,
|
||||
|
||||
///
|
||||
/// Unmap a region (can span more than one block) of virtual memory from its physical memory. After a call to this function, the memory should not be accesible without error.
|
||||
/// Unmap a region (can span more than one block) of virtual memory from its physical memory. After a call to this function, the memory should not be accessible without error.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN virtual_start: usize - The start of the virtual region to unmap
|
||||
|
@ -148,7 +148,7 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
|||
/// Arguments:
|
||||
/// IN start: usize - The start of the memory region to manage
|
||||
/// IN end: usize - The end of the memory region to manage. Must be greater than the start
|
||||
/// INOUT allocator: *std.mem.Allocator - The allocator to use when allocating and freeing regions
|
||||
/// IN/OUT allocator: *std.mem.Allocator - The allocator to use when allocating and freeing regions
|
||||
/// IN mapper: Mapper - The mapper to use when allocating and freeing regions
|
||||
/// IN payload: Payload - The payload data to be passed to the mapper
|
||||
///
|
||||
|
@ -193,13 +193,13 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
|||
/// Map a region (can span more than one block) of virtual memory to a specific region of memory
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT self: *Self - The manager to modify
|
||||
/// IN/OUT self: *Self - The manager to modify
|
||||
/// IN virtual: mem.Range - The virtual region to set
|
||||
/// IN physical: ?mem.Range - The physical region to map to or null if only the virtual region is to be set
|
||||
/// IN attrs: Attributes - The attributes to apply to the memory regions
|
||||
///
|
||||
/// Error: VmmError || Bitmap(u32).BitmapError || std.mem.Allocator.Error || MapperError
|
||||
/// VmmError.AlreadyAllocated - The virtual address has arlready been allocated
|
||||
/// VmmError.AlreadyAllocated - The virtual address has already been allocated
|
||||
/// VmmError.PhysicalAlreadyAllocated - The physical address has already been allocated
|
||||
/// VmmError.PhysicalVirtualMismatch - The physical region and virtual region are of different sizes
|
||||
/// VmmError.InvalidVirtAddresses - The start virtual address is greater than the end address
|
||||
|
@ -256,7 +256,7 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
|||
/// Allocate a number of contiguous blocks of virtual memory
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT self: *Self - The manager to allocate for
|
||||
/// IN/OUT self: *Self - The manager to allocate for
|
||||
/// IN num: usize - The number of blocks to allocate
|
||||
/// IN attrs: Attributes - The attributes to apply to the mapped memory
|
||||
///
|
||||
|
@ -298,7 +298,7 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
|||
/// Free a previous allocation
|
||||
///
|
||||
/// Arguments:
|
||||
/// INOUT self: *Self - The manager to free within
|
||||
/// IN/OUT self: *Self - The manager to free within
|
||||
/// IN vaddr: usize - The start of the allocation to free. This should be the address returned from a prior `alloc` call
|
||||
///
|
||||
/// Error: Bitmap.BitmapError || VmmError
|
||||
|
@ -316,12 +316,12 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
|||
for (physical.items) |block, i| {
|
||||
// Clear the address space entry, unmap the virtual memory and free the physical memory
|
||||
try self.bmp.clearEntry(entry + i);
|
||||
pmm.free(block) catch |e| panic(@errorReturnTrace(), "Failed to free PMM reserved memory at {x}: {}\n", .{ block * BLOCK_SIZE, e });
|
||||
pmm.free(block) catch |e| panic(@errorReturnTrace(), "Failed to free PMM reserved memory at 0x{X}: {}\n", .{ block * BLOCK_SIZE, e });
|
||||
}
|
||||
// Unmap the entire range
|
||||
const region_start = entry * BLOCK_SIZE;
|
||||
const region_end = (entry + num_physical_allocations) * BLOCK_SIZE;
|
||||
self.mapper.unmapFn(region_start, region_end, self.payload) catch |e| panic(@errorReturnTrace(), "Failed to unmap VMM reserved memory from {x} to {x}: {}\n", .{ region_start, region_end, e });
|
||||
self.mapper.unmapFn(region_start, region_end, self.payload) catch |e| panic(@errorReturnTrace(), "Failed to unmap VMM reserved memory from 0x{X} to 0x{X}: {}\n", .{ region_start, region_end, e });
|
||||
// The allocation is freed so remove from the map
|
||||
self.allocations.removeAssertDiscard(vaddr);
|
||||
} else {
|
||||
|
@ -336,7 +336,7 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
|||
///
|
||||
/// Arguments:
|
||||
/// IN mem_profile: *const mem.MemProfile - The system's memory profile. This is used to find the kernel code region and boot modules
|
||||
/// INOUT allocator: *std.mem.Allocator - The allocator to use when needing to allocate memory
|
||||
/// IN/OUT allocator: *std.mem.Allocator - The allocator to use when needing to allocate memory
|
||||
///
|
||||
/// Return: VirtualMemoryManager
|
||||
/// The virtual memory manager created with all reserved virtual regions allocated
|
||||
|
@ -367,7 +367,10 @@ pub fn init(mem_profile: *const mem.MemProfile, allocator: *std.mem.Allocator) s
|
|||
};
|
||||
}
|
||||
|
||||
if (build_options.rt_test) runtimeTests(arch.VmmPayload, vmm, mem_profile);
|
||||
switch (build_options.test_mode) {
|
||||
.Initialisation => runtimeTests(arch.VmmPayload, vmm, mem_profile),
|
||||
else => {},
|
||||
}
|
||||
return vmm;
|
||||
}
|
||||
|
||||
|
@ -391,7 +394,7 @@ test "alloc and free" {
|
|||
std.testing.expectEqual(@as(?usize, null), result);
|
||||
should_be_set = false;
|
||||
} else {
|
||||
// Else it should have succedded and allocated the correct address
|
||||
// Else it should have succeeded and allocated the correct address
|
||||
std.testing.expectEqual(@as(?usize, vmm.start + entry * BLOCK_SIZE), result);
|
||||
try virtual_allocations.append(result orelse unreachable);
|
||||
}
|
||||
|
@ -528,7 +531,7 @@ fn testInit(num_entries: u32) std.mem.Allocator.Error!VirtualMemoryManager(u8) {
|
|||
/// IN pstart: usize - The start of the physical region to map
|
||||
/// IN pend: usize - The end of the physical region to map
|
||||
/// IN attrs: Attributes - The attributes to map with
|
||||
/// INOUT allocator: *std.mem.Allocator - The allocator to use. Ignored
|
||||
/// IN/OUT allocator: *std.mem.Allocator - The allocator to use. Ignored
|
||||
/// IN payload: u8 - The payload value. Expected to be 39
|
||||
///
|
||||
fn testMap(vstart: usize, vend: usize, pstart: usize, pend: usize, attrs: Attributes, allocator: *std.mem.Allocator, payload: u8) (std.mem.Allocator.Error || MapperError)!void {
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
def get_test_cases(TestCase):
|
||||
return [
|
||||
TestCase("GDT init", [r"Init gdt"]),
|
||||
TestCase("GDT tests", [r"GDT: Tested loading GDT"]),
|
||||
TestCase("GDT done", [r"Done gdt"]),
|
||||
|
||||
TestCase("IDT init", [r"Init idt"]),
|
||||
TestCase("IDT tests", [r"IDT: Tested loading IDT"]),
|
||||
TestCase("IDT done", [r"Done idt"]),
|
||||
|
||||
TestCase("PIC init", [r"Init pic"]),
|
||||
TestCase("PIC tests", [r"PIC: Tested masking"]),
|
||||
TestCase("PIC done", [r"Done pic"]),
|
||||
|
||||
TestCase("ISR init", [r"Init isr"]),
|
||||
TestCase("ISR tests", [r"ISR: Tested registered handlers", r"ISR: Tested opened IDT entries"]),
|
||||
TestCase("ISR done", [r"Done isr"]),
|
||||
|
||||
TestCase("IRQ init", [r"Init irq"]),
|
||||
TestCase("IRQ tests", [r"IRQ: Tested registered handlers", r"IRQ: Tested opened IDT entries"]),
|
||||
TestCase("IRQ done", [r"Done irq"]),
|
||||
|
||||
TestCase("Paging init", [r"Init paging"]),
|
||||
TestCase("Paging tests", [r"Paging: Tested accessing unmapped memory", r"Paging: Tested accessing mapped memory"]),
|
||||
TestCase("Paging done", [r"Done paging"]),
|
||||
|
||||
TestCase("PIT init", [r"Init pit"]),
|
||||
TestCase("PIT init", [r".+"], r"\[DEBUG\] "),
|
||||
TestCase("PIT tests", [r"PIT: Tested init", r"PIT: Tested wait ticks", r"PIT: Tested wait ticks 2"]),
|
||||
TestCase("PIT done", [r"Done pit"]),
|
||||
|
||||
TestCase("RTC init", [r"Init rtc"]),
|
||||
TestCase("RTC tests", [r"RTC: Tested init", r"RTC: Tested interrupts"]),
|
||||
TestCase("RTC done", [r"Done rtc"]),
|
||||
|
||||
TestCase("Syscalls init", [r"Init syscalls"]),
|
||||
TestCase("Syscalls tests", [r"Syscalls: Tested no args", r"Syscalls: Tested 1 arg", r"Syscalls: Tested 2 args", r"Syscalls: Tested 3 args", r"Syscalls: Tested 4 args", r"Syscalls: Tested 5 args"]),
|
||||
TestCase("Syscalls done", [r"Done syscalls"]),
|
||||
TestCase("VGA init", [r"Init vga"]),
|
||||
TestCase("VGA tests", [r"VGA: Tested max scan line", r"VGA: Tested cursor shape", r"VGA: Tested updating cursor"]),
|
||||
TestCase("VGA done", [r"Done vga"]),
|
||||
TestCase("TTY tests", [r"TTY: Tested globals", r"TTY: Tested printing"]),
|
||||
|
||||
]
|
|
@ -639,7 +639,7 @@ pub fn freeTest() void {
|
|||
/// IN/OUT self: *Self - Self. This is the mocking object to be modified to add
|
||||
/// the test parameters.
|
||||
/// IN fun_name: []const u8 - The function name to add the test parameters to.
|
||||
/// IN params: arglist - The parameters to add.
|
||||
/// IN params: var - The parameters to add.
|
||||
///
|
||||
pub fn addTestParams(comptime fun_name: []const u8, params: var) void {
|
||||
var mock_obj = getMockObject();
|
||||
|
|
118
test/rt-test.py
118
test/rt-test.py
|
@ -1,118 +0,0 @@
|
|||
import atexit
|
||||
import queue
|
||||
import threading
|
||||
import subprocess
|
||||
import signal
|
||||
import re
|
||||
import sys
|
||||
import datetime
|
||||
import os
|
||||
import importlib.util
|
||||
|
||||
msg_queue = queue.Queue(-1)
|
||||
proc = None
|
||||
|
||||
class TestCase:
|
||||
def __init__(self, name, expected, prefix=r"\[INFO\] "):
|
||||
self.name = name
|
||||
self.expected = expected
|
||||
self.prefix = prefix
|
||||
|
||||
def failure(msg):
|
||||
print("FAILURE: %s" %(msg))
|
||||
sys.exit(1)
|
||||
|
||||
def test_failure(case, exp, expected_idx, found):
|
||||
failure("%s #%d, expected '%s', found '%s'" %(case.name, expected_idx + 1, exp, found))
|
||||
|
||||
def test_pass(case, exp, expected_idx, found):
|
||||
print("PASS: %s #%d, expected '%s', found '%s'" %(case.name, expected_idx + 1, exp, found))
|
||||
|
||||
def get_pre_archinit_cases():
|
||||
return [
|
||||
TestCase("Serial tests", [r"c", r"123"], ""),
|
||||
|
||||
TestCase("Log info tests", [r"Test INFO level", r"Test INFO level with args a, 1", r"Test INFO function", r"Test INFO function with args a, 1"], r"\[INFO\] "),
|
||||
TestCase("Log debug tests", [r"Test DEBUG level", r"Test DEBUG level with args a, 1", r"Test DEBUG function", r"Test DEBUG function with args a, 1"], r"\[DEBUG\] "),
|
||||
TestCase("Log warning tests", [r"Test WARNING level", r"Test WARNING level with args a, 1", r"Test WARNING function", r"Test WARNING function with args a, 1"], r"\[WARNING\] "),
|
||||
TestCase("Log error tests", [r"Test ERROR level", r"Test ERROR level with args a, 1", r"Test ERROR function", r"Test ERROR function with args a, 1"], r"\[ERROR\] "),
|
||||
|
||||
TestCase("Mem init", [r"Init mem"]),
|
||||
TestCase("Mem done", [r"Done mem"]),
|
||||
|
||||
TestCase("Panic init", [r"Init panic"]),
|
||||
TestCase("Panic done", [r"Done panic"]),
|
||||
|
||||
TestCase("PMM init", [r"Init pmm"]),
|
||||
TestCase("PMM tests", [r"PMM: Tested allocation"]),
|
||||
TestCase("PMM done", [r"Done pmm"]),
|
||||
|
||||
TestCase("VMM init", [r"Init vmm"]),
|
||||
TestCase("VMM tests", [r"VMM: Tested allocations"]),
|
||||
TestCase("VMM done", [r"Done vmm"]),
|
||||
TestCase("Arch init starts", [r"Init arch \w+"])
|
||||
]
|
||||
|
||||
def get_post_archinit_cases():
|
||||
return [
|
||||
TestCase("Arch init finishes", [r"Arch init done"]),
|
||||
|
||||
TestCase("Heap", [r"Init heap", r"Done heap"]),
|
||||
|
||||
TestCase("TTY init", [r"Init tty"]),
|
||||
TestCase("TTY done", [r"Done tty"]),
|
||||
|
||||
TestCase("Init finishes", [r"Init done"]),
|
||||
|
||||
TestCase("Panic tests", [r"Kernel panic: integer overflow", r"c[a-z\d]+: panic", r"c[a-z\d]+: panic.runtimeTests", r"c[a-z\d]+: kmain", r"c[a-z\d]+: start_higher_half"], r"\[ERROR\] ")
|
||||
]
|
||||
|
||||
def read_messages(proc):
|
||||
while True:
|
||||
line = proc.stdout.readline().decode("utf-8")
|
||||
msg_queue.put(line)
|
||||
|
||||
def cleanup():
|
||||
global proc
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
|
||||
|
||||
if __name__ == "__main__":
|
||||
arch = sys.argv[1]
|
||||
zig_path = sys.argv[2]
|
||||
spec = importlib.util.spec_from_file_location("arch", "test/kernel/arch/" + arch + "/rt-test.py")
|
||||
arch_module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(arch_module)
|
||||
|
||||
# The list of log statements to look for before arch init is called +
|
||||
# All log statements to look for, including the arch-specific ones +
|
||||
# The list of log statements to look for after arch init is called
|
||||
cases = get_pre_archinit_cases() + arch_module.get_test_cases(TestCase) + get_post_archinit_cases()
|
||||
|
||||
if len(cases) > 0:
|
||||
proc = subprocess.Popen(zig_path + " build run -Drt-test=true -Darch=" + arch, stdout=subprocess.PIPE, shell=True, preexec_fn=os.setsid)
|
||||
atexit.register(cleanup)
|
||||
case_idx = 0
|
||||
read_thread = threading.Thread(target=read_messages, args=(proc,))
|
||||
read_thread.daemon = True
|
||||
read_thread.start()
|
||||
# Go through the cases
|
||||
while case_idx < len(cases):
|
||||
case = cases[case_idx]
|
||||
expected_idx = 0
|
||||
# Go through the expected log messages
|
||||
while expected_idx < len(case.expected):
|
||||
e = case.prefix + case.expected[expected_idx]
|
||||
try:
|
||||
line = msg_queue.get(block=True, timeout=5)
|
||||
except queue.Empty:
|
||||
failure("Timed out waiting for '%s'" %(e))
|
||||
line = line.strip()
|
||||
pattern = re.compile(e)
|
||||
# Pass if the line matches the expected pattern, else fail
|
||||
if pattern.fullmatch(line):
|
||||
test_pass(case, e, expected_idx, line)
|
||||
else:
|
||||
test_failure(case, e, expected_idx, line)
|
||||
expected_idx += 1
|
||||
case_idx += 1
|
||||
sys.exit(0)
|
277
test/runtime_test.zig
Normal file
277
test/runtime_test.zig
Normal file
|
@ -0,0 +1,277 @@
|
|||
const std = @import("std");
|
||||
const ChildProcess = std.ChildProcess;
|
||||
const Thread = std.Thread;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Builder = std.build.Builder;
|
||||
const Step = std.build.Step;
|
||||
const Queue = std.atomic.Queue([]const u8);
|
||||
const Node = std.TailQueue([]const u8).Node;
|
||||
|
||||
// Creating a new runtime test:
|
||||
// 1. Add a enum to `TestMode`. The name should try to describe the test in one word :P
|
||||
// 2. Add a description for the new runtime test to explain to the use what this will test.
|
||||
// 3. Create a function with in the RuntimeStep struct that will perform the test. At least this
|
||||
// should use `self.get_msg()` which will get the serial log lines from the OS. Look at
|
||||
// test_init or test_panic for examples.
|
||||
// 4. In the create function, add your test mode and test function to the switch.
|
||||
// 5. Celebrate if it works lel
|
||||
|
||||
/// The enumeration of tests with all the runtime tests.
|
||||
pub const TestMode = enum {
|
||||
/// This is for the default test mode. This will just run the OS normally.
|
||||
None,
|
||||
|
||||
/// Run the OS's initialisation runtime tests to ensure the OS is properly set up.
|
||||
Initialisation,
|
||||
|
||||
/// Run the panic runtime test.
|
||||
Panic,
|
||||
|
||||
///
|
||||
/// Return a string description for the test mode provided.
|
||||
///
|
||||
/// Argument:
|
||||
/// IN mode: TestMode - The test mode.
|
||||
///
|
||||
/// Return: []const u8
|
||||
/// The string description for the test mode.
|
||||
///
|
||||
pub fn getDescription(mode: TestMode) []const u8 {
|
||||
return switch (mode) {
|
||||
.None => "Runs the OS normally (Default)",
|
||||
.Initialisation => "Initialisation runtime tests",
|
||||
.Panic => "Panic runtime tests",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// The runtime step for running the runtime tests for the OS.
|
||||
pub const RuntimeStep = struct {
|
||||
/// The Step, that is all you need to know
|
||||
step: Step,
|
||||
|
||||
/// The builder pointer, also all you need to know
|
||||
builder: *Builder,
|
||||
|
||||
/// The message queue that stores the log lines
|
||||
msg_queue: Queue,
|
||||
|
||||
/// The qemu process, this is needed for the `read_logs` thread.
|
||||
os_proc: *ChildProcess,
|
||||
|
||||
/// The argv of the qemu process so can create the qemu process
|
||||
argv: [][]const u8,
|
||||
|
||||
/// The test function that will be run for the current runtime test.
|
||||
test_func: TestFn,
|
||||
|
||||
/// The error set for the RuntimeStep
|
||||
const Error = error{
|
||||
/// The error for if a test fails. If the test function returns false, this will be thrown
|
||||
/// at the wnd of the make function as we need to clean up first. This will ensure the
|
||||
/// build fails.
|
||||
TestFailed,
|
||||
|
||||
/// This is used for `self.get_msg()` when the queue is empty after a timeout.
|
||||
QueueEmpty,
|
||||
};
|
||||
|
||||
/// The type of the test function.
|
||||
const TestFn = fn (self: *RuntimeStep) bool;
|
||||
|
||||
/// The time used for getting message from the message queue. This is in milliseconds.
|
||||
const queue_timeout: usize = 5000;
|
||||
|
||||
///
|
||||
/// This will just print all the serial logs.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN/OUT self: *RuntimeStep - Self.
|
||||
///
|
||||
/// Return: bool
|
||||
/// This will always return true
|
||||
///
|
||||
fn print_logs(self: *RuntimeStep) bool {
|
||||
while (true) {
|
||||
const msg = self.get_msg() catch return true;
|
||||
defer self.builder.allocator.free(msg);
|
||||
std.debug.warn("{}\n", .{msg});
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// This tests the OS is initialised correctly by checking that we get a `SUCCESS` at the end.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN/OUT self: *RuntimeStep - Self.
|
||||
///
|
||||
/// Return: bool
|
||||
/// Whether the test has passed or failed.
|
||||
///
|
||||
fn test_init(self: *RuntimeStep) bool {
|
||||
while (true) {
|
||||
const msg = self.get_msg() catch return false;
|
||||
defer self.builder.allocator.free(msg);
|
||||
// Print the line to see what is going on
|
||||
std.debug.warn("{}\n", .{msg});
|
||||
if (std.mem.startsWith(u8, msg, "[ERROR] FAILURE")) {
|
||||
return false;
|
||||
} else if (std.mem.eql(u8, msg, "[INFO] SUCCESS")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// This tests the OS's panic by checking that we get a kernel panic for integer overflow.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN/OUT self: *RuntimeStep - Self.
|
||||
///
|
||||
/// Return: bool
|
||||
/// Whether the test has passed or failed.
|
||||
///
|
||||
fn test_panic(self: *RuntimeStep) bool {
|
||||
while (true) {
|
||||
const msg = self.get_msg() catch return false;
|
||||
defer self.builder.allocator.free(msg);
|
||||
// Print the line to see what is going on
|
||||
std.debug.warn("{}\n", .{msg});
|
||||
if (std.mem.eql(u8, msg, "[ERROR] Kernel panic: integer overflow")) {
|
||||
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
|
||||
/// will call the test function to test a specifics part of the OS defined by the test mode.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN/OUT step: *Step - The step of this step.
|
||||
///
|
||||
/// Error: Thread.SpawnError || ChildProcess.SpawnError || Allocator.Error || Error
|
||||
/// Thread.SpawnError - If there is an error spawning the real logs thread.
|
||||
/// ChildProcess.SpawnError - If there is an error spawning the qemu process.
|
||||
/// Allocator.Error.OutOfMemory - If there is no more memory to allocate.
|
||||
/// Error.TestFailed - The error if the test failed.
|
||||
///
|
||||
fn make(step: *Step) (Thread.SpawnError || ChildProcess.SpawnError || Allocator.Error || Error)!void {
|
||||
const self = @fieldParentPtr(RuntimeStep, "step", step);
|
||||
|
||||
// Create the qemu process
|
||||
self.os_proc = try ChildProcess.init(self.argv, self.builder.allocator);
|
||||
defer self.os_proc.deinit();
|
||||
|
||||
self.os_proc.stdout_behavior = .Pipe;
|
||||
self.os_proc.stdin_behavior = .Inherit;
|
||||
self.os_proc.stderr_behavior = .Inherit;
|
||||
|
||||
try self.os_proc.spawn();
|
||||
|
||||
// Start up the read thread
|
||||
var thread = try Thread.spawn(self, read_logs);
|
||||
|
||||
// Call the testing function
|
||||
const res = self.test_func(self);
|
||||
|
||||
// Now kill our baby
|
||||
_ = try self.os_proc.kill();
|
||||
|
||||
// Join the thread
|
||||
thread.wait();
|
||||
|
||||
// Free the rest of the queue
|
||||
while (self.msg_queue.get()) |node| {
|
||||
self.builder.allocator.free(node.data);
|
||||
self.builder.allocator.destroy(node);
|
||||
}
|
||||
|
||||
// If the test function returns false, then fail the build
|
||||
if (!res) {
|
||||
return Error.TestFailed;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// This is to only be used in the read logs thread. This reads the stdout of the qemu process
|
||||
/// and stores each line in the queue.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN/OUT self: *RuntimeStep - Self.
|
||||
///
|
||||
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;
|
||||
while (true) {
|
||||
const line = stream.readUntilDelimiterAlloc(self.builder.allocator, '\n', max_line_length) catch |e| switch (e) {
|
||||
error.EndOfStream => {
|
||||
// When the qemu process closes, this will return a EndOfStream, so can catch and return so then can
|
||||
// join the thread to exit nicely :)
|
||||
return;
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
// put line in the queue
|
||||
var node = self.builder.allocator.create(Node) catch unreachable;
|
||||
node.* = Node.init(line);
|
||||
self.msg_queue.put(node);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// This return a log message from the queue in the order it would appear in the qemu process.
|
||||
/// The line will need to be free with allocator.free(line) then finished with the line.
|
||||
///
|
||||
/// Arguments:
|
||||
/// IN/OUT self: *RuntimeStep - Self.
|
||||
///
|
||||
/// Return: []const u8
|
||||
/// A log line from the queue.
|
||||
///
|
||||
/// Error: Error
|
||||
/// error.QueueEmpty - If the queue is empty for more than the timeout, this will be thrown.
|
||||
///
|
||||
fn get_msg(self: *RuntimeStep) Error![]const u8 {
|
||||
var i: usize = 0;
|
||||
while (i < queue_timeout) : (i += 1) {
|
||||
if (self.msg_queue.get()) |node| {
|
||||
defer self.builder.allocator.destroy(node);
|
||||
return node.data;
|
||||
}
|
||||
std.time.sleep(std.time.ns_per_ms);
|
||||
}
|
||||
return Error.QueueEmpty;
|
||||
}
|
||||
|
||||
///
|
||||
/// Create a runtime step with a specific test mode.
|
||||
///
|
||||
/// Argument:
|
||||
/// IN builder: *Builder - The builder. This is used for the allocator.
|
||||
/// IN test_mode: TestMode - The test mode.
|
||||
/// IN qemu_args: [][]const u8 - The qemu arguments used to create the OS process.
|
||||
///
|
||||
/// Return: *RuntimeStep
|
||||
/// The Runtime step pointer to add to the build process.
|
||||
///
|
||||
pub fn create(builder: *Builder, test_mode: TestMode, qemu_args: [][]const u8) *RuntimeStep {
|
||||
const runtime_step = builder.allocator.create(RuntimeStep) catch unreachable;
|
||||
runtime_step.* = RuntimeStep{
|
||||
.step = Step.init(.Custom, builder.fmt("Runtime {}", .{@tagName(test_mode)}), builder.allocator, make),
|
||||
.builder = builder,
|
||||
.msg_queue = Queue.init(),
|
||||
.os_proc = undefined,
|
||||
.argv = qemu_args,
|
||||
.test_func = switch (test_mode) {
|
||||
.None => print_logs,
|
||||
.Initialisation => test_init,
|
||||
.Panic => test_panic,
|
||||
},
|
||||
};
|
||||
return runtime_step;
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue