// Drawing and the text console, on whatever display is bound. See include/gfx/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 "core/types.h" #include "arch/x86.h" #include "arch/memlayout.h" #include "core/defs.h" #include "gfx/theme.h" #include "gfx/logo.h" #include "dev/device.h" #include "gfx/display.h" #include "gfx/gfx.h" #include "dev/vt.h" #include "gfx/text.h" #include "arch/schboot.h" #include "gfx/ttf.h" #include "arch/vmap.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, the // font is the built-in bitmap reached through font_builtin(), and 8x16 is what // that one measures. // // This file no longer touches `font_data` at all. It used to walk the array // itself when there was no TrueType face, which made two glyph blitters that // had to agree about a cell size only one of them was told had changed -- and // they did not agree: every line of PSOGM drew five rows of the next // character's bitmap underneath itself. The bitmap is a font provider like any // other now, and there is one blitter. #define BITMAP_W 8 #define BITMAP_H 16 static int cell_w = BITMAP_W; static int cell_h = BITMAP_H; #define CHAR_WIDTH cell_w #define CHAR_HEIGHT cell_h // The typeface. Null only until the first glyph is drawn, which fills it in // with the built-in bitmap; gfx_font_init() replaces that with a real face // when there is one. // Whether the font in `cfont` is the built-in bitmap standing in, or a real // typeface that was chosen. // // The distinction was implicit in `cfont == 0` until the built-in became a // provider like any other, and losing it cost a boot: draw_glyph_px() installs // the fallback lazily, a verbose boot draws before gfx_font_init() runs, and // gfx_font_init() then saw a font already installed and returned without // loading anything. The console stayed 8x16 for the whole boot while reporting // itself as JetBrains Mono. static int cfont_is_builtin; static struct ttf consttf; static struct font *cfont; static int cfont_baseline; static uint32 blend(uint32 bg, uint32 fg, uint a); static void draw_glyph_px(int x, int y, uint32 cp, uint32 fg, uint32 bg); // 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; // Where `fb` points once gfx_accelerate() has run: a copy of the screen in // RAM, which every drawing routine reads and writes, and which gfx_flush() // copies onto the scanout -- `hw` -- a rectangle at a time. // // The framebuffer itself is device memory, uncached. Drawing into it directly // cost nothing in QEMU, where it is RAM, and on a real graphics card it was // the slowest thing the kernel did: a scroll reads every byte on the screen // back across the bus, one uncached read per byte, and an anti-aliased glyph // reads every pixel it blends. A screenful of boot log took long enough that // photographs of the monitor caught the scroll half done, and a second CPU // waiting for the console lock declared a deadlock. The copy is read instead, // and the card is only ever written -- through a write-combining mapping, so // a row goes out as bursts rather than one transaction per pixel. static volatile uchar *hw; static uchar *shadow; static uint64 shadow_bytes; static uint64 wc_phys; // the scanout ioremap_wc() mapped, and where static volatile uchar *wc_ptr; // What gfx_flush() still owes the scanout, in pixels, inclusive. Separate from // the row span below: that one is for a second screen and is taken by // somebody else. static int fl_x0 = -1, fl_y0, fl_x1, fl_y1; static void dirty(int x0, int y0, int x1, int y1) { if (fb != (volatile uchar *)shadow) return; if (fl_x0 < 0) { fl_x0 = x0; fl_y0 = y0; fl_x1 = x1; fl_y1 = y1; return; } if (x0 < fl_x0) fl_x0 = x0; if (y0 < fl_y0) fl_y0 = y0; if (x1 > fl_x1) fl_x1 = x1; if (y1 > fl_y1) fl_y1 = y1; } // What has been drawn since anyone last asked. // // The console framebuffer is one screen on a machine that may have two, and // the second one is a copy of this that somebody has to make. Copying all of // it every time would be four megabytes to move one line of text, so the rows // that were touched are remembered as a single span -- not a list of // rectangles, because console output is lines and a span is what lines make. // // Rows rather than pixels: the copy is row-at-a-time anyway, and a column // range would save nothing a memcpy cares about. static int dmg_y0 = -1, dmg_y1 = -1; static void damage(int y0, int y1) { if (y0 < 0) y0 = 0; if (y1 >= (int)scanout.height) y1 = (int)scanout.height - 1; if (y1 < y0) return; if (dmg_y0 < 0 || y0 < dmg_y0) dmg_y0 = y0; if (dmg_y1 < 0 || y1 > dmg_y1) dmg_y1 = y1; } // Hand the span over and forget it. Returns 0 when nothing has changed, which // is the common case and the reason the caller can afford to ask often. int gfx_damage_take(int *y0, int *y1) { if (dmg_y0 < 0) return 0; *y0 = dmg_y0; *y1 = dmg_y1; dmg_y0 = dmg_y1 = -1; return 1; } 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; hw = info->ptr; // The framebuffer gfx_accelerate() mapped write-combined, coming back -- // vga_fb_restore() hands over its own uncached pointer to the same frames. if (wc_ptr && info->phys == wc_phys) hw = scanout.ptr = wc_ptr; fb = hw; if (shadow && (uint64)scanout.stride * scanout.height <= shadow_bytes) { fb = (volatile uchar *)shadow; // A new screen has none of what the copy holds. dirty(0, 0, (int)scanout.width - 1, (int)scanout.height - 1); } // 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)); damage(y, y); dirty(x, y, x, y); 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) { // Pixel-positioned rather than on the grid -- the crash screen lays itself // out in pixels because it is a report, not a terminal. Same glyph path as // the grid uses, which it did not used to be; see draw_glyph_px(). draw_glyph_px(x, y, c, color, gfx_bg); } void gfx_puts(int x, int y, char *s, uint32 color) { while (*s) { gfx_putc((uchar)*s, color); s++; } } // The screen, `by` bytes towards the top. memmove() goes a byte at a time, // which against RAM is merely slow; eight at a time is what a scroll of a // screenful wants. Forward is safe because the destination is below the // source. static void move_up(uint64 by, uint64 n) { volatile uchar *d = fb; volatile uchar *s = fb + by; if (((by | n | (uint64)fb) & 7) == 0) { for (uint64 i = 0; i < n; i += 8) *(volatile uint64 *)(d + i) = *(volatile uint64 *)(s + i); return; } for (uint64 i = 0; i < n; i++) d[i] = s[i]; } 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; move_up(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. // One glyph, at a pixel position, in a cell-sized box. // // Both the console grid and the pixel-positioned text in the crash screen come // through here, which is the point: they used to be two implementations, and // only one of them was updated when the cell size stopped being 8x16. // gfx_draw_char() still walked `font_data[c][row]` for `row < CHAR_HEIGHT` -- // and CHAR_HEIGHT is the *TrueType* cell height now, while font_data has // exactly 16 rows per glyph. Every line of PSOGM therefore drew five rows of // the *next character's* bitmap underneath itself. It looked like each line // had been printed twice. static void draw_glyph_px(int x, int y, uint32 cp, uint32 fg, uint32 bg) { struct surface screen, cell; if (!fb) return; if (x < 0 || y < 0 || x + cell_w > (int)scanout.width || y + cell_h > (int)scanout.height) return; // There is always a font. Before gfx_font_init() runs it is the built-in // bitmap, *as a font provider* -- which is the whole point of the provider // seam: the early console and the real one are the same code path drawing // through different metrics, rather than two blitters that have to be kept // in agreement. They were not kept in agreement, and PSOGM printed every // line twice for it. if (!cfont) { cfont = font_builtin(); cfont_baseline = cfont->ops->baseline(cfont); cfont_is_builtin = 1; } // Background first. 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); // Dirty before the early return: a blank cell is a background fill and // nothing else, and that fill is what rubs the text cursor out. Marked // only after the glyph, the blank was drawn to the back buffer and never // copied to the screen -- a block left at the end of every line. dirty(x, y, x + cell_w - 1, y + cell_h - 1); if (cp == ' ') return; // nothing to draw over the background // The cell as a surface of its own, so the renderer's own bounds check is // what stops a tall glyph bleeding into the line above or below. The pen is // at the cell's left edge, on its baseline. screen.pixels = (void *)fb; screen.stride = scanout.stride; screen.width = (int)scanout.width; screen.height = (int)scanout.height; screen.bpp = (int)scanout.bpp; cell = surface_sub(&screen, x, y, cell_w, cell_h); text_draw_glyph(&cell, cfont, 0, cfont_baseline, cp, fg); dirty(x, y, x + cell_w - 1, y + cell_h - 1); } void gfx_draw_cell(int col, int row, uint32 cp, uint32 fg, uint32 bg) { draw_glyph_px(col * cell_w, row * cell_h, cp, fg, bg); } // 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[]; const unsigned char *data = _binary_consfont_start; uint32 len = (uint32)(_binary_consfont_end - _binary_consfont_start); struct glyph gl; // A built-in font standing in is not a font having been chosen. Returning // here on that basis is what kept the bitmap on screen for an entire // verbose boot. if (cfont && !cfont_is_builtin) return; // A typeface off the boot volume, if the loader found one. // // Tried first and fallen back from silently: the built-in font is not a // degraded mode, it is the same font, and a machine with no console.ttf is // the ordinary case rather than a fault. What this buys is that the font is // *replaceable* -- drop a different TrueType file on the ESP and the console // is drawn with it, with no kernel to rebuild. // // In place, out of memory the loader marked reserved. See SCHBOOT_FONT_ADDR // for why the kernel does not read this file itself. { uint64 font_pa = *(volatile uint64 *)P2V(SCHBOOT_FONT_ADDR); uint32 font_len = *(volatile uint32 *)P2V(SCHBOOT_FONT_LEN); if (font_pa && font_len >= 4096 && font_pa + font_len <= 0x100000000ULL) { data = (const unsigned char *)P2V(font_pa); len = font_len; } } // The size follows the screen: about 45 rows whatever the resolution, and // never under 16 pixels per em, the bitmap's height. A fixed 16 was right // on 1024x768 and unreadable on a 1440p monitor -- and asking for a smaller // mode to get bigger text is no answer, because firmware on real machines // often "sets" a smaller mode by leaving the panel at its native size and // misreporting the row length, which is the sheared, repeated screen. int em = scanout.height / 45; if (em < 16) em = 16; if (em > 40) em = 40; if (ttf_open(&consttf, data, len, em) != 0) { // A bad file on the ESP must not cost the console its font. Nothing has // been changed yet at this point, so the built-in one is still there to // fall back to -- and it is worth saying, because a font that silently // did not load looks exactly like a font that did. if (data == _binary_consfont_start) return; kinfo("console: the font on the boot volume would not parse; " "using the built-in one\n"); data = _binary_consfont_start; len = (uint32)(_binary_consfont_end - _binary_consfont_start); if (ttf_open(&consttf, data, len, em) != 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; cfont_is_builtin = 0; } // **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 && !cfont_is_builtin; } 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; move_up(line_size, total_size - line_size); damage(0, (int)scanout.height - 1); // a scroll moves every row dirty(0, 0, (int)scanout.width - 1, (int)scanout.height - 1); 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; gfx_flush(); } // --- the copy in RAM --------------------------------------------------------- // Copy what has been drawn since the last flush onto the scanout. // // Called where drawing ends rather than where it happens: after a message, a // write(2) to a terminal, a terminal switch, the crash screen. A line of // output is then one rectangle however many glyphs it took. // // Not a lock. Two CPUs flushing at once would only copy the same pixels // twice, and a flush that finds another in progress leaves its damage for // the next one -- so it can be called from the panic path and from inside an // interrupt without either of them waiting on anything. static uint64 flushing; void gfx_flush(void) { int x0, y0, x1, y1, bpp; if (!hw || fb != (volatile uchar *)shadow || fl_x0 < 0) return; if (xchg(&flushing, 1) != 0) return; x0 = fl_x0; y0 = fl_y0; x1 = fl_x1; y1 = fl_y1; fl_x0 = -1; if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 >= (int)scanout.width) x1 = (int)scanout.width - 1; if (y1 >= (int)scanout.height) y1 = (int)scanout.height - 1; bpp = (int)scanout.bpp / 8; for (int y = y0; y <= y1 && x0 <= x1; y++) { uint64 off = (uint64)y * scanout.stride + (uint64)x0 * bpp; uint64 end = (uint64)y * scanout.stride + (uint64)(x1 + 1) * bpp; // Bytes up to an eight-byte boundary, words, and bytes after. The // source and the destination share an offset, so one alignment serves // both. while (off < end && (off & 7)) { hw[off] = shadow[off]; off++; } for (; off + 8 <= end; off += 8) *(volatile uint64 *)(hw + off) = *(uint64 *)(shadow + off); for (; off < end; off++) hw[off] = shadow[off]; } // Write-combined stores sit in the CPU's buffers until something drains // them. They would get there; this says when. asm volatile("sfence" ::: "memory"); flushing = 0; } // The crash screen, whatever else was happening. The CPU that was flushing // may have been stopped halfway through by the panic, holding the flag for // ever, and a crash report that never reaches the screen is the one outcome // worse than a slow one. void gfx_flush_all(void) { flushing = 0; dirty(0, 0, (int)scanout.width - 1, (int)scanout.height - 1); gfx_flush(); } // Move drawing into RAM, and map the scanout write-combined. // // Once, from kmain, as soon as there is a page allocator. Everything drawn // before that went straight to the framebuffer, and is drawn again here from // the terminal's grid rather than read back from it -- reading the screen is // the slow thing this exists to stop doing. void gfx_accelerate(void) { uint64 bytes; void *wc; if (!fb || shadow || scanout.phys == 0 || scanout.bpp < 8) return; bytes = (uint64)scanout.stride * scanout.height; if ((wc = ioremap_wc(scanout.phys, bytes)) != 0) { wc_phys = scanout.phys; wc_ptr = (volatile uchar *)wc; hw = scanout.ptr = wc_ptr; fb = hw; } if ((shadow = vmap_alloc(bytes)) == 0) { kinfo("gfx: no memory for a copy of the screen; drawing to it directly\n"); return; } shadow_bytes = bytes; fb = (volatile uchar *)shadow; gfx_clear(gfx_bg); vt_repaint(vt_current()); gfx_flush(); kinfo("gfx: %d KB copy of the screen in RAM, %s\n", (int)(bytes / 1024), wc ? "write-combined to the card" : "copied out uncached"); } // The scanout itself, for /dev/fb0: a program drawing on the screen draws on // the screen, not on the console's copy of it. volatile uchar * gfx_hw_ptr(void) { return hw; } /* ------------------------------------------------------------------ */ /* 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; damage(y0, y0 + h - 1); dirty(x0, y0, x0 + w - 1, y0 + h - 1); // 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); }