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
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
build_mode: ["", -Drelease-fast=true, -Drelease-safe=true, -Drelease-small=true]
|
build_mode: ["", -Drelease-fast, -Drelease-safe, -Drelease-small]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
@ -27,11 +27,13 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install qemu qemu-system --fix-missing
|
sudo apt-get install qemu qemu-system --fix-missing
|
||||||
|
- name: Check formatting
|
||||||
|
run: zig*/zig fmt --check src
|
||||||
- name: Build kernel
|
- name: Build kernel
|
||||||
run: zig*/zig build ${{ matrix.build_mode }}
|
run: zig*/zig build ${{ matrix.build_mode }}
|
||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: zig*/zig build test ${{ matrix.build_mode }}
|
run: zig*/zig build test ${{ matrix.build_mode }}
|
||||||
- name: Run runtime tests
|
- name: Run runtime test - Initialisation
|
||||||
run: zig*/zig build test -Drt-test=true ${{ matrix.build_mode }}
|
run: zig*/zig build rt-test -Ddisable-display -Dtest-mode=Initialisation ${{ matrix.build_mode }}
|
||||||
- name: Check formatting
|
- name: Run runtime test - Panic
|
||||||
run: zig*/zig fmt --check src
|
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
|
zig build debug
|
||||||
```
|
```
|
||||||
|
|
||||||
## Test
|
## Unit testing
|
||||||
|
|
||||||
Run the unit tests or runtime tests.
|
Run the unit tests.
|
||||||
|
|
||||||
```Shell
|
```Shell
|
||||||
zig build test
|
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
|
## 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`).
|
* `-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.
|
* **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.
|
* **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`.
|
* `-Darch=`: String (default `x86`). Currently the only supported value is `x86`.
|
||||||
* **build**: Build for a certain architecture.
|
* **build**: Build for a certain architecture.
|
||||||
* **test**: Test a certain architecture.
|
* **test**: Test a certain architecture.
|
||||||
|
* `-Ddisable-display`: Boolean (default `false`)
|
||||||
|
* This disables the display output of QEMU.
|
||||||
|
|
||||||
## Contribution
|
## Contribution
|
||||||
|
|
||||||
|
|
112
build.zig
112
build.zig
|
@ -1,5 +1,7 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const rt = @import("test/runtime_test.zig");
|
||||||
|
const RuntimeStep = rt.RuntimeStep;
|
||||||
const Builder = std.build.Builder;
|
const Builder = std.build.Builder;
|
||||||
const LibExeObjStep = std.build.LibExeObjStep;
|
const LibExeObjStep = std.build.LibExeObjStep;
|
||||||
const Step = std.build.Step;
|
const Step = std.build.Step;
|
||||||
|
@ -7,6 +9,8 @@ const Target = std.Target;
|
||||||
const CrossTarget = std.zig.CrossTarget;
|
const CrossTarget = std.zig.CrossTarget;
|
||||||
const fs = std.fs;
|
const fs = std.fs;
|
||||||
const Mode = builtin.Mode;
|
const Mode = builtin.Mode;
|
||||||
|
const TestMode = rt.TestMode;
|
||||||
|
const ArrayList = std.ArrayList;
|
||||||
|
|
||||||
const x86_i686 = CrossTarget{
|
const x86_i686 = CrossTarget{
|
||||||
.cpu_arch = .i386,
|
.cpu_arch = .i386,
|
||||||
|
@ -33,88 +37,100 @@ pub fn build(b: *Builder) !void {
|
||||||
const main_src = "src/kernel/kmain.zig";
|
const main_src = "src/kernel/kmain.zig";
|
||||||
const arch_root = "src/kernel/arch";
|
const arch_root = "src/kernel/arch";
|
||||||
const constants_path = try fs.path.join(b.allocator, &[_][]const u8{ arch_root, arch, "constants.zig" });
|
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" });
|
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 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 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 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 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()) {
|
const make_iso = switch (target.getCpuArch()) {
|
||||||
.i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec.getOutputPath(), output_iso }),
|
.i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec.getOutputPath(), output_iso }),
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
|
|
||||||
make_iso.step.dependOn(&exec.step);
|
make_iso.step.dependOn(&exec.step);
|
||||||
|
|
||||||
b.default_step.dependOn(&make_iso.step);
|
b.default_step.dependOn(&make_iso.step);
|
||||||
|
|
||||||
const test_step = b.step("test", "Run tests");
|
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 mock_path = "\"../../test/mock/kernel/\"";
|
||||||
const arch_mock_path = "\"../../../../test/mock/kernel/\"";
|
const arch_mock_path = "\"../../../../test/mock/kernel/\"";
|
||||||
const unit_tests = b.addTest(main_src);
|
const unit_tests = b.addTest(main_src);
|
||||||
unit_tests.setBuildMode(build_mode);
|
unit_tests.setBuildMode(build_mode);
|
||||||
unit_tests.setMainPkgPath(".");
|
unit_tests.setMainPkgPath(".");
|
||||||
unit_tests.addPackagePath("constants", constants_path);
|
unit_tests.addPackagePath("constants", constants_path);
|
||||||
unit_tests.addBuildOption(bool, "rt_test", rt_test);
|
unit_tests.addBuildOption(TestMode, "test_mode", test_mode);
|
||||||
unit_tests.addBuildOption([]const u8, "mock_path", mock_path);
|
unit_tests.addBuildOption([]const u8, "mock_path", mock_path);
|
||||||
unit_tests.addBuildOption([]const u8, "arch_mock_path", arch_mock_path);
|
unit_tests.addBuildOption([]const u8, "arch_mock_path", arch_mock_path);
|
||||||
|
|
||||||
if (builtin.os.tag != .windows) unit_tests.enable_qemu = true;
|
if (builtin.os.tag != .windows) {
|
||||||
|
unit_tests.enable_qemu = true;
|
||||||
|
}
|
||||||
|
|
||||||
unit_tests.setTarget(.{ .cpu_arch = target.cpu_arch });
|
unit_tests.setTarget(.{ .cpu_arch = target.cpu_arch });
|
||||||
|
|
||||||
test_step.dependOn(&unit_tests.step);
|
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_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 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_cmd = b.addSystemCommand(qemu_args);
|
||||||
const qemu_debug_cmd = b.addSystemCommand(qemu_args);
|
const qemu_debug_cmd = b.addSystemCommand(qemu_args);
|
||||||
qemu_debug_cmd.addArgs(&[_][]const u8{ "-s", "-S" });
|
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_cmd.step.dependOn(&make_iso.step);
|
||||||
qemu_debug_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 expectEqual = std.testing.expectEqual;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const is_test = builtin.is_test;
|
const is_test = builtin.is_test;
|
||||||
|
const panic = @import("../../panic.zig").panic;
|
||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const mock_path = build_options.arch_mock_path;
|
const mock_path = build_options.arch_mock_path;
|
||||||
const arch = if (is_test) @import(mock_path ++ "arch_mock.zig") else @import("arch.zig");
|
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
|
// Load the TSS
|
||||||
arch.ltr(TSS_OFFSET);
|
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 {
|
fn mock_lgdt(ptr: *const GdtPtr) void {
|
||||||
|
@ -671,8 +674,13 @@ test "init" {
|
||||||
///
|
///
|
||||||
fn rt_loadedGDTSuccess() void {
|
fn rt_loadedGDTSuccess() void {
|
||||||
const loaded_gdt = arch.sgdt();
|
const loaded_gdt = arch.sgdt();
|
||||||
expect(gdt_ptr.limit == loaded_gdt.limit);
|
if (gdt_ptr.limit != loaded_gdt.limit) {
|
||||||
expect(gdt_ptr.base == loaded_gdt.base);
|
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 {
|
fn runtimeTests() void {
|
||||||
rt_loadedGDTSuccess();
|
rt_loadedGDTSuccess();
|
||||||
log.logInfo("GDT: Tested loading GDT\n", .{});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ const expectEqual = std.testing.expectEqual;
|
||||||
const expectError = std.testing.expectError;
|
const expectError = std.testing.expectError;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const is_test = builtin.is_test;
|
const is_test = builtin.is_test;
|
||||||
|
const panic = @import("../../panic.zig").panic;
|
||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const mock_path = build_options.arch_mock_path;
|
const mock_path = build_options.arch_mock_path;
|
||||||
const gdt = if (is_test) @import(mock_path ++ "gdt_mock.zig") else @import("gdt.zig");
|
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,
|
.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{
|
var idt_entries: [NUMBER_OF_ENTRIES]IdtEntry = [_]IdtEntry{IdtEntry{
|
||||||
.base_low = 0,
|
.base_low = 0,
|
||||||
.selector = 0,
|
.selector = 0,
|
||||||
|
@ -186,7 +186,10 @@ pub fn init() void {
|
||||||
|
|
||||||
arch.lidt(&idt_ptr);
|
arch.lidt(&idt_ptr);
|
||||||
|
|
||||||
if (build_options.rt_test) runtimeTests();
|
switch (build_options.test_mode) {
|
||||||
|
.Initialisation => runtimeTests(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testHandler0() callconv(.Naked) void {}
|
fn testHandler0() callconv(.Naked) void {}
|
||||||
|
@ -325,8 +328,12 @@ test "init" {
|
||||||
///
|
///
|
||||||
fn rt_loadedIDTSuccess() void {
|
fn rt_loadedIDTSuccess() void {
|
||||||
const loaded_idt = arch.sidt();
|
const loaded_idt = arch.sidt();
|
||||||
expect(idt_ptr.limit == loaded_idt.limit);
|
if (idt_ptr.limit != loaded_idt.limit) {
|
||||||
expect(idt_ptr.base == loaded_idt.base);
|
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", .{});
|
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.
|
/// IN irq_num: u8 - The IRQ index to test.
|
||||||
///
|
///
|
||||||
/// Return: bool
|
/// Return: bool
|
||||||
/// Whether the IRQ index if valid.
|
/// Whether the IRQ index is valid.
|
||||||
///
|
///
|
||||||
pub fn isValidIrq(irq_num: u32) bool {
|
pub fn isValidIrq(irq_num: u32) bool {
|
||||||
return irq_num < NUMBER_OF_ENTRIES;
|
return irq_num < NUMBER_OF_ENTRIES;
|
||||||
|
@ -136,7 +136,10 @@ pub fn init() void {
|
||||||
openIrq(i, interrupts.getInterruptStub(i));
|
openIrq(i, interrupts.getInterruptStub(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (build_options.rt_test) runtimeTests();
|
switch (build_options.test_mode) {
|
||||||
|
.Initialisation => runtimeTests(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testFunction0() callconv(.Naked) void {}
|
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 {
|
fn rt_unregisteredHandlers() void {
|
||||||
// Ensure all ISR are not registered yet
|
// Ensure all ISR are not registered yet
|
||||||
for (irq_handlers) |h, i| {
|
for (irq_handlers) |h, i| {
|
||||||
if (h) |_| {
|
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| {
|
for (idt_entries) |entry, i| {
|
||||||
if (i >= IRQ_OFFSET and isValidIrq(i - IRQ_OFFSET)) {
|
if (i >= IRQ_OFFSET and isValidIrq(i - IRQ_OFFSET)) {
|
||||||
if (!idt.isIdtOpen(entry)) {
|
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.
|
/// 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;
|
var isr_handlers: [NUMBER_OF_ENTRIES]?IsrHandler = [_]?IsrHandler{null} ** NUMBER_OF_ENTRIES;
|
||||||
|
|
||||||
/// The syscall hander.
|
/// The syscall handler.
|
||||||
var syscall_handler: ?IsrHandler = null;
|
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
|
/// IN isr_num: u16 - The isr number to check
|
||||||
///
|
///
|
||||||
/// Return: bool
|
/// Return: bool
|
||||||
/// Whether a ISR hander index if valid.
|
/// Whether a ISR handler index is valid.
|
||||||
///
|
///
|
||||||
pub fn isValidIsr(isr_num: u32) bool {
|
pub fn isValidIsr(isr_num: u32) bool {
|
||||||
return isr_num < NUMBER_OF_ENTRIES or isr_num == syscalls.INTERRUPT;
|
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));
|
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 {}
|
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 {
|
fn rt_unregisteredHandlers() void {
|
||||||
// Ensure all ISR are not registered yet
|
// Ensure all ISR are not registered yet
|
||||||
for (isr_handlers) |h, i| {
|
for (isr_handlers) |h, i| {
|
||||||
if (h) |_| {
|
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| {
|
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", .{});
|
log.logInfo("ISR: Tested registered handlers\n", .{});
|
||||||
|
@ -383,7 +386,7 @@ fn rt_openedIdtEntries() void {
|
||||||
for (idt_entries) |entry, i| {
|
for (idt_entries) |entry, i| {
|
||||||
if (isValidIsr(i)) {
|
if (isValidIsr(i)) {
|
||||||
if (!idt.isIdtOpen(entry)) {
|
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 mem = @import("../../mem.zig");
|
||||||
const vmm = @import("../../vmm.zig");
|
const vmm = @import("../../vmm.zig");
|
||||||
const multiboot = @import("multiboot.zig");
|
const multiboot = @import("multiboot.zig");
|
||||||
const options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
/// An array of directory entries and page tables. Forms the first level of paging and covers the entire 4GB memory space.
|
/// An array of directory entries and page tables. Forms the first level of paging and covers the entire 4GB memory space.
|
||||||
|
@ -138,7 +138,7 @@ inline fn virtToTableEntryIdx(virt: usize) usize {
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// val: *align(1) u32 - The entry to modify
|
/// 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 {
|
inline fn setAttribute(val: *align(1) u32, attr: u32) void {
|
||||||
val.* |= attr;
|
val.* |= attr;
|
||||||
|
@ -149,7 +149,7 @@ inline fn setAttribute(val: *align(1) u32, attr: u32) void {
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// val: *align(1) u32 - The entry to modify
|
/// 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 {
|
inline fn clearAttribute(val: *align(1) u32, attr: u32) void {
|
||||||
val.* &= ~attr;
|
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.
|
/// 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:
|
/// Arguments:
|
||||||
/// IN virt_addr: usize - The start of the virtual space to map
|
/// 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:
|
/// Arguments:
|
||||||
/// IN virtual_start: usize - The start of the virtual region to map
|
/// 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 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 physical_end: usize - The end (exclusive) of the physical region to map to
|
||||||
/// IN attrs: vmm.Attributes - The attributes to apply to this mapping
|
/// 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
|
/// IN/OUT 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 dir: *Directory - The page directory to map within
|
||||||
///
|
///
|
||||||
/// Error: vmm.MapperError || std.mem.Allocator.Error
|
/// Error: vmm.MapperError || std.mem.Allocator.Error
|
||||||
/// * - See mapDirEntry
|
/// * - See mapDirEntry
|
||||||
|
@ -326,7 +326,7 @@ pub fn map(virt_start: usize, virt_end: usize, phys_start: usize, phys_end: usiz
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// IN virtual_start: usize - The start of the virtual region to unmap
|
/// 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
|
/// 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
|
/// Error: std.mem.Allocator.Error || vmm.MapperError
|
||||||
/// vmm.MapperError.NotMapped - If the region being unmapped wasn't mapped in the first place
|
/// 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", .{});
|
log.logInfo("Init paging\n", .{});
|
||||||
defer log.logInfo("Done 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});
|
panic(@errorReturnTrace(), "Failed to register page fault ISR: {}\n", .{e});
|
||||||
};
|
};
|
||||||
const dir_physaddr = @ptrToInt(mem.virtToPhys(&kernel_directory));
|
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)
|
: [addr] "{eax}" (dir_physaddr)
|
||||||
);
|
);
|
||||||
const v_end = std.mem.alignForward(@ptrToInt(mem_profile.vaddr_end) + mem.FIXED_ALLOC_SIZE, PAGE_SIZE_4KB);
|
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 {
|
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
|
// 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_callback: *u32;
|
||||||
extern var rt_fault_callback2: *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
|
// Accessing unmapped mem causes a page fault
|
||||||
var ptr = @intToPtr(*u8, v_end);
|
var ptr = @intToPtr(*u8, v_end);
|
||||||
var value = ptr.*;
|
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
|
// This is the label that we return to after processing the page fault
|
||||||
asm volatile (
|
asm volatile (
|
||||||
\\.global rt_fault_callback
|
\\.global rt_fault_callback
|
||||||
\\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", .{});
|
log.logInfo("Paging: Tested accessing unmapped memory\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rt_accessMappedMem(v_end: u32) void {
|
fn rt_accessMappedMem(v_end: u32) void {
|
||||||
use_callback2 = true;
|
use_callback2 = true;
|
||||||
faulted = false;
|
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 ptr = @intToPtr(*u8, v_end - PAGE_SIZE_4KB);
|
||||||
var value = ptr.*;
|
var value = ptr.*;
|
||||||
asm volatile (
|
asm volatile (
|
||||||
\\.global rt_fault_callback2
|
\\.global rt_fault_callback2
|
||||||
\\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", .{});
|
log.logInfo("Paging: Tested accessing mapped memory\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -468,7 +468,10 @@ pub fn init() void {
|
||||||
// Clear the IRQ for the slave
|
// Clear the IRQ for the slave
|
||||||
clearMask(IRQ_CASCADE_FOR_SLAVE);
|
clearMask(IRQ_CASCADE_FOR_SLAVE);
|
||||||
|
|
||||||
if (build_options.rt_test) runtimeTests();
|
switch (build_options.test_mode) {
|
||||||
|
.Initialisation => runtimeTests(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "sendCommandMaster" {
|
test "sendCommandMaster" {
|
||||||
|
@ -814,11 +817,11 @@ test "init" {
|
||||||
fn rt_picAllMasked() void {
|
fn rt_picAllMasked() void {
|
||||||
// The master will have interrupt 2 clear because this is the link to the slave (third bit)
|
// The master will have interrupt 2 clear because this is the link to the slave (third bit)
|
||||||
if (readDataMaster() != 0xFB) {
|
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) {
|
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", .{});
|
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" {
|
test "sendCommand" {
|
||||||
|
@ -551,7 +554,7 @@ fn rt_waitTicks() void {
|
||||||
const difference = getTicks() - waiting;
|
const difference = getTicks() - waiting;
|
||||||
|
|
||||||
if (previous_count + epsilon < difference or previous_count > difference + epsilon) {
|
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", .{});
|
log.logInfo("PIT: Tested wait ticks\n", .{});
|
||||||
|
@ -575,13 +578,13 @@ fn rt_waitTicks2() void {
|
||||||
const difference = getTicks() + 15 - waiting;
|
const difference = getTicks() + 15 - waiting;
|
||||||
|
|
||||||
if (previous_count + epsilon < difference or previous_count > difference + epsilon) {
|
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
|
// Reset ticks
|
||||||
ticks = 0;
|
ticks = 0;
|
||||||
|
|
||||||
|
log.logInfo("PIT: Tested wait ticks 2\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -593,7 +596,7 @@ fn rt_initCounter_0() void {
|
||||||
const expected_hz: u32 = 10027;
|
const expected_hz: u32 = 10027;
|
||||||
|
|
||||||
if (time_ns != expected_ns or time_under_1_ns != expected_ps or getFrequency() != expected_hz) {
|
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(),
|
getFrequency(),
|
||||||
expected_hz,
|
expected_hz,
|
||||||
time_ns,
|
time_ns,
|
||||||
|
@ -611,19 +614,19 @@ fn rt_initCounter_0() void {
|
||||||
irq_exists = true;
|
irq_exists = true;
|
||||||
},
|
},
|
||||||
error.InvalidIrq => {
|
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) {
|
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 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);
|
const actual_mode = readBackCommand(CounterSelect.Counter0);
|
||||||
|
|
||||||
if (expected_mode != actual_mode) {
|
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", .{});
|
log.logInfo("PIT: Tested init\n", .{});
|
||||||
|
|
|
@ -49,9 +49,9 @@ var ticks: u32 = 0;
|
||||||
/// registers so don't get inconsistent values.
|
/// registers so don't get inconsistent values.
|
||||||
///
|
///
|
||||||
/// Return: bool
|
/// 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;
|
return (cmos.readStatusRegister(cmos.StatusRegister.A, false) & 0x80) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ fn bcdToBinary(bcd: u32) u32 {
|
||||||
///
|
///
|
||||||
fn readRtcRegisters() DateTime {
|
fn readRtcRegisters() DateTime {
|
||||||
// Make sure there isn't a update in progress
|
// Make sure there isn't a update in progress
|
||||||
while (isBuzzy()) {}
|
while (isBusy()) {}
|
||||||
|
|
||||||
var date_time = DateTime{
|
var date_time = DateTime{
|
||||||
.second = cmos.readRtcRegister(cmos.RtcRegister.SECOND),
|
.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
|
// Read status register C to clear any interrupts that may have happened during set up
|
||||||
const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false);
|
const reg_c = cmos.readStatusRegister(cmos.StatusRegister.C, false);
|
||||||
|
|
||||||
if (build_options.rt_test) runtimeTests();
|
switch (build_options.test_mode) {
|
||||||
|
.Initialisation => runtimeTests(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "isBuzzy not buzzy" {
|
test "isBusy not busy" {
|
||||||
cmos.initTest();
|
cmos.initTest();
|
||||||
defer cmos.freeTest();
|
defer cmos.freeTest();
|
||||||
|
|
||||||
|
@ -295,10 +298,10 @@ test "isBuzzy not buzzy" {
|
||||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x60) },
|
.{ cmos.StatusRegister.A, false, @as(u8, 0x60) },
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(!isBuzzy());
|
expect(!isBusy());
|
||||||
}
|
}
|
||||||
|
|
||||||
test "isBuzzy buzzy" {
|
test "isBusy busy" {
|
||||||
cmos.initTest();
|
cmos.initTest();
|
||||||
defer cmos.freeTest();
|
defer cmos.freeTest();
|
||||||
|
|
||||||
|
@ -307,7 +310,7 @@ test "isBuzzy buzzy" {
|
||||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x80) },
|
.{ cmos.StatusRegister.A, false, @as(u8, 0x80) },
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(isBuzzy());
|
expect(isBusy());
|
||||||
}
|
}
|
||||||
|
|
||||||
test "calcDayOfWeek" {
|
test "calcDayOfWeek" {
|
||||||
|
@ -446,7 +449,7 @@ test "readRtcRegisters" {
|
||||||
cmos.initTest();
|
cmos.initTest();
|
||||||
defer cmos.freeTest();
|
defer cmos.freeTest();
|
||||||
|
|
||||||
// Have 2 buzzy loops
|
// Have 2 busy loops
|
||||||
cmos.addTestParams(
|
cmos.addTestParams(
|
||||||
"readStatusRegister",
|
"readStatusRegister",
|
||||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x80), cmos.StatusRegister.A, false, @as(u8, 0x80), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
.{ 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();
|
cmos.initTest();
|
||||||
defer cmos.freeTest();
|
defer cmos.freeTest();
|
||||||
|
|
||||||
// No buzzy loop
|
// No busy loop
|
||||||
cmos.addTestParams(
|
cmos.addTestParams(
|
||||||
"readStatusRegister",
|
"readStatusRegister",
|
||||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
.{ 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
|
// Will try again, and now stable
|
||||||
// No buzzy loop
|
// No busy loop
|
||||||
cmos.addTestParams(
|
cmos.addTestParams(
|
||||||
"readStatusRegister",
|
"readStatusRegister",
|
||||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
||||||
|
@ -555,7 +558,7 @@ test "readRtc is BCD" {
|
||||||
cmos.initTest();
|
cmos.initTest();
|
||||||
defer cmos.freeTest();
|
defer cmos.freeTest();
|
||||||
|
|
||||||
// No buzzy loop
|
// No busy loop
|
||||||
cmos.addTestParams(
|
cmos.addTestParams(
|
||||||
"readStatusRegister",
|
"readStatusRegister",
|
||||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
.{ 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();
|
cmos.initTest();
|
||||||
defer cmos.freeTest();
|
defer cmos.freeTest();
|
||||||
|
|
||||||
// No buzzy loop
|
// No busy loop
|
||||||
cmos.addTestParams(
|
cmos.addTestParams(
|
||||||
"readStatusRegister",
|
"readStatusRegister",
|
||||||
.{ cmos.StatusRegister.A, false, @as(u8, 0x00), cmos.StatusRegister.A, false, @as(u8, 0x00) },
|
.{ 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;
|
irq_exists = true;
|
||||||
},
|
},
|
||||||
error.InvalidIrq => {
|
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) {
|
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
|
// Check the rate
|
||||||
const status_a = cmos.readStatusRegister(cmos.StatusRegister.A, false);
|
const status_a = cmos.readStatusRegister(cmos.StatusRegister.A, false);
|
||||||
if (status_a & @as(u8, 0x0F) != 7) {
|
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
|
// Check if interrupts are enabled
|
||||||
const status_b = cmos.readStatusRegister(cmos.StatusRegister.B, true);
|
const status_b = cmos.readStatusRegister(cmos.StatusRegister.B, true);
|
||||||
if (status_b & ~@as(u8, 0x40) == 0) {
|
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", .{});
|
log.logInfo("RTC: Tested init\n", .{});
|
||||||
|
@ -727,7 +730,7 @@ fn rt_interrupts() void {
|
||||||
pit.waitTicks(100);
|
pit.waitTicks(100);
|
||||||
|
|
||||||
if (prev_ticks == ticks) {
|
if (prev_ticks == ticks) {
|
||||||
panic(@errorReturnTrace(), "No interrupt happened\n", .{});
|
panic(@errorReturnTrace(), "FAILURE: No interrupt happened\n", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
log.logInfo("RTC: Tested interrupts\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.
|
/// This function computes the divisor for a desired baudrate. Note that multiple baudrates can have the same divisor.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
|
|
|
@ -3,7 +3,8 @@ const testing = @import("std").testing;
|
||||||
const assert = @import("std").debug.assert;
|
const assert = @import("std").debug.assert;
|
||||||
const isr = @import("isr.zig");
|
const isr = @import("isr.zig");
|
||||||
const log = @import("../../log.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
|
/// The isr number associated with syscalls
|
||||||
pub const INTERRUPT: u16 = 0x80;
|
pub const INTERRUPT: u16 = 0x80;
|
||||||
|
@ -241,39 +242,43 @@ pub fn init() void {
|
||||||
defer log.logInfo("Done syscalls\n", .{});
|
defer log.logInfo("Done syscalls\n", .{});
|
||||||
|
|
||||||
isr.registerIsr(INTERRUPT, handle) catch unreachable;
|
isr.registerIsr(INTERRUPT, handle) catch unreachable;
|
||||||
if (options.rt_test) runtimeTests();
|
|
||||||
|
switch (build_options.test_mode) {
|
||||||
|
.Initialisation => runtimeTests(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tests
|
/// 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 {
|
fn testHandler0(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||||
testInt += 1;
|
test_int += 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testHandler1(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
fn testHandler1(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||||
testInt += arg1;
|
test_int += arg1;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testHandler2(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
fn testHandler2(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||||
testInt += arg1 + arg2;
|
test_int += arg1 + arg2;
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testHandler3(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
fn testHandler3(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
||||||
testInt += arg1 + arg2 + arg3;
|
test_int += arg1 + arg2 + arg3;
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testHandler4(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
fn testHandler4(ctx: *arch.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;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testHandler5(ctx: *arch.InterruptContext, arg1: u32, arg2: u32, arg3: u32, arg4: u32, arg5: u32) u32 {
|
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;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,29 +291,40 @@ test "registerSyscall returns SyscallExists" {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtimeTests() void {
|
fn runtimeTests() void {
|
||||||
registerSyscall(123, testHandler0) catch unreachable;
|
registerSyscall(123, testHandler0) catch panic(@errorReturnTrace(), "FAILURE registering handler 0\n", .{});
|
||||||
registerSyscall(124, testHandler1) catch unreachable;
|
registerSyscall(124, testHandler1) catch panic(@errorReturnTrace(), "FAILURE registering handler 1\n", .{});
|
||||||
registerSyscall(125, testHandler2) catch unreachable;
|
registerSyscall(125, testHandler2) catch panic(@errorReturnTrace(), "FAILURE registering handler 2\n", .{});
|
||||||
registerSyscall(126, testHandler3) catch unreachable;
|
registerSyscall(126, testHandler3) catch panic(@errorReturnTrace(), "FAILURE registering handler 3\n", .{});
|
||||||
registerSyscall(127, testHandler4) catch unreachable;
|
registerSyscall(127, testHandler4) catch panic(@errorReturnTrace(), "FAILURE registering handler 4\n", .{});
|
||||||
registerSyscall(128, testHandler5) catch unreachable;
|
registerSyscall(128, testHandler5) catch panic(@errorReturnTrace(), "FAILURE registering handler 5\n", .{});
|
||||||
assert(testInt == 0);
|
|
||||||
|
|
||||||
if (syscall0(123) == 0 and testInt == 1)
|
if (test_int != 0) {
|
||||||
log.logInfo("Syscalls: Tested no args\n", .{});
|
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) {
|
||||||
if (syscall3(126, 2, 3, 4) == 3 and testInt == 17)
|
panic(@errorReturnTrace(), "FAILURE syscall2\n", .{});
|
||||||
log.logInfo("Syscalls: Tested 3 args\n", .{});
|
}
|
||||||
|
|
||||||
if (syscall4(127, 2, 3, 4, 5) == 4 and testInt == 31)
|
if (syscall2(125, 2, 3) != 2 or test_int != 8) {
|
||||||
log.logInfo("Syscalls: Tested 4 args\n", .{});
|
panic(@errorReturnTrace(), "FAILURE syscall2\n", .{});
|
||||||
|
}
|
||||||
if (syscall5(128, 2, 3, 4, 5, 6) == 5 and testInt == 51)
|
|
||||||
log.logInfo("Syscalls: Tested 5 args\n", .{});
|
if (syscall3(126, 2, 3, 4) != 3 or test_int != 17) {
|
||||||
|
panic(@errorReturnTrace(), "FAILURE syscall3\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;
|
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);
|
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
|
// Set by default the underline cursor
|
||||||
setCursorShape(CursorShape.UNDERLINE);
|
setCursorShape(CursorShape.UNDERLINE);
|
||||||
|
|
||||||
if (build_options.rt_test) runtimeTests();
|
switch (build_options.test_mode) {
|
||||||
|
.Initialisation => runtimeTests(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "entryColour" {
|
test "entryColour" {
|
||||||
|
@ -531,7 +534,7 @@ fn rt_correctMaxScanLine() void {
|
||||||
const max_scan_line = getPortData(REG_MAXIMUM_SCAN_LINE);
|
const max_scan_line = getPortData(REG_MAXIMUM_SCAN_LINE);
|
||||||
|
|
||||||
if (max_scan_line != CURSOR_SCANLINE_END) {
|
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", .{});
|
log.logInfo("VGA: Tested max scan line\n", .{});
|
||||||
|
@ -543,14 +546,14 @@ fn rt_correctMaxScanLine() void {
|
||||||
fn rt_correctCursorShape() void {
|
fn rt_correctCursorShape() void {
|
||||||
// Check the global variables are correct
|
// Check the global variables are correct
|
||||||
if (cursor_scanline_start != CURSOR_SCANLINE_MIDDLE or cursor_scanline_end != CURSOR_SCANLINE_END) {
|
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_start = getPortData(REG_CURSOR_START);
|
||||||
const cursor_end = getPortData(REG_CURSOR_END);
|
const cursor_end = getPortData(REG_CURSOR_END);
|
||||||
|
|
||||||
if (cursor_start != CURSOR_SCANLINE_MIDDLE or cursor_end != CURSOR_SCANLINE_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", .{});
|
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);
|
const actual_y_loc = @truncate(u8, actual_linear_loc / WIDTH);
|
||||||
|
|
||||||
if (x != actual_x_loc or y != actual_y_loc) {
|
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
|
// 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.
|
/// Set an entry within a bitmap as occupied.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// IN idx: usize - The index within the bitmap to set.
|
||||||
///
|
///
|
||||||
/// Error: BitmapError.
|
/// Error: BitmapError.
|
||||||
|
@ -86,7 +86,7 @@ pub fn Bitmap(comptime BitmapType: type) type {
|
||||||
/// Set an entry within a bitmap as unoccupied.
|
/// Set an entry within a bitmap as unoccupied.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// IN idx: usize - The index within the bitmap to clear.
|
||||||
///
|
///
|
||||||
/// Error: BitmapError.
|
/// Error: BitmapError.
|
||||||
|
@ -106,7 +106,7 @@ pub fn Bitmap(comptime BitmapType: type) type {
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// IN self: *const Self - The bitmap to use.
|
/// 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.
|
/// Return: BitmapType.
|
||||||
/// The bit corresponding to that index but within a single 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.
|
/// Find a number of contiguous free entries and set them.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// IN num: usize - The number of entries to set.
|
||||||
///
|
///
|
||||||
/// Return: ?usize
|
/// 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.
|
/// 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:
|
/// 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 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
|
/// 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.
|
/// Free previously allocated memory. It is recommended to not call this directly and instead use the Allocator interface.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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 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.
|
/// 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", .{});
|
tty.print("Hello Pluto from kernel :)\n", .{});
|
||||||
|
|
||||||
// The panic runtime tests must run last as they never return
|
switch (build_options.test_mode) {
|
||||||
if (build_options.rt_test) panic_root.runtimeTests();
|
.Initialisation => {
|
||||||
|
log.logInfo("SUCCESS\n", .{});
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
arch.spinWait();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ const LoggingError = error{};
|
||||||
/// The OutStream for the format function
|
/// The OutStream for the format function
|
||||||
const OutStream = std.io.OutStream(void, LoggingError, logCallback);
|
const OutStream = std.io.OutStream(void, LoggingError, logCallback);
|
||||||
|
|
||||||
|
/// The different levels of logging that can be outputted.
|
||||||
pub const Level = enum {
|
pub const Level = enum {
|
||||||
INFO,
|
INFO,
|
||||||
DEBUG,
|
DEBUG,
|
||||||
|
@ -18,6 +19,20 @@ pub const Level = enum {
|
||||||
|
|
||||||
var serial: Serial = undefined;
|
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 {
|
fn logCallback(context: void, str: []const u8) LoggingError!usize {
|
||||||
serial.writeBytes(str);
|
serial.writeBytes(str);
|
||||||
return str.len;
|
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.
|
/// Write a message to the log output stream with a certain logging level.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// IN comptime level: Level - The logging level to use. Determines the message prefix and whether it is filtered.
|
/// IN comptime level: Level - The logging level to use. Determines the message prefix and
|
||||||
/// IN comptime format: []const u8 - The message format. Uses the standard format specification options.
|
/// 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.
|
/// 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 {
|
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.
|
/// Write a message to the log output stream with the INFO level.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// IN args: var - A struct of the parameters for the format string.
|
||||||
///
|
///
|
||||||
pub fn logInfo(comptime format: []const u8, args: var) void {
|
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.
|
/// Write a message to the log output stream with the DEBUG level.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// IN args: var - A struct of the parameters for the format string.
|
||||||
///
|
///
|
||||||
pub fn logDebug(comptime format: []const u8, args: var) void {
|
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.
|
/// Write a message to the log output stream with the WARNING level.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// IN args: var - A struct of the parameters for the format string.
|
||||||
///
|
///
|
||||||
pub fn logWarning(comptime format: []const u8, args: var) void {
|
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.
|
/// Write a message to the log output stream with the ERROR level.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// IN args: var - A struct of the parameters for the format string.
|
||||||
///
|
///
|
||||||
pub fn logError(comptime format: []const u8, args: var) void {
|
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 {
|
pub fn init(ser: Serial) void {
|
||||||
serial = ser;
|
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 {
|
pub fn runtimeTests() void {
|
||||||
inline for (@typeInfo(Level).Enum.fields) |field| {
|
inline for (@typeInfo(Level).Enum.fields) |field| {
|
||||||
const level = @field(Level, field.name);
|
const level = @field(Level, field.name);
|
||||||
|
|
|
@ -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
|
/// 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,
|
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,
|
physical_reserved: []Range,
|
||||||
|
|
||||||
/// The allocator to use before a heap can be set up.
|
/// 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 log = @import("log.zig");
|
||||||
const multiboot = @import("multiboot.zig");
|
const multiboot = @import("multiboot.zig");
|
||||||
const mem = @import("mem.zig");
|
const mem = @import("mem.zig");
|
||||||
|
const build_options = @import("build_options");
|
||||||
const ArrayList = std.ArrayList;
|
const ArrayList = std.ArrayList;
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ const PanicError = error{
|
||||||
InvalidSymbolFile,
|
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 {
|
const MapEntry = struct {
|
||||||
/// The address that the entry corresponds to
|
/// The address that the entry corresponds to
|
||||||
addr: usize,
|
addr: usize,
|
||||||
|
@ -59,7 +60,7 @@ const SymbolMap = struct {
|
||||||
/// Error: std.mem.Allocator.Error
|
/// Error: std.mem.Allocator.Error
|
||||||
/// * - See ArrayList.append
|
/// * - 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 });
|
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.
|
/// whitespace character.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// characters have been consumed.
|
||||||
/// IN end: *const u8 - The end address at which to start looking. A whitespace character must
|
/// IN end: *const u8 - The end address at which to start looking. A whitespace character must
|
||||||
/// be found before this.
|
/// 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.
|
/// PanicError.InvalidSymbolFile: The address given is greater than or equal to the end address.
|
||||||
///
|
///
|
||||||
fn parseChar(ptr: [*]const u8, end: *const u8) PanicError!u8 {
|
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];
|
return ptr[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +222,7 @@ fn parseNonWhitespace(ptr: [*]const u8, end: *const u8) PanicError![*]const u8 {
|
||||||
/// character.
|
/// character.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// characters have been consumed.
|
||||||
/// IN end: *const u8 - The end address at which to start looking. A whitespace character must
|
/// IN end: *const u8 - The end address at which to start looking. A whitespace character must
|
||||||
/// be found before this.
|
/// 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.
|
/// in the format of '\d+\w+[a-zA-Z0-9]+'. Must be terminated by a whitespace character.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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.
|
/// 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
|
/// IN end: *const u8 - The end address at which to start looking. A whitespace character must
|
||||||
/// be found before this.
|
/// 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", .{});
|
defer log.logInfo("Done panic\n", .{});
|
||||||
|
|
||||||
// Exit if we haven't loaded all debug modules
|
// Exit if we haven't loaded all debug modules
|
||||||
if (mem_profile.modules.len < 1)
|
if (mem_profile.modules.len < 1) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
var kmap_start: usize = 0;
|
var kmap_start: usize = 0;
|
||||||
var kmap_end: usize = 0;
|
var kmap_end: usize = 0;
|
||||||
for (mem_profile.modules) |module| {
|
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
|
// Don't try to load the symbols if there was no symbol map file. This is a valid state so just
|
||||||
// exit early
|
// exit early
|
||||||
if (kmap_start == 0 or kmap_end == 0)
|
if (kmap_start == 0 or kmap_end == 0) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var syms = SymbolMap.init(allocator);
|
var syms = SymbolMap.init(allocator);
|
||||||
errdefer syms.deinit();
|
errdefer syms.deinit();
|
||||||
|
@ -307,6 +312,11 @@ pub fn init(mem_profile: *const mem.MemProfile, allocator: *std.mem.Allocator) !
|
||||||
try syms.addEntry(entry);
|
try syms.addEntry(entry);
|
||||||
}
|
}
|
||||||
symbol_map = syms;
|
symbol_map = syms;
|
||||||
|
|
||||||
|
switch (build_options.test_mode) {
|
||||||
|
.Panic => runtimeTests(),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parseChar" {
|
test "parseChar" {
|
||||||
|
@ -441,7 +451,13 @@ test "SymbolMap" {
|
||||||
testing.expectEqual(map.search(2345), "jkl");
|
testing.expectEqual(map.search(2345), "jkl");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Runtime test for panic. This will trigger a integer overflow.
|
||||||
|
///
|
||||||
pub fn runtimeTests() void {
|
pub fn runtimeTests() void {
|
||||||
|
@setRuntimeSafety(true);
|
||||||
var x: u8 = 255;
|
var x: u8 = 255;
|
||||||
x += 1;
|
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) {
|
switch (build_options.test_mode) {
|
||||||
runtimeTests(mem, allocator);
|
.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" {
|
test "alloc" {
|
||||||
bitmap = try Bitmap(u32).init(32, std.heap.page_allocator);
|
bitmap = try Bitmap(u32).init(32, std.heap.page_allocator);
|
||||||
comptime var addr = 0;
|
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 {
|
pub fn init(boot_payload: arch.BootPayload) Serial {
|
||||||
const serial = arch.initSerial(boot_payload);
|
const serial = arch.initSerial(boot_payload);
|
||||||
if (build_options.rt_test) runtimeTests(serial);
|
switch (build_options.test_mode) {
|
||||||
|
.Initialisation => runtimeTests(serial),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
return serial;
|
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_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 physical_end: usize - The end of the physical memory to map to
|
||||||
/// IN attrs: Attributes - The attributes to apply to this region of memory
|
/// 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
|
/// IN spec: Payload - The payload to pass to the mapper
|
||||||
///
|
///
|
||||||
/// Error: std.mem.AllocatorError || MapperError
|
/// 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,
|
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:
|
/// Arguments:
|
||||||
/// IN virtual_start: usize - The start of the virtual region to unmap
|
/// IN virtual_start: usize - The start of the virtual region to unmap
|
||||||
|
@ -148,7 +148,7 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// IN start: usize - The start of the memory region to manage
|
/// 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
|
/// 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 mapper: Mapper - The mapper to use when allocating and freeing regions
|
||||||
/// IN payload: Payload - The payload data to be passed to the mapper
|
/// 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
|
/// Map a region (can span more than one block) of virtual memory to a specific region of memory
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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 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 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
|
/// IN attrs: Attributes - The attributes to apply to the memory regions
|
||||||
///
|
///
|
||||||
/// Error: VmmError || Bitmap(u32).BitmapError || std.mem.Allocator.Error || MapperError
|
/// 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.PhysicalAlreadyAllocated - The physical address has already been allocated
|
||||||
/// VmmError.PhysicalVirtualMismatch - The physical region and virtual region are of different sizes
|
/// VmmError.PhysicalVirtualMismatch - The physical region and virtual region are of different sizes
|
||||||
/// VmmError.InvalidVirtAddresses - The start virtual address is greater than the end address
|
/// 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
|
/// Allocate a number of contiguous blocks of virtual memory
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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 num: usize - The number of blocks to allocate
|
||||||
/// IN attrs: Attributes - The attributes to apply to the mapped memory
|
/// 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
|
/// Free a previous allocation
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// 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
|
/// 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
|
/// Error: Bitmap.BitmapError || VmmError
|
||||||
|
@ -316,12 +316,12 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
||||||
for (physical.items) |block, i| {
|
for (physical.items) |block, i| {
|
||||||
// Clear the address space entry, unmap the virtual memory and free the physical memory
|
// Clear the address space entry, unmap the virtual memory and free the physical memory
|
||||||
try self.bmp.clearEntry(entry + i);
|
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
|
// Unmap the entire range
|
||||||
const region_start = entry * BLOCK_SIZE;
|
const region_start = entry * BLOCK_SIZE;
|
||||||
const region_end = (entry + num_physical_allocations) * 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
|
// The allocation is freed so remove from the map
|
||||||
self.allocations.removeAssertDiscard(vaddr);
|
self.allocations.removeAssertDiscard(vaddr);
|
||||||
} else {
|
} else {
|
||||||
|
@ -336,7 +336,7 @@ pub fn VirtualMemoryManager(comptime Payload: type) type {
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// IN mem_profile: *const mem.MemProfile - The system's memory profile. This is used to find the kernel code region and boot modules
|
/// 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
|
/// Return: VirtualMemoryManager
|
||||||
/// The virtual memory manager created with all reserved virtual regions allocated
|
/// 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;
|
return vmm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,7 +394,7 @@ test "alloc and free" {
|
||||||
std.testing.expectEqual(@as(?usize, null), result);
|
std.testing.expectEqual(@as(?usize, null), result);
|
||||||
should_be_set = false;
|
should_be_set = false;
|
||||||
} else {
|
} 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);
|
std.testing.expectEqual(@as(?usize, vmm.start + entry * BLOCK_SIZE), result);
|
||||||
try virtual_allocations.append(result orelse unreachable);
|
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 pstart: usize - The start of the physical region to map
|
||||||
/// IN pend: usize - The end 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
|
/// 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
|
/// 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 {
|
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
|
/// IN/OUT self: *Self - Self. This is the mocking object to be modified to add
|
||||||
/// the test parameters.
|
/// the test parameters.
|
||||||
/// IN fun_name: []const u8 - The function name to add the test parameters to.
|
/// 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 {
|
pub fn addTestParams(comptime fun_name: []const u8, params: var) void {
|
||||||
var mock_obj = getMockObject();
|
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