ajhahn.de
← FlashOS
Flash 280 lines
// utilc: kernel utility functions.
// Layouts come from src/task_layout.zig.

const layout = #import("task_layout")
const TaskStruct = layout.TaskStruct
const KeRegs = layout.KeRegs

// Kernel-log byte-ring (src/klog_ring.zig). main_output tees every line
// into it so a userland `dmesg` can read the boot log back via
// sys_klog_read — see klog_ring.zig for the overwrite-oldest + lock-free
// rationale.
const klog_ring = #import("klog_ring")

const MU i32 = 0
const PL i32 = 1

extern fn mini_uart_send_string(str [*:0]u8) void
extern fn mini_uart_recv() u8
extern fn pl011_uart_send_string(str [*:0]u8) void
extern fn err_hang() noreturn

/// Render a u64 as 16 hex chars into buf (no NUL).
export fn u64_to_char_array(inw u64, buf [*]mut u8) void {
    var i u32 = 0
    while i < 16 {
        const shift u6 = #intCast((15 - i) * 4)
        const tmp u8 = #intCast((inw >> shift) & 0xF)
        if tmp <= 9 {
            buf[i] = tmp + '0'
        } else {
            buf[i] = tmp - 10 + 'a'
        }
        i += 1
    }
}

export fn char_to_char_array(ch u8, buf [*]mut u8) void {
    buf[0] = ch
}

