// Drawing and the text console, on whatever display is bound. See include/gfx.h // for why this is not a driver and does not name one. // // It was kernel/dev/vga.c, and the name was the problem: panic.c called // gfx_fill_rect, console.c called gfx_putc, /dev/fb0 called gfx_fb_phys. None // of this code is VGA-specific -- it is bitmap drawing over a linear // framebuffer -- and the one part that genuinely was is now the driver, in // kernel/drivers/gpu/vga/. // // The framebuffer arrives through gfx_attach_scanout() rather than being read // out of the boot block here. That is the whole of the inversion: a driver // publishes what it owns, and this file draws on it without knowing what it is. #include "types.h" #include "x86.h" #include "memlayout.h" #include "defs.h" #include "theme.h" #include "font.h" #include "logo.h" #include "device.h" #include "display.h" #include "gfx.h" #include "vt.h" #include "text.h" #include "ttf.h" #define BACKSPACE 0x100 #define COLOR_BG SCH_BG // The cell size, and why it is not a constant any more. // // It was 8x16, the size of the built-in bitmap font, and everything in this // file multiplied by it. That font is CP437: 256 glyphs, no Ą, no Ę, no Ś, no // Ω, no ∞. Those did not come out wrong, they came out as `?`, because the // grid could not hold a codepoint above 255 and mapped what it could not keep. // // So the console draws from a real typeface now, and a real typeface decides // its own metrics. These start at the bitmap's size because the console has to // work from the first panic onwards -- before gfx_font_init() has run there is // no font, and 8x16 against font_data is exactly what there always was. static int cell_w = 8; static int cell_h = 16; #define CHAR_WIDTH cell_w #define CHAR_HEIGHT cell_h // The typeface, once there is one. `cfont` is the switch: null means fall back // to the bitmap, which is what early boot and any font failure get. static struct ttf consttf; static struct font *cfont; static int cfont_baseline; static uint32 blend(uint32 bg, uint32 fg, uint a); // The scanout, as published by whichever driver owns the display. Zeroed until // one does, and every entry point checks `fb` before touching memory -- a // kernel that panics before any display driver has run still has to be able to // call in here and do nothing gracefully. static struct fb_info scanout; static volatile uchar *fb; static int cursor_x = 0; static int cursor_y = 0; // Current fill color for glyph backgrounds, backspace erase, and scroll -- // normally COLOR_BG, but panic() switches this to paint the whole console a // solid color (e.g. PSOGM's purple) instead of leaving cream-colored boxes // around each character. static uint32 gfx_bg = COLOR_BG; void gfx_attach_scanout(const struct fb_info *info) { scanout = *info; fb = info->ptr; // Tell the terminals how big the screen actually is. They were sized before // this driver existed -- the PCI bus is walked long after the terminals are // set up -- so until this call every grid is the 80x24 fallback. See // vt_resize(). vt_resize(); } const struct fb_info * gfx_scanout(void) { return fb ? &scanout : 0; } void gfx_set_bg(uint32 color) { gfx_bg = color; } // The mode that was actually set. Exported because the resolution is chosen at // boot now (press R -- see boot/bios/stage2.S), so nothing above may assume it. uint32 gfx_width(void) { return scanout.width; } uint32 gfx_height(void) { return scanout.height; } void gfx_draw_pixel(int x, int y, uint32 color) { if (!fb || x < 0 || x >= scanout.width || y < 0 || y >= scanout.height) return; uint32 offset = (y * scanout.stride) + (x * (scanout.bpp / 8)); if (scanout.bpp == 32) { *(volatile uint32*)(fb + offset) = color; } else if (scanout.bpp == 24) { fb[offset] = color & 0xFF; fb[offset + 1] = (color >> 8) & 0xFF; fb[offset + 2] = (color >> 16) & 0xFF; } else if (scanout.bpp == 16) { uint16 c16 = (((color >> 19) & 0x1F) << 11) | (((color >> 10) & 0x3F) << 5) | ((color >> 3) & 0x1F); *(volatile uint16*)(fb + offset) = c16; } } void gfx_clear(uint32 color) { for (int y = 0; y < scanout.height; y++) { for (int x = 0; x < scanout.width; x++) { gfx_draw_pixel(x, y, color); } } } void gfx_draw_char(int x, int y, uchar c, uint32 color) { // No `c > 127` rejection any more: font_data holds 256 Latin-1 glyphs, and // a uchar cannot index past them. if (!fb) return; for (int row = 0; row < CHAR_HEIGHT; row++) { uchar row_data = font_data[c][row]; for (int col = 0; col < CHAR_WIDTH; col++) { if (row_data & (0x80 >> col)) { gfx_draw_pixel(x + col, y + row, color); } else { gfx_draw_pixel(x + col, y + row, gfx_bg); } } } } void gfx_puts(int x, int y, char *s, uint32 color) { while (*s) { gfx_putc((uchar)*s, color); s++; } } void gfx_putc(int c, uint32 color) { if (!fb) return; if (c == BACKSPACE || c == 0x08 || c == '\b' || c == 127) { if (cursor_x >= CHAR_WIDTH) { cursor_x -= CHAR_WIDTH; for (int i = 0; i < CHAR_HEIGHT; i++) { for (int j = 0; j < CHAR_WIDTH; j++) { gfx_draw_pixel(cursor_x + j, cursor_y + i, gfx_bg); } } } return; } if (c == '\n') { cursor_x = 0; cursor_y += CHAR_HEIGHT; } else if (c == '\r') { cursor_x = 0; } else if (c == '\t') { // Advance to the next multiple of eight columns, erasing as it goes so a // tab overwrites rather than skipping over stale characters. // // There was no tab case at all, so '\t' fell through to gfx_draw_char and // was drawn as glyph 9 -- which in CP437 is a hollow circle. Pressing Tab // printed a circle. Same for every other control character: the font has // a picture for each of them, and none of those pictures is what the // character means. int col = cursor_x / CHAR_WIDTH; int stop = (col + 8) & ~7; while (col < stop && cursor_x < (int)scanout.width) { for (int i = 0; i < CHAR_HEIGHT; i++) for (int j = 0; j < CHAR_WIDTH; j++) gfx_draw_pixel(cursor_x + j, cursor_y + i, gfx_bg); cursor_x += CHAR_WIDTH; col++; } } else if (c < 32) { return; // any other control character: not a glyph } else { gfx_draw_char(cursor_x, cursor_y, (uchar)c, color); cursor_x += CHAR_WIDTH; } if (cursor_x >= scanout.width) { cursor_x = 0; cursor_y += CHAR_HEIGHT; } if (cursor_y > (scanout.height - CHAR_HEIGHT)) { uint32 line_size = scanout.stride * CHAR_HEIGHT; uint32 total_size = scanout.stride * scanout.height; uint32 copy_size = total_size - line_size; memmove((void*)fb, (void*)(fb + line_size), copy_size); for (int y = scanout.height - CHAR_HEIGHT; y < scanout.height; y++) { for (int x = 0; x < scanout.width; x++) { gfx_draw_pixel(x, y, gfx_bg); } } cursor_y = scanout.height - CHAR_HEIGHT; } } // --- primitives for the text layer ---------------------------------------- // // kernel/dev/vt.c owns a grid of cells and needs exactly two things the // console-cursor interface above cannot give it: draw one cell at a grid // position with its *own* background, and scroll the whole screen without // consulting a cursor. Everything else a terminal does is bookkeeping on the // grid, which is not this file's business. // One cell, addressed in cells rather than pixels, with its own colours. // gfx_draw_char paints its background from the file-static gfx_bg, which is // right for a console that has one background and wrong for a terminal where // every cell carries its own. void gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg) { if (!fb) return; int x = col * cell_w, y = row * cell_h; if (x < 0 || y < 0 || x + cell_w > (int)scanout.width || y + cell_h > (int)scanout.height) return; // No font yet: the bitmap, exactly as before. Anything it cannot hold is // drawn as a space rather than as the wrong glyph -- `?` was at least honest // about being a substitute, and a CP437 picture of something unrelated is // not. if (!cfont) { uchar g = cp < 256 ? (uchar)cp : ' '; for (int r = 0; r < cell_h; r++) { uchar bits = font_data[g][r]; for (int c = 0; c < cell_w; c++) gfx_draw_pixel(x + c, y + r, (bits & (0x80 >> c)) ? fg : bg); } return; } // Background first, then the glyph blended over it. The cell is always // filled: a terminal cell owns its rectangle, and leaving the old pixels // where a narrow glyph does not reach is how ghosting gets in. for (int r = 0; r < cell_h; r++) for (int c = 0; c < cell_w; c++) gfx_draw_pixel(x + c, y + r, bg); struct glyph gl; if (cp == ' ' || cfont->ops->glyph(cfont, cp, &gl) != 0) return; // no such glyph: the cell stays background // `cov` is w*h bytes of coverage, 0..255, positioned by the bearing from the // pen. The pen sits on the baseline at the left of the cell. int gx = x + gl.bearing_x; int gy = y + cfont_baseline - gl.bearing_y; for (int r = 0; r < gl.h; r++) { int py = gy + r; if (py < y || py >= y + cell_h) continue; // clip to the cell: a tall glyph must not for (int c = 0; c < gl.w; c++) { // bleed into the line above or below int px = gx + c; if (px < x || px >= x + cell_w) continue; uint a = gl.cov[r * gl.w + c]; if (a == 0) continue; gfx_draw_pixel(px, py, a == 255 ? fg : blend(bg, fg, a)); } } } // Bring the console typeface up. Called once, early, from the display path. // // The font is linked into the kernel (see CONSFONT in the Makefile) because // the console has to work before there is a filesystem -- a panic during boot // has to be readable. // // A size failure is not fatal and not silent: `cfont` stays null and every // cell keeps coming from the bitmap, which is what this system has always // drawn. A console that lost its font would be a much worse outcome than one // that never had a good one. void gfx_font_init(void) { extern unsigned char _binary_consfont_start[], _binary_consfont_end[]; uint32 len = (uint32)(_binary_consfont_end - _binary_consfont_start); struct glyph gl; if (cfont) return; // 16 pixels per em. The bitmap it replaces is 16 tall, so the terminal keeps // the number of rows it had and nothing that assumed a screenful has to // change. if (ttf_open(&consttf, _binary_consfont_start, len, 16) != 0) return; struct font *f = ttf_font(&consttf); if (!f) return; // The cell is as wide as the font's own advance. JetBrains Mono is // monospaced, so measuring one glyph measures all of them -- and measuring // rather than assuming is what keeps this correct if the font is ever // changed for another one. if (f->ops->glyph(f, 'M', &gl) != 0 || gl.advance <= 0) return; int w = gl.advance, h = f->ops->height(f); // Checked *before* they are stored. Writing them first and returning on a // bad value leaves the console with a cell size no font is drawing at, and // the bitmap fallback reading past its own 16-row glyphs. if (w < 4 || w > 64 || h < 6 || h > 64) return; // implausible metrics: keep the bitmap cell_w = w; cell_h = h; cfont_baseline = f->ops->baseline(f); cfont = f; } // **Nothing here prints.** gfx_font_init() runs before vt_init(), because // vt_init() has to know how big a cell is before it can work out how many fit // on the screen -- and by then cons_serial_only may already be clear, so a // cprintf() would reach vt_current() before there is a current terminal. That // is a fault during boot with no console to report it on, which is a // spectacularly unhelpful way to fail. // // So main() asks afterwards and says it then. int gfx_font_ready(void) { return cfont != 0; } void gfx_cell_size(int *w, int *h) { if (w) *w = cell_w; if (h) *h = cell_h; } // Move the whole screen up one text row and clear the row it exposes. The // cursor is not touched: the caller tracks it in cells and knows where it // wants to be, which is not necessarily where a pixel-scroll would leave it. void gfx_scroll_up(uint32 bg) { if (!fb || scanout.height < CHAR_HEIGHT) return; uint32 line_size = scanout.stride * CHAR_HEIGHT; uint32 total_size = scanout.stride * scanout.height; memmove((void*)fb, (void*)(fb + line_size), total_size - line_size); for (int y = (int)scanout.height - CHAR_HEIGHT; y < (int)scanout.height; y++) for (int x = 0; x < (int)scanout.width; x++) gfx_draw_pixel(x, y, bg); } void gfx_screen_reset(void) { gfx_bg = COLOR_BG; gfx_clear(COLOR_BG); cursor_x = 0; cursor_y = 0; } /* ------------------------------------------------------------------ */ /* Drawing primitives for the crash screen */ /* */ /* The crash screen lays itself out rather than printing, which needs */ /* two things the console never did: filled shapes, and text at an */ /* absolute position that leaves the cursor alone. */ /* ------------------------------------------------------------------ */ int gfx_ready(void) { return fb != 0; } void gfx_dims(int *w, int *h) { if (w) *w = (int)scanout.width; if (h) *h = (int)scanout.height; } int gfx_char_w(void) { return CHAR_WIDTH; } int gfx_char_h(void) { return CHAR_HEIGHT; } /* ------------------------------------------------------------------ */ /* Framebuffer geometry */ /* */ /* Whatever the attached scanout says, rather than anything read from */ /* the boot block here -- the driver that owns the display is the only */ /* thing that knows where the pixels are. See kernel/dev/fb.c for the */ /* device these back. */ /* ------------------------------------------------------------------ */ uint64 gfx_fb_phys(void) { return scanout.phys; } // Bytes of framebuffer, measured in scanlines rather than as // width * height * bpp/8 -- the two differ whenever the card pads its rows, // and it is the stride that says how far apart two rows really are. uint64 gfx_fb_size(void) { if (!fb) return 0; return (uint64)scanout.stride * (uint64)scanout.height; } uint32 gfx_fb_stride(void) { return scanout.stride; } uint32 gfx_fb_bpp(void) { return scanout.bpp; } volatile uchar *gfx_fb_ptr(void) { return fb; } /* ------------------------------------------------------------------ */ /* Cursor addressing and erasing */ /* */ /* What an ANSI escape parser needs and gfx_putc alone cannot give it: */ /* the ability to put the cursor somewhere specific and to erase a */ /* region without writing spaces over it. See kernel/core/console.c. */ /* ------------------------------------------------------------------ */ int gfx_cols(void) { return scanout.width ? (int)(scanout.width / CHAR_WIDTH) : 0; } int gfx_rows(void) { return scanout.height ? (int)(scanout.height / CHAR_HEIGHT) : 0; } void gfx_cursor_get(int *col, int *row) { if (col) *col = cursor_x / CHAR_WIDTH; if (row) *row = cursor_y / CHAR_HEIGHT; } // Clamped rather than validated: an escape sequence is untrusted input that // arrives from any program that can write to the terminal, and "CUP to row // 9999" should put the cursor on the last row, not off the end of the // framebuffer. void gfx_cursor_set(int col, int row) { int maxc = gfx_cols(), maxr = gfx_rows(); if (maxc <= 0 || maxr <= 0) return; if (col < 0) col = 0; if (row < 0) row = 0; if (col >= maxc) col = maxc - 1; if (row >= maxr) row = maxr - 1; cursor_x = col * CHAR_WIDTH; cursor_y = row * CHAR_HEIGHT; } // Fill a run of character cells with the background colour. static void erase_cells(int col, int row, int count) { int maxc = gfx_cols(); while (count-- > 0) { int x = col * CHAR_WIDTH, y = row * CHAR_HEIGHT; for (int i = 0; i < CHAR_HEIGHT; i++) for (int j = 0; j < CHAR_WIDTH; j++) gfx_draw_pixel(x + j, y + i, gfx_bg); if (++col >= maxc) { col = 0; row++; } } } // ED -- erase in display. 0: cursor to end, 1: start to cursor, 2: all. void gfx_erase_display(int mode) { int col, row, maxc = gfx_cols(), maxr = gfx_rows(); gfx_cursor_get(&col, &row); switch (mode) { case 0: erase_cells(col, row, (maxr - row) * maxc - col); break; case 1: erase_cells(0, 0, row * maxc + col + 1); break; default: gfx_clear(gfx_bg); break; } } // EL -- erase in line. Same three modes, bounded to the current row. void gfx_erase_line(int mode) { int col, row, maxc = gfx_cols(); gfx_cursor_get(&col, &row); switch (mode) { case 0: erase_cells(col, row, maxc - col); break; case 1: erase_cells(0, row, col + 1); break; default: erase_cells(0, row, maxc); break; } } // Row pointer computed once per row rather than per pixel. The crash screen // paints a solid card behind the QR symbol, and at 8 pixels per module that is // tens of thousands of writes going through gfx_draw_pixel's bounds check and // multiply. void gfx_fill_rect(int x0, int y0, int w, int h, uint32 color) { if (!fb) return; if (x0 < 0) { w += x0; x0 = 0; } if (y0 < 0) { h += y0; y0 = 0; } if (x0 + w > (int)scanout.width) w = (int)scanout.width - x0; if (y0 + h > (int)scanout.height) h = (int)scanout.height - y0; if (w <= 0 || h <= 0) return; // Only the 32bpp case gets the fast path; anything else is rare enough that // correctness through gfx_draw_pixel is the better trade. if (scanout.bpp != 32) { for (int y = y0; y < y0 + h; y++) for (int x = x0; x < x0 + w; x++) gfx_draw_pixel(x, y, color); return; } for (int y = y0; y < y0 + h; y++) { volatile uint32 *row = (volatile uint32 *)(fb + y * scanout.stride) + x0; for (int x = 0; x < w; x++) row[x] = color; } } void gfx_fill_circle(int cx, int cy, int r, uint32 color) { for (int dy = -r; dy <= r; dy++) for (int dx = -r; dx <= r; dx++) if (dx * dx + dy * dy <= r * r) gfx_draw_pixel(cx + dx, cy + dy, color); } // Text at an absolute position, leaving the console cursor alone. The crash // screen lays itself out, so gfx_putc's cursor tracking and scrolling are // exactly what it does not want. void gfx_text_at(int x, int y, const char *s, uint32 color) { for (int i = 0; s[i]; i++) gfx_draw_char(x + i * CHAR_WIDTH, y, (uchar)s[i], color); } // --- Logo ------------------------------------------------------------------ // // The boot splash's logo is drawn by the *loader*, not here -- see splash() in // boot/bios/stage2main.c. It has to be, because the kernel does not reach gfx_init() // until roughly the last 300ms of a 2.8s boot: drawing it here put the logo on // screen for one frame before console_takeover() wiped it, which is worse than // not having one. // // What lives here is the same drawing code against the same mask, for the // things that want the mark *after* boot -- the crash screen, and eventually // whatever schinit shows. Keeping one copy of the compositing logic is why this // is not simply deleted. // Blend `fg` over `bg` by coverage a (0..255). Per-channel, integer only: this // runs before any floating point is safe to touch. static uint32 blend(uint32 bg, uint32 fg, uint a) { uint32 r = (((fg >> 16) & 0xFF) * a + ((bg >> 16) & 0xFF) * (255 - a)) / 255; uint32 g = (((fg >> 8) & 0xFF) * a + ((bg >> 8) & 0xFF) * (255 - a)) / 255; uint32 b = (((fg ) & 0xFF) * a + ((bg ) & 0xFF) * (255 - a)) / 255; return (r << 16) | (g << 8) | b; } void gfx_draw_logo(int x, int y, uint32 color, uint32 bg) { if (!fb) return; for (int dy = 0; dy < LOGO_H; dy++) { for (int dx = 0; dx < LOGO_W; dx++) { uint a = LOGO_INK(dy * LOGO_W + dx); // Skip the background rather than painting it. The mask is a square but // the mark is a circle, so most of it is empty -- and drawing that empty // area in bg would stamp a visible box over whatever is underneath. if (a == 0) continue; gfx_draw_pixel(x + dx, y + dy, a == 255 ? color : blend(bg, color, a)); } } } // Centred, for callers that own the whole screen. void gfx_logo_centered(int cy, uint32 color, uint32 bg) { if (!fb || scanout.width < LOGO_W) return; gfx_draw_logo(((int)scanout.width - LOGO_W) / 2, cy - LOGO_H / 2, color, bg); }