// 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) }