From 827f8b90d17ab826feb0b82c6b912f47003908e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Gr=C3=A9goire?= Date: Fri, 26 Jun 2026 23:41:22 -0400 Subject: [PATCH] all: add console and logging machinery --- src/dev/fbconsole.zig | 193 ++++++++++++++++++++++++++++++++++++++++ src/dev/sun12x22.psfu | Bin 0 -> 12087 bytes src/ex/console.zig | 111 +++++++++++++++++++++++ src/ex/init.zig | 17 ++-- src/ex/private.zig | 1 + src/ex/root.zig | 5 ++ src/kern/init.zig | 1 + src/kern/log.zig | 22 ++++- src/kern/root.zig | 2 + src/platform/pc/smp.zig | 2 + 10 files changed, 344 insertions(+), 10 deletions(-) create mode 100644 src/dev/fbconsole.zig create mode 100644 src/dev/sun12x22.psfu create mode 100644 src/ex/console.zig diff --git a/src/dev/fbconsole.zig b/src/dev/fbconsole.zig new file mode 100644 index 0000000..b6f0910 --- /dev/null +++ b/src/dev/fbconsole.zig @@ -0,0 +1,193 @@ +const r = @import("root"); +const std = @import("std"); +const pl = r.pl; +const ke = r.ke; +const ex = r.ex; + +const PSF1_MAGIC: u16 = 0x0436; +const PSF2_MAGIC: u32 = 0x864ab572; + +const Psf1Header = extern struct { + magic: u16, + mode: u8, + glyph_size: u8, +}; + +const Psf2Header = extern struct { + magic: u32, + version: u32, + header_size: u32, + flags: u32, + glyph_count: u32, + glyph_size: u32, + height: u32, + width: u32, +}; + +const FontInfo = struct { + header_size: usize, + glyph_size: usize, + width: usize, + height: usize, +}; + +const font_file = @embedFile("sun12x22.psfu"); +var font: FontInfo = undefined; +var framebuffer: [*]u32 = undefined; +var framebuffer_width: usize = 0; +var framebuffer_height: usize = 0; + +const bg_color: u32 = 0xFFFFFFFF; +const text_color: u32 = 0xFF000000; +const window_padding: usize = 10; + +var content_x: usize = 0; +var content_y: usize = 0; +var content_w: usize = 0; +var content_h: usize = 0; +var visible_h: usize = 0; + +fn load_font() void { + if (font_file.len >= @sizeOf(Psf2Header)) { + const h2: *const Psf2Header = @ptrCast(@alignCast(font_file.ptr)); + if (h2.magic == PSF2_MAGIC) { + font = .{ + .header_size = h2.header_size, + .glyph_size = h2.glyph_size, + .width = h2.width, + .height = h2.height, + }; + return; + } + } + if (font_file.len >= @sizeOf(Psf1Header)) { + const h1: *const Psf1Header = @ptrCast(@alignCast(font_file.ptr)); + if (h1.magic == PSF1_MAGIC) { + font = .{ + .header_size = @sizeOf(Psf1Header), + .glyph_size = h1.glyph_size, + .width = 8, + .height = h1.glyph_size, + }; + return; + } + } + font = .{ .header_size = 0, .glyph_size = 0, .width = 0, .height = 0 }; +} + +fn plot_character(x: usize, y: usize, c: u8) void { + const bytes_per_row = font.glyph_size / font.height; + const glyph_offset = font.header_size + (@as(usize, c) * font.glyph_size); + const glyph_data = font_file[glyph_offset..(glyph_offset + font.glyph_size)]; + for (0..font.height) |row| { + var row_data: u32 = 0; + for (0..bytes_per_row) |b| { + row_data = (row_data << 8) | @as(u32, glyph_data[row * bytes_per_row + b]); + } + const shift_base = bytes_per_row * 8; + for (0..font.width) |col| { + if ((row_data & (@as(u32, 1) << @intCast(shift_base - 1 - col))) != 0) { + framebuffer[(y + row) * framebuffer_width + (x + col)] = text_color; + } + } + } +} + +var cursor_x: usize = 0; +var cursor_y: usize = 0; + +fn clear_content_rows(y: usize, height: usize) void { + for (0..height) |row| { + const dst_y = content_y + y + row; + if (dst_y >= content_y + visible_h) break; + + for (0..content_w) |col| { + framebuffer[dst_y * framebuffer_width + content_x + col] = bg_color; + } + } +} + +fn scroll_up(pixels: usize) void { + if (pixels >= visible_h) { + clear_content_rows(0, visible_h); + return; + } + + const remaining_h = visible_h - pixels; + for (0..remaining_h) |row| { + const dst_y = content_y + row; + const src_y = dst_y + pixels; + + for (0..content_w) |col| { + framebuffer[dst_y * framebuffer_width + content_x + col] = + framebuffer[src_y * framebuffer_width + content_x + col]; + } + } + + clear_content_rows(remaining_h, pixels); +} + +fn ensure_cursor_visible() void { + while (cursor_y + font.height > visible_h) { + scroll_up(font.height); + cursor_y -= font.height; + } +} + +fn newline() void { + cursor_x = 0; + cursor_y += font.height; + + while (cursor_y > visible_h) { + scroll_up(font.height); + cursor_y -= font.height; + } +} + +fn write_char(c: u8) void { + if (font.width == 0 or font.height == 0 or content_w < font.width or visible_h < font.height) { + return; + } + + if (c == '\n') { + newline(); + } else if (c == '\r') { + cursor_x = 0; + } else { + if (cursor_x + font.width > content_w) { + newline(); + } + + ensure_cursor_visible(); + plot_character(content_x + cursor_x, content_y + cursor_y, c); + cursor_x += font.width; + } +} + +fn write(_: ?*anyopaque, s: []const u8) void { + for (s) |c| { + write_char(c); + } +} + +var console: ex.Console = .{ .ctx = null, .link = undefined, .write = write }; + +pub fn init(boot_info: *pl.BootInfo) void { + framebuffer = @ptrFromInt(boot_info.framebuffer.?.address); + framebuffer_width = boot_info.framebuffer.?.width; + framebuffer_height = boot_info.framebuffer.?.height; + + load_font(); + + content_x = window_padding; + content_y = window_padding; + content_w = framebuffer_width - (window_padding * 2); + content_h = framebuffer_height - (window_padding * 2); + visible_h = if (font.height == 0) 0 else content_h - (content_h % font.height); + + for (0..framebuffer_width * framebuffer_height) |i| { + framebuffer[i] = bg_color; + } + + ex.console.register(&console); +} diff --git a/src/dev/sun12x22.psfu b/src/dev/sun12x22.psfu new file mode 100644 index 0000000000000000000000000000000000000000..937a96cd93f7ed3b80dd4f2828b1e79e53fe1226 GIT binary patch literal 12087 zcmbVSYmi(;74BuiP6C_E##C9eu-6c5|<7!9yJ241Uf<@=?SgE`Nwn)4sgczy5&quK`GZ>wzu4FKarF zR87~-EuEVh&CYFrW~b5E(W#q!T<$-j>bhy_x~^-grYfQ$26M=sAj+bmDypWLKL2v5 zEKbP56{sx(P7qbS-mEvLXxlrVhe3?UBQqCGvsw>ge6FeMUeeqWV;AvD9SiSuIGBcM zXkYuBa{0Y(tHp^&uyjY%hytqSw09j`x^Fh#Y#jBE`kdy|t(b}#FeN!C$HW9+T1_iX z$8xlQ$B)P6^=Dv-aGDJjGbD#3enH2Db>ddBL-?rSjk6iwCCkg`UKGIQHNdtgz!ze~glQ4@7wu0XD->#BxyiGw4=xXJ@Y;`$7-sCCxq zTO(MfNA*w&MokZHM8U^8)}mZZ45$In90wmCB(OF&Q`C#+<>K)eE-tS_0$CoCmLylS zYH{DCqADgu(-1hEM1E=9kdqGZ{55#&G|hlUJ_jeiir26X&B$+E292KrT>{-HJ))N7 zqeZn#q7`AiV9j9NXzC(#ZetwA z=6piT0NgMr*c#55895_1AYayla!}4lo@cLpnADf*3)Qu1ttdr24vFRZ6nTm_;f-sT zE?e}fW~it!P~I*!%V{&Mr&ZmINf&oWi~)GgL(rFDw=q*1Q1QrV`^0@q&?OhS3VODs zgw`11aa`QE#1?W!ZZjjGHKMj*UD$pW1*XNoyDZKZ;kxzx&6(-C{f=L8j$eysgx2AOfJ}fWH2TR-c zdgsWVfM)^;)=XJfbY$~oAZ5Ti&kdLp;6%VOz)HaB&@fmN%r>hJ70?=netF;p`f5NQ z0J_rGHRaMRTkfLu*`l`Uh5;HvBZmh*rDTAkMhhi)TrnU@SRG!UtbM46mCCMcRV_#R z$*FQy&mak`Lo}>xn7YKik#_aUVx7xws+87&bUuZW%BC@Fo+X*P59NR^i&31a4g8kH zfCe|m39($`v4GPIsjAq%W(om){QB z;?Oj4j?+%hn$=X+F2v>ReiM*!+?Jm%fo8=1XU9yd&0HY=>7=`^yF zW3M#_a@Wi>Bs=qft0co^@^ZihfPeSQO*f?1@OF4tdY5_^c^7+|yp8(;1CS>;u*wpx3B0=Q(oAq3Z{eK@Iz;CeFNjz$~x;{hem5Wq^l# z{57i?>>dyEgPj=@Wre+tc?D9!yG|(DT+>|S-V*bGHNuI;_SafqmJa0BBV&tOUBFG|*(2?Us*ol6{%Pn~Bagj0W?P3hb4}UXGh`iyQbBa4PN~ zQoHpZro^|`xF4T$NR;9Rd(Ug6~VO_%g$)1fnhH1I_45fm1^F&v3qn})j=6vxbbA*1s?n3j5v(Z8j(({!I|jzXnLn%jC5d0aIoHW zqP^_(M-Ay8)+dA#ry1}t(jjB1+kYZZ173RBkY){@XOd|=sBPU$s@X+n}hQ7hz zMv{WJ3uSSR+PUaD#QdZ^D)!}gPDSBub)%Msr5uHz0jn8n(t^5oJ6F7wY2A^Nre>9u zJ7kb=R_EzSeSEtf^MtoK4O`WOlvb;jHE;jYlmTh0oDR1t#0n!4zK@d(5bRGIr4Ph$*fd@51#`z+vS1h zycC+3RSfJ&vr(5+8I(uGq}RYMO(=1$q!Jw*f>&Yfs;z?`>VyRI_&M%0DiR#rMzyts zwrWnBT>1s>O`vr9FEJ;y3@^!nn9J^mzCAzCFu? zwV|d~>$5e#N-}H(?ReR@xvJRUHO;h6V+z4c#@|?&x{cNX zyO`Nq)`VE2_zTK}KW=tpsKTo^cu$Quw!e5`(&_R)a1Qz10T%3}TFj@#=R3vv-p_tZ zaQo4-nC{~GFdEeLnTlCo+>f%j@xAwI?{dEN?%P;!=jAUP1F8(Y;O`p~>NG7x_EnI_ z4F^uUFNka>f+=WDa;`-v-JiO~TM*-+vx18a-U|a4moSD zN`B+#*Zm2s?L+=jZ(4b(pLBcU3~!Hml_2cqIGcT zS}gA2aF)+|#BUJ~d31|-aL2Oz7V+T9%~I@-t{tTaw}@YNJX>;cZHei+^C&JA$=Dq$ z(X+hq+`0tUrBTc;^H9v8+fp<)cPz#ljP%T{FD`u$8&$O5c+`HuU9&`PdHw$<`iD}d zf5;mvkFRW9MXoZZYx|lhbwj~JlRY@_SFG1JuGYXyj9;b3)%Gl^;Q#iBzKTS?ob~A9 z-yY;{1%G?wn@Jj6T*kniLB?^NHz8PI=w_c_90)T!xQ{ z|MrUB%}ThR#b2q9|DMY1PwB0g=VAY_KO?uwDczltn`rwLWvzp|KDq(&y4euFuJM~^ zRn*YRIbIijd2H zy8}@nK7;ryx*eqXxtHhXKw+<%-@A+Ecel{%Ahlnn_LFFP4mD39-h+4_;=PD>)BK)? zk09(| zx{|J<@6dPYYWg01pMLQ6zFj0~ny#VE^h3IqW@rojh<;2zp`X%qbUocbTd6@e(oJ+T z{fvH2+h{xOpq+FJ{eo_#+vs+hzv~VPe?#Fx3ZJI%Q3@ZYa36(FQ1~Q;&rtX*g-=oV zXA1vB;d2!3qVU)AyHI&2h5ISoP2r0azD(f@6uwH~-zhvq;T(kzQu|SAKSpf`=8h-n z@Vyj1Lg61N{0oJzQ20EB_fY#8YQIA5Ltx=$bEDarN`*#uc-afk%M$}7aiFj-cLvH_w@X({d5FO?sWw$3jP(vJ=8t`qz9;t(jH9Wmxrj$+PLrSL$4A2 EA1a-r-T(jq literal 0 HcmV?d00001 diff --git a/src/ex/console.zig b/src/ex/console.zig new file mode 100644 index 0000000..f057578 --- /dev/null +++ b/src/ex/console.zig @@ -0,0 +1,111 @@ +//! Logging (for now) consoles. +const std = @import("std"); +const r = @import("root"); +const rtl = @import("rtl"); +const ke = r.ke; +const ps = r.ps; +const ex = r.ex; + +pub const Console = struct { + link: rtl.List.Entry, + write: *const fn (ctx: ?*anyopaque, slice: []const u8) void, + ctx: ?*anyopaque, +}; + +const ConWriter = struct { + interface: std.Io.Writer, + console: *Console, + + pub fn init(console: *Console) ConWriter { + return .{ + .interface = .{ + .vtable = &.{ .drain = drain }, + .buffer = &.{}, + .end = 0, + }, + .console = console, + }; + } + + fn drain(w: *std.Io.Writer, data: []const []const u8, _: usize) std.Io.Writer.Error!usize { + const self: *ConWriter = @fieldParentPtr("interface", w); + var total_written: usize = 0; + for (data) |slice| { + self.console.write(self.console.ctx, slice); + total_written += slice.len; + } + + return total_written; + } +}; + +var console_list: rtl.List = undefined; +var console_lock: ke.Mutex = .init(); +var last_seq: usize = 0; + +fn write_on(buf: []u8, rec: anytype, console: *Console) void { + var writer = ConWriter.init(console); + writer.interface.writeAll(buf[0..rec.length]) catch unreachable; +} + +fn drain_from_on(seq: usize, console: *Console) usize { + var cur_seq: usize = seq; + var buf: [1024]u8 = undefined; + while (true) { + const rec = ke.log.ringbuffer.read(cur_seq, &buf) catch break; + + write_on(&buf, rec, console); + + cur_seq += 1; + } + + return cur_seq; +} + +fn drain_from_all(seq: usize) usize { + var cur_seq: usize = seq; + var buf: [1024]u8 = undefined; + while (true) { + const rec = ke.log.ringbuffer.read(cur_seq, &buf) catch break; + + console_lock.acquire(); + + var it = console_list.iterator(); + + while (it.next()) : (it.advance()) { + const console: *Console = @fieldParentPtr("link", it.get()); + write_on(&buf, rec, console); + } + + console_lock.release(); + + cur_seq += 1; + } + + return cur_seq; +} + +fn worker(_: ?*anyopaque) void { + while (true) { + _ = ke.wait.wait_one(&ke.log.event.hdr, null) catch unreachable; + ke.log.event.reset(); + + last_seq = drain_from_all(last_seq); + } +} + +pub fn register(cons: *Console) void { + // Drain all logs until now. + _ = drain_from_on(0, cons); + + console_lock.acquire(); + console_list.insert_tail(&cons.link); + console_lock.release(); +} + +pub fn init() void { + console_list.init(); + + const td = ps.thread.create_kernel(ke.Thread.Priority.default, worker, null) catch @panic("handle me"); + ke.sched.enqueue(&td.kern); +} diff --git a/src/ex/init.zig b/src/ex/init.zig index d55ef9c..771f49f 100644 --- a/src/ex/init.zig +++ b/src/ex/init.zig @@ -5,8 +5,10 @@ const ke = r.ke; const mm = r.mm; const ex = r.ex; const ps = r.ps; -const amd64 = @import("arch"); -const turnstile = ke.private.turnstile; +const exp = ex.private; + +// XXX +const fbconsole = @import("../dev/fbconsole.zig"); pub fn init(boot_info: *pl.BootInfo) void { mm.init(boot_info); @@ -15,14 +17,15 @@ pub fn init(boot_info: *pl.BootInfo) void { pl.late_init(boot_info); mm.late_init(); ke.sched.late_init(); - ex.private.workqueue.init() catch @panic("e"); + exp.workqueue.init() catch @panic("e"); + exp.console.init(); if (boot_info.framebuffer != null) { - const t = ps.thread.create_kernel(ke.Thread.Priority.default, ex.fireworks.start, boot_info) catch @panic("oom"); - ke.sched.enqueue(&t.kern); - } + fbconsole.init(boot_info); - while (true) {} + // const t = ps.thread.create_kernel(ke.Thread.Priority.default, ex.fireworks.start, boot_info) catch @panic("oom"); + // ke.sched.enqueue(&t.kern); + } while (true) { std.atomic.spinLoopHint(); diff --git a/src/ex/private.zig b/src/ex/private.zig index b3406fd..512279e 100644 --- a/src/ex/private.zig +++ b/src/ex/private.zig @@ -1,3 +1,4 @@ pub const init_mod = @import("init.zig"); pub const workqueue = @import("workqueue.zig"); pub const init = init_mod.init; +pub const console = @import("console.zig"); diff --git a/src/ex/root.zig b/src/ex/root.zig index d372565..38a466e 100644 --- a/src/ex/root.zig +++ b/src/ex/root.zig @@ -4,7 +4,12 @@ pub const private = @import("private.zig"); const p = private; pub const WorkItem = p.workqueue.WorkItem; +pub const Console = p.console.Console; pub const workqueue = struct { pub const enqueue = p.workqueue.enqueue; }; + +pub const console = struct { + pub const register = p.console.register; +}; diff --git a/src/kern/init.zig b/src/kern/init.zig index b67c620..3c012cf 100644 --- a/src/kern/init.zig +++ b/src/kern/init.zig @@ -13,6 +13,7 @@ pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void { ki.impl.early_init(); std.log.info("Welcome to the machine", .{}); pl.early_init(boot_info); + ki.log.init(); ki.cpu.init_cpu(0); ki.turnstile.init_turnstiles(); ki.shootdown.init(); diff --git a/src/kern/log.zig b/src/kern/log.zig index f502f85..258fbaf 100644 --- a/src/kern/log.zig +++ b/src/kern/log.zig @@ -39,6 +39,22 @@ const avg_msg_size_bits = 5; pub var ringbuffer = ki.log_ring.RingBuffer(config.log_buffer_shift, avg_msg_size_bits).init(); +// Initialize manually because we need this ASAP. +pub var event: ke.Event = .{ + .hdr = .{ + .lock = .init(), + .signaled = 0, + .type = .Notification, + .waitblocks = .{ + .head = .{ .next = &event.hdr.waitblocks.head, .prev = &event.hdr.waitblocks.head }, + }, + }, +}; + +pub fn init() void { + event.init(.Notification); +} + pub fn log( comptime level: std.log.Level, comptime scope: @EnumLiteral(), @@ -63,9 +79,9 @@ pub fn log( ringbuffer.publish(res); - // TODO: for now, we print to the platform's debug output immediately. - // In the future, we should instead set an event that would get triggered in Ex, - // which would consume the ringbuffer from multiple consoles in a separate thread. + // Signal whomever is waiting on logs to get published. + event.signal(); + var writer = DebugWriter.init(); const ipl = out_lock.acquire_at(.High); diff --git a/src/kern/root.zig b/src/kern/root.zig index 538daa1..1663c96 100644 --- a/src/kern/root.zig +++ b/src/kern/root.zig @@ -68,6 +68,8 @@ pub const wait = struct { pub const log = struct { pub const register_console = p.log.register_console; pub const log = p.log.log; + pub const ringbuffer = &p.log.ringbuffer; + pub const event = &p.log.event; }; pub const cpu = struct { diff --git a/src/platform/pc/smp.zig b/src/platform/pc/smp.zig index 509da8b..18a03bf 100644 --- a/src/platform/pc/smp.zig +++ b/src/platform/pc/smp.zig @@ -80,6 +80,8 @@ pub fn init() linksection(r.init) void { ki.impl.cpu_offsets = @ptrCast(mm.zone.gpa.alloc(usize, apic.apics.items.len + 1) catch @panic("Failed to allocate AP local data offsets")); const percpu_size = @intFromPtr(&__percpu_end) - @intFromPtr(&__percpu_start); + log.info("per-CPU data size: {} bytes", .{percpu_size}); + ki.impl.cpu_offsets[0] = 0; cpu_id_to_apic_id[0] = apic.get_id();