Remove the old and in with the new
Added the new testing to the OS files Some spelling INOUT => IN/OUT Added some doc comments to log Added the new runtime to the build + added the None test mode Moved some stuff around None test mode is the default to run/build the OS normally with no runtime tests. Add the new runtime testing to the CI Updated README and CI Increased timeout Print the log message Spelling Move runtime to test folder Add new RT to tty Add a log to use the unmapped memory to cause page fault in release Ensure the integer overflow happens even in release builds
This commit is contained in:
parent
d6d99ef667
commit
2c91e6f9d0
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