Cross-architecture test suite
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
||||
//! In-kernel test cases, run at the end of bring-up when the kernel is built with
|
||||
//! `-Dtest-case=<name>`. Each case writes structured markers to the serial port
|
||||
//! that the QEMU harness (test/qemu_test.py) asserts on:
|
||||
//!
|
||||
//! [PASS]/[FAIL] <check> per assertion
|
||||
//! DANOS-TEST-RESULT: PASS|FAIL overall, for non-faulting cases
|
||||
//!
|
||||
//! Faulting cases (fault-ud, fault-pf, fault-df) deliberately don't return a
|
||||
//! result line — they trigger a CPU exception, and the harness asserts on the
|
||||
//! exception report the handler prints (which also reaches serial).
|
||||
|
||||
const std = @import("std");
|
||||
const danos = @import("danos");
|
||||
const arch = @import("arch");
|
||||
const pmm = @import("pmm.zig");
|
||||
|
||||
/// Formatted write straight to serial, independent of the framebuffer console.
|
||||
fn log(comptime fmt: []const u8, args: anytype) void {
|
||||
var buf: [128]u8 = undefined;
|
||||
arch.serialWrite(std.fmt.bufPrint(&buf, fmt, args) catch return);
|
||||
}
|
||||
|
||||
var passed: u32 = 0;
|
||||
var failed: u32 = 0;
|
||||
|
||||
fn check(name: []const u8, ok: bool) void {
|
||||
if (ok) {
|
||||
passed += 1;
|
||||
log("[PASS] {s}\n", .{name});
|
||||
} else {
|
||||
failed += 1;
|
||||
log("[FAIL] {s}\n", .{name});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(case: []const u8, boot_info: *const BootInfo) void {
|
||||
if (eql(case, "smoke")) {
|
||||
smoke(boot_info);
|
||||
} else if (eql(case, "fault-ud")) {
|
||||
faultInvalidOpcode();
|
||||
} else if (eql(case, "fault-pf")) {
|
||||
faultPageFault();
|
||||
} else if (eql(case, "fault-df")) {
|
||||
faultDoubleFault();
|
||||
} else {
|
||||
log("DANOS-TEST-RESULT: FAIL (unknown case '{s}')\n", .{case});
|
||||
}
|
||||
}
|
||||
|
||||
const BootInfo = danos.BootInfo;
|
||||
|
||||
fn eql(a: []const u8, b: []const u8) bool {
|
||||
return std.mem.eql(u8, a, b);
|
||||
}
|
||||
|
||||
/// Non-destructive checks of the memory map and frame allocator.
|
||||
fn smoke(boot_info: *const BootInfo) void {
|
||||
log("DANOS-TEST-BEGIN: smoke\n", .{});
|
||||
|
||||
// The memory map has some usable RAM.
|
||||
const mm = boot_info.memory_map;
|
||||
const regions = @as([*]const danos.MemoryRegion, @ptrFromInt(mm.regions))[0..mm.len];
|
||||
var usable: u64 = 0;
|
||||
for (regions) |r| {
|
||||
if (r.kind == .usable) usable += r.pages;
|
||||
}
|
||||
check("memory map reports usable RAM", usable > 0);
|
||||
|
||||
// The frame allocator hands out distinct, page-aligned frames.
|
||||
const a = pmm.alloc();
|
||||
const b = pmm.alloc();
|
||||
check("alloc returns a frame", a != null);
|
||||
check("alloc returns distinct frames", a != null and b != null and a.? != b.?);
|
||||
check("frames are page-aligned", (a orelse 1) % danos.page_size == 0);
|
||||
|
||||
// Freeing restores the count.
|
||||
const before = pmm.stats().free_frames;
|
||||
if (a) |p| pmm.free(p);
|
||||
if (b) |p| pmm.free(p);
|
||||
check("free returns frames to the pool", pmm.stats().free_frames == before + 2);
|
||||
|
||||
// Paging is active on our own tables (CR3 is non-zero and page-aligned).
|
||||
const cr3 = arch.readCr3();
|
||||
check("paging active (CR3 set)", cr3 != 0 and cr3 % danos.page_size == 0);
|
||||
|
||||
log("DANOS-TEST-RESULT: {s} ({d} passed, {d} failed)\n", .{
|
||||
if (failed == 0) "PASS" else "FAIL",
|
||||
passed,
|
||||
failed,
|
||||
});
|
||||
log("DANOS-TEST-DONE\n", .{});
|
||||
}
|
||||
|
||||
fn faultInvalidOpcode() void {
|
||||
log("DANOS-TEST-BEGIN: fault-ud\n", .{});
|
||||
asm volatile ("ud2");
|
||||
}
|
||||
|
||||
fn faultPageFault() void {
|
||||
log("DANOS-TEST-BEGIN: fault-pf\n", .{});
|
||||
// Runtime address so the backend emits a register store (not a `mov moffs`,
|
||||
// which the self-hosted x86_64 backend can't encode).
|
||||
var addr: u64 = 0xdeadbeef000; // above our identity-mapped 4 GiB
|
||||
const p: *volatile u64 = @ptrFromInt(addr);
|
||||
p.* = 1;
|
||||
addr += 0;
|
||||
}
|
||||
|
||||
fn faultDoubleFault() void {
|
||||
log("DANOS-TEST-BEGIN: fault-df\n", .{});
|
||||
// Point RSP at unmapped memory, then fault: the CPU can't push the fault
|
||||
// frame, which escalates to #DF — survivable only because #DF runs on IST1.
|
||||
var bad_sp: u64 = 0x5000000000;
|
||||
asm volatile (
|
||||
\\mov %[sp], %%rsp
|
||||
\\ud2
|
||||
:
|
||||
: [sp] "r" (bad_sp),
|
||||
: .{ .memory = true }
|
||||
);
|
||||
bad_sp += 0;
|
||||
}
|
||||
Reference in New Issue
Block a user