Microkernel thing OS experiment (Zig ⚡)
1const std = @import("std"); 2const builtin = @import("builtin"); 3pub const psf2 = @import("psf2.zig"); 4const are_we_le = builtin.cpu.arch.endian() == .little; 5 6const fontdata_embed = @embedFile("fonts/spleen-12x24.psf"); 7const fontdata: [fontdata_embed.len]u8 align(@alignOf(u32)) = fontdata_embed.*; 8 9/// Basic framebuffer container 10pub const Framebuffer = struct { 11 const Self = @This(); 12 address: [*]u32, 13 width: u64, 14 height: u64, 15 pitch: u64, 16 bypp: u16, 17 red_mask_size: u8, 18 red_mask_shift: u8, 19 green_mask_size: u8, 20 green_mask_shift: u8, 21 blue_mask_size: u8, 22 blue_mask_shift: u8, 23}; 24 25/// Framebuffer based console 26pub const Console = struct { 27 fb: Framebuffer, 28 canvas: [*]u8, 29 font: psf2.Font, 30 x_pos: usize = 0, 31 y_pos: usize = 0, 32 x_chrs_max: usize, 33 y_chrs_max: usize, 34 fg_color: u32 = 0xFFFFFFFF, 35 bg_color: u32 = 0, 36 37 /// Create an instance given a framebuffer 38 /// Canvas must be exactly fb.width * fb.height * fb.bypp bytes 39 pub fn init(fb: Framebuffer, canvas: [*]u8) Console { 40 const font = psf2.Font.new(&fontdata); 41 return init_with_font(fb, canvas, font); 42 } 43 44 /// Create an instance given a framebuffer and font 45 pub fn init_with_font(fb: Framebuffer, canvas: [*]u8, font: psf2.Font) Console { 46 const font_hdr = font.getHdr(); 47 return .{ 48 .fb = fb, 49 .font = font, 50 .canvas = canvas, 51 // TODO: implement spacing between chars? 52 .x_chrs_max = fb.width / font_hdr.width, 53 .y_chrs_max = fb.height / font_hdr.height, 54 }; 55 } 56 57 /// Write a string to the console 58 pub fn puts(self: *Console, msg: []const u8) usize { 59 var written: usize = 0; 60 61 const start_line, const num_lines = blk: { 62 const start_line = self.y_pos; 63 var scrolled: bool = false; 64 for (msg) |ch| { 65 // TODO: handle characters that failed to print 66 scrolled |= self.putc(ch) catch false; 67 written += 1; 68 } 69 if (scrolled) break :blk .{ 0, self.y_chrs_max }; 70 break :blk .{ start_line, self.y_pos - start_line + 1 }; 71 }; 72 self.renderCanvas(start_line, num_lines); 73 return written; 74 } 75 76 // Copy in the given lines from the canvas to the framebuffer 77 fn renderCanvas( 78 self: *Console, 79 start_line: usize, 80 num_lines: usize, 81 ) void { 82 const glyph_height: usize = @intCast(self.font.getHdr().height); 83 // Not necessarily fb pitch! 84 const canvas_pitch = self.fb.width * self.fb.bypp; 85 const byte_fb: [*]u8 = @ptrCast(self.fb.address); 86 87 const src_buf = self.canvas[canvas_pitch * glyph_height * start_line ..][0 .. canvas_pitch * glyph_height * num_lines]; 88 const dst_buf = byte_fb[self.fb.pitch * glyph_height * start_line ..][0 .. self.fb.pitch * glyph_height * num_lines]; 89 @memcpy(dst_buf, src_buf); 90 } 91 92 /// Write a character to the console, return true if scrolled 93 /// If putchar failed we did not scroll for sure 94 fn putc(self: *Console, ch: u8) !bool { 95 var scrolled: bool = false; 96 // Handle newlines 97 if (ch == '\r') return scrolled; 98 if (ch == '\n') { 99 // Reset to the beginning of the next line 100 self.x_pos = 0; 101 self.y_pos += 1; 102 // If we've overrun, scroll the entire framebuffer up one 103 // and then reset to the last line 104 if (self.y_pos >= self.y_chrs_max) { 105 self.scrollUp(); 106 scrolled = true; 107 } 108 return scrolled; 109 } 110 // TODO: color palette and escape codes? 111 try self.putchar(ch, self.x_pos, self.y_pos, self.fg_color, self.bg_color); 112 self.x_pos += 1; 113 114 // If our x is too far, go down a line 115 if (self.x_pos < self.x_chrs_max) return scrolled; 116 self.x_pos = 0; 117 self.y_pos += 1; 118 if (self.y_pos >= self.y_chrs_max) { 119 self.scrollUp(); 120 scrolled = true; 121 } 122 return scrolled; 123 } 124 125 fn putchar(self: *const Console, ch: u8, x_pos: usize, y_pos: usize, fg_val: u32, bg_val: u32) !void { 126 const raw_color_choice: [2]u32 = [2]u32{ 127 if (are_we_le) bg_val else @byteSwap(bg_val), 128 if (are_we_le) fg_val else @byteSwap(fg_val), 129 }; 130 131 const font = self.font; 132 const hdr = font.getHdr(); 133 134 const bytes_per_line = hdr.bytesPerLine(); 135 const mask_shamt: u5 = @truncate(bytes_per_line * 8 - 1); 136 const mask_initial: u32 = @as(u32, 1) << mask_shamt; 137 const glyph = try font.getGlyph(ch); 138 139 // Offset into framebuffer of the beginning of the character 140 const canvas_pitch: usize = self.fb.width * self.fb.bypp; 141 var offset: usize = (y_pos * @as(usize, hdr.height) * canvas_pitch) + (x_pos * @as(usize, hdr.width) * self.fb.bypp); 142 // run for every line 143 var glyph_y: u32 = 0; 144 var mask: u32 = 0; 145 while (glyph_y < hdr.height) : (glyph_y += 1) { 146 // initialize the mask and current line 147 mask = mask_initial; 148 // TODO: endian 149 const line_value: u32 = std.mem.readVarInt(u32, glyph[glyph_y * bytes_per_line ..][0..bytes_per_line], .big); 150 // offset into the fb of the current line 151 var line_offset: usize = offset; 152 var glyph_x: u32 = 0; 153 while (glyph_x < hdr.width) : (glyph_x += 1) { 154 // Write the fb or bg color 155 const color: [4]u8 = @bitCast(raw_color_choice[@intFromBool(line_value & mask != 0)]); 156 @memcpy(self.canvas[line_offset..][0..self.fb.bypp], color[0..]); 157 // Move right a pixel 158 line_offset += self.fb.bypp; 159 mask >>= 1; 160 } 161 // Move down a line 162 offset += canvas_pitch; 163 } 164 } 165 166 // Set the fg and bg color 167 pub fn setColor(self: *Console, new_fg: u32, new_bg: u32) void { 168 self.fg_color = self.convertColor(new_fg); 169 self.bg_color = self.convertColor(new_bg); 170 } 171 172 // Convert a normal _RGB u32 to the actual framebuffer format 173 fn convertColor(self: *const Console, color: u32) u32 { 174 // The color value also needs to be scaled. For example, 175 // if it's 10 bits per color and we're starting from 8 bits, 176 // Full bright will only be 0xFF/0x3FF or about 25% brightness. 177 // To fix this hypothetical, shift left by (10 - 8). This isn't 178 // perfectly accurate but close enough. 179 const red_left_shift: u5 = @truncate(self.fb.red_mask_size); 180 const green_left_shift: u5 = @truncate(self.fb.red_mask_size); 181 const blue_left_shift: u5 = @truncate(self.fb.red_mask_size); 182 183 // Get our source RGB 888 184 const right_shift = 8; 185 const red_src: u32 = (color >> 16) & 0xFF; 186 const green_src: u32 = (color >> 8) & 0xFF; 187 const blue_src: u32 = color & 0xFF; 188 189 // These shifts are the offsets to place each color. 190 const red_dest_shift: u5 = @truncate(self.fb.red_mask_shift); 191 const green_dest_shift: u5 = @truncate(self.fb.green_mask_shift); 192 const blue_dest_shift: u5 = @truncate(self.fb.blue_mask_shift); 193 194 // Do the calculations 195 const red_dst = switch (std.math.order(red_left_shift, right_shift)) { 196 .gt => red_src << (red_left_shift - right_shift), 197 .lt => red_src >> (right_shift - red_left_shift), 198 .eq => red_src, 199 } << red_dest_shift; 200 201 const green_dst = switch (std.math.order(green_left_shift, right_shift)) { 202 .gt => green_src << (green_left_shift - right_shift), 203 .lt => green_src >> (right_shift - green_left_shift), 204 .eq => green_src, 205 } << green_dest_shift; 206 207 const blue_dst = switch (std.math.order(blue_left_shift, right_shift)) { 208 .gt => blue_src << (blue_left_shift - right_shift), 209 .lt => blue_src >> (right_shift - blue_left_shift), 210 .eq => blue_src, 211 } << blue_dest_shift; 212 213 return red_dst | green_dst | blue_dst; 214 } 215 216 // Make sure to set damage to the entire screen! 217 fn scrollUp(self: *Console) void { 218 const glyph_height: usize = @intCast(self.font.getHdr().height); 219 const canvas_pitch: usize = self.fb.width * self.fb.bypp; 220 221 // Copy 1:n line up to 0:n-1 with memmove 222 const dst_buf = self.canvas[0 .. canvas_pitch * glyph_height * (self.y_chrs_max - 1)]; 223 const src_buf = self.canvas[canvas_pitch * glyph_height ..][0 .. canvas_pitch * glyph_height * (self.y_chrs_max - 1)]; 224 @memmove(dst_buf, src_buf); 225 226 // Clear last line 227 const last_line = self.canvas[canvas_pitch * glyph_height * (self.y_chrs_max - 1) ..][0 .. canvas_pitch * glyph_height]; 228 @memset(last_line, 0); 229 self.x_pos = 0; 230 self.y_pos = self.y_chrs_max - 1; 231 } 232 233 // Get a writer with optional buffering 234 pub fn writer(self: *Console, buffer: []u8) Writer { 235 return Writer.init(self, buffer); 236 } 237 238 // Writer with the new std.Io.Writer interface 239 pub const Writer = struct { 240 console: *Console, 241 interface: std.Io.Writer, 242 243 pub fn init(console: *Console, buffer: []u8) Writer { 244 return .{ 245 .console = console, 246 .interface = .{ 247 .buffer = buffer, 248 .vtable = &.{ .drain = Writer.drain }, 249 }, 250 }; 251 } 252 253 pub fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) !usize { 254 // fieldParentPtr is so cool 255 const self: *Writer = @fieldParentPtr("interface", w); 256 var written: usize = 0; 257 // First, consume the buffer 258 if (w.end < w.buffer.len) { 259 const n = self.console.puts(w.buffer[w.end..]); 260 written += n; 261 w.end += n; 262 } 263 264 // Iterate over all the provided slices 265 for (data, 0..) |slice, i| { 266 // If we are the last slice, splat by the amount 267 if (i == data.len - 1) { 268 for (0..splat) |_| { 269 written += self.console.puts(slice); 270 } 271 break; 272 } 273 written += self.console.puts(slice); 274 } 275 return written; 276 } 277 }; 278};