const std = @import("std"); const Cmdline = @import("cmdline.zig").Cmdline; const io = @import("arch").io; const PREDEFINED_PORTS: [2]u16 = .{ 0x3F8, // ttyS0 0x2F8, // ttyS1 }; var serial_writer: ?SerialWriter = null; // Parse earlyprintk argument from cmdline // Format: earlyprintk=serial,port,baudrate, port can either be ioport address (e.g., 0x3F8) or // a predefined name (e.g., ttyS0/ttyS1). fn parseEarlyprintk(cmdline: *const Cmdline) ?struct { port: u16, baudrate: u32 } { var buf = [_]u8{0} ** 32; const len = cmdline.parseArg("earlyprintk", &buf) orelse 0; if (len == 0) return null; const arg = buf[0..len]; var it = std.mem.splitAny(u8, arg, ","); if(std.mem.eql(u8, it.next() orelse "", "serial") == false) { return null; } const port_str = it.next() orelse return null; var port: u16 = undefined; if (port_str.len == 5 and std.mem.startsWith(u8, port_str, "ttyS")) { const port_idx = std.fmt.parseInt(u8, port_str[4..5], 10) catch return null; if (port_idx >= PREDEFINED_PORTS.len) { return null; } port = PREDEFINED_PORTS[port_idx]; } else if (std.fmt.parseInt(u16, port_str, 16) catch null) |port_val| { port = port_val; } else { return null; } const baudrate_str = it.next() orelse return null; const baudrate = std.fmt.parseInt(u32, baudrate_str, 10) catch return null; return .{ .port = port, .baudrate = baudrate }; } fn serialInit(port: u16, baudrate: u32) void { const divisor: u16 = @intCast(115200 / baudrate); // Disable interrupts io.outb(port + 1, 0x00); // Disable FIFO io.outb(port + 2, 0x00); // Set 8 data bits, 1 stop bit, no parity io.outb(port + 3, 0x80); const c = io.inb(port + 3); // Enable DLAB io.outb(port + 3, c | 0x80); // Set divisor low byte io.outb(port + 0, @intCast(divisor & 0x00FF)); // Set divisor high byte io.outb(port + 1, @intCast((divisor >> 8) & 0x00FF)); io.outb(port + 3, c & ~@as(u8, 0x80)); } pub fn earlyConsoleInit(cmdline: *const Cmdline) void { const result = parseEarlyprintk(cmdline) orelse return; serialInit(result.port, result.baudrate); serial_writer = SerialWriter.init(result.port); } const SerialWriter = struct { port: u16, interface: std.Io.Writer, var WRITER_VTABLE: std.Io.Writer.VTable = undefined; pub fn init(port: u16) SerialWriter { WRITER_VTABLE = .{ .drain = SerialWriter.drain, }; var sw = SerialWriter{ .port = port, .interface = undefined, }; sw.interface = .{ .buffer = &[0]u8{}, .vtable = @call(.never_inline, getWriterVTable, .{}), }; return sw; } pub fn writer(self: *SerialWriter) *std.Io.Writer { return &self.interface; } fn getWriterVTable() *const std.Io.Writer.VTable { return &WRITER_VTABLE; } // fn getWriterDrain() *const @TypeOf(SerialWriter.drain) { // return SerialWriter.drain; // } fn drain(self: *std.Io.Writer, buf: []const []const u8, splat: usize) error{}!usize { const sw: *SerialWriter = @fieldParentPtr("interface", self); var written: usize = 0; for (buf, 0..buf.len) |part, i| { const repeat = if (i == buf.len - 1) splat else 1; for (0..repeat) |_| { for (part) |c| { SerialWriter.putchar(sw, c); written += 1; } } } return written; } fn putchar(self: *const SerialWriter, c: u8) void { var timeout: usize = 0xffff; while(io.inb(self.port + 5) & 0x20 == 0 and timeout > 0) { timeout -= 1; asm volatile ("pause" ::: .{ .memory = true } ); } io.outb(self.port, c); } }; pub fn dprint(comptime fmt: []const u8, args: anytype) void { var sw = serial_writer orelse return; _ = sw.writer().print(fmt, args) catch {}; }