export fn main_output_char(interface i32, ch u8) void {
    var printable [2]u8 = undefined
    printable[0] = ch
    printable[1] = 0
    main_output(interface, #ptrCast(&printable[0]))
}

export fn main_output(interface i32, str [*:0]u8) void {
    // Tee every emitted line into the kernel log ring before it goes out
    // the UART. pushStr is pure + allocation-free + never re-enters
    // main_output, so this is safe from any context (kernel / syscall /
    // IRQ / pre-`current` boot) and leaves the free-page baseline intact.
    klog_ring.klog.pushStr(str)
    switch interface {
        MU => mini_uart_send_string(str),
        PL => pl011_uart_send_string(str),
        else => main_output(MU, "main_output bad interface\n"),
    }
}

export fn main_output_u64(interface i32, inw u64) void {
    var printable [17]u8 = undefined
    printable[16] = 0
    u64_to_char_array(inw, #ptrCast(&printable[0]))
    main_output(interface, #ptrCast(&printable[0]))
}

export fn main_output_process(interface i32, p *mut TaskStruct) void {
    main_output(interface, "task address: ")
    main_output_u64(interface, #intFromPtr(p))
    main_output(interface, ", state: ")
    main_output_u64(interface, #bitCast(p.state))
    main_output(interface, ", counter: ")
    main_output_u64(interface, #bitCast(p.counter))
    main_output(interface, ", priority: ")
    main_output_u64(interface, #bitCast(p.priority))
    main_output(interface, ", preempt_count: ")
    main_output_u64(interface, #bitCast(p.preempt_count))
    main_output(interface, ", pgd: ")
    main_output_u64(interface, p.mm.pgd)
    main_output(interface, "\n")
}

export fn main_recv(interface i32) u8 {
    switch interface {
        MU => return mini_uart_recv(),
        else => {
            main_output(MU, "main_recv bad interface\n")
            return 0
        },
    }
}

export fn copy_ke_regs(to *mut KeRegs, from *mut KeRegs) void {
    var i usize = 0
    while i < 31 {
        to.regs[i] = from.regs[i]
        i += 1
    }
    to.sp = from.sp
    to.elr = from.elr
    to.pstate = from.pstate
}

export fn memset(dst [*]mut u8, c i32, n_in u64) [*]mut u8 {
    var n = n_in
    var p = dst
    const byte u8 = #truncate(#as(u32, #bitCast(c)))
    while n != 0 {
        p[0] = byte
        p += 1
        n -= 1
    }
    return dst
}

/// Byte-granular memory copy.
export fn memcpy(dst *mut anyopaque, src *anyopaque, bytes u64) *mut anyopaque {
    var d [*]mut u8 = #ptrCast(dst)
    var s [*]u8 = #ptrCast(src)
    var n = bytes

    if #intFromPtr(d) % 8 == 0 && #intFromPtr(s) % 8 == 0 {
        var d64 [*]mut u64 = #ptrCast(#alignCast(d))
        var s64 [*]u64 = #ptrCast(#alignCast(s))
        while n >= 8 {
            d64[0] = s64[0]
            d64 += 1
            s64 += 1
            n -= 8
        }
        d = #ptrCast(d64)
        s = #ptrCast(s64)
    }

    while n > 0 {
        d[0] = s[0]
        d += 1
        s += 1
        n -= 1
    }
    return dst
}

export fn panic(msg [*:0]u8) noreturn {
    main_output(MU, "KERNEL PANIC: ")
    main_output(MU, msg)
    main_output(MU, "\n")
    err_hang()
}

/// Byte-wise compare without alignment requirements. std.mem.eql
/// lowers to wide loads under ReleaseSmall, which trip
/// `SCTLR_EL1.A`-asserted strict alignment when the slices live at
/// odd VAs (newc cpio entry names land at `cursor + 110`; mount-prefix
/// matching starts at arbitrary path offsets). The plain byte loop has
/// no alignment requirement; cost is irrelevant on these short scans.
pub export fn mem_eql_bytes(a [*]u8, b [*]u8, n u64) bool {
    var i u64 = 0
    while i < n {
        if a[i] != b[i] { return false }
        i += 1
    }
    return true
}

// --- Host Tests ---
const std = #import("std")
const testing = std.testing

extern var last_output [1024]u8
extern var last_output_len usize

fn reset_output() void {
    last_output_len = 0
    #memset(&last_output, 0)
}

test "utilc: u64_to_char_array renders hex correctly" {
    var buf [16]u8 = undefined
    u64_to_char_array(0x123456789ABCDEF0, &buf)
    try testing.expectEqualStrings("123456789abcdef0", &buf)

    u64_to_char_array(0x0, &buf)
    try testing.expectEqualStrings("0000000000000000", &buf)

    u64_to_char_array(0xFFFFFFFFFFFFFFFF, &buf)
    try testing.expectEqualStrings("ffffffffffffffff", &buf)
}

test "utilc: char_to_char_array sets char" {
    var buf [1]u8 = undefined
    char_to_char_array('X', &buf)
    try testing.expectEqual(#as(u8, 'X'), buf[0])
}

test "utilc: main_output sends to UART" {
    reset_output()
    main_output(MU, "test output")
    try testing.expectEqualStrings("test output", last_output[0..last_output_len])
}

test "utilc: main_output_char sends char" {
    reset_output()
    main_output_char(MU, 'Z')
    try testing.expectEqualStrings("Z", last_output[0..last_output_len])
}

test "utilc: main_output_u64 sends hex" {
    reset_output()
    main_output_u64(MU, 0x1234)
    try testing.expectEqualStrings("0000000000001234", last_output[0..last_output_len])
}

test "utilc: main_output_process sends task info" {
    reset_output()
    var t TaskStruct = undefined
    #memset(std.mem.asBytes(&t), 0)
    t.state = 1
    t.counter = 10
    t.priority = 5
    t.preempt_count = 0
    t.mm.pgd = 0xDEADBEEF

    main_output_process(MU, &t)
    // Just verify it doesn't crash and produces some output
    try testing.expect(last_output_len > 0)
    try testing.expect(std.mem.containsAtLeast(u8, last_output[0..last_output_len], 1, "task address: "))
    try testing.expect(std.mem.containsAtLeast(u8, last_output[0..last_output_len], 1, "pgd: "))
}

test "utilc: memset fills memory correctly" {
    var buf [10]u8 = [_]u8{0} ** 10
    _ = memset(&buf, 'A', 5)
    try testing.expectEqualStrings("AAAAA", buf[0..5])
    try testing.expectEqual(#as(u8, 0), buf[5])
}

test "utilc: memcpy copies memory correctly (aligned)" {
    const src = "Hello, World!"
    var dst [13]u8 align(8) = undefined
    var src_buf [13]u8 align(8) = undefined
    #memcpy(&src_buf, src)

    _ = memcpy(&dst, &src_buf, 13)
    try testing.expectEqualStrings(src, &dst)
}

test "utilc: memcpy copies memory correctly (unaligned)" {
    var src [20]u8 = undefined
    for *p, i in &src { p.* = #intCast(i) }
    var dst [20]u8 = [_]u8{0} ** 20

    // Use unaligned offsets
    _ = memcpy(dst[1..10].ptr, src[5..14].ptr, 9)
    try testing.expectEqualSlices(u8, src[5..14], dst[1..10])
}

test "utilc: memcpy copies 31 bytes (0x1F) correctly" {
    var src [31]u8 align(8) = undefined
    for *p, i in &src { p.* = #intCast(i) }
    var dst [31]u8 align(8) = [_]u8{0} ** 31

    _ = memcpy(&dst, &src, 31)
    try testing.expectEqualSlices(u8, &src, &dst)
}

test "utilc: copy_ke_regs copies regs" {
    var from layout.KeRegs = undefined
    var to layout.KeRegs = undefined
    #memset(std.mem.asBytes(&from), 0xAA)
    #memset(std.mem.asBytes(&to), 0xBB)

    copy_ke_regs(&to, &from)
    try testing.expectEqualSlices(u64, &from.regs, &to.regs)
    try testing.expectEqual(from.sp, to.sp)
    try testing.expectEqual(from.elr, to.elr)
    try testing.expectEqual(from.pstate, to.pstate)
}