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 0000000..937a96c Binary files /dev/null and b/src/dev/sun12x22.psfu differ 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();