`jmp mpenter`.
x86-64 requires rsp to be 16-byte aligned immediately before a `call`, so a
function starts with rsp ending in 8 -- the return address -- and its
`push %rbp; mov %rsp,%rbp` prologue leaves %rbp 16-byte aligned. Compilers
build on that: an aligned vector store to a frame slot is `movaps`, and
`movaps` to a misaligned address is #GP.
entry64mp loaded the AP's stack pointer, which is page-aligned, and *jumped*.
Nothing pushed a return address, so %rbp came out eight bytes off -- on every
frame, on every application processor, for the life of the machine.
It cost nothing for as long as no function an AP ran needed an aligned vector
store. Then the console's glyph path started returning a small struct by value
(the cell-as-a-surface change), clang spilled it with movaps, and CPU 1 took a
#GP the instant it announced itself on a verbose boot:
unexpected trap 13 from cpu 1 rip ffff800000120a3b <draw_glyph_px+0x1cb>
120a37: 0f 10 45 90 movups -0x70(%rbp), %xmm0
120a3b: 0f 29 45 b0 movaps %xmm0, -0x50(%rbp) <- #GP
movups on the line above loads fine; movaps stores and faults. The
displacement is a multiple of 16, so what is misaligned is %rbp itself, which
is a fact about how the function was entered.
`call mpenter`, not `jmp`. Eight bytes for an ABI the whole toolchain assumes.
**It was hiding behind a second bug.** The first report was not a #GP at all:
Stop code ACQUIRE
#2 cprintf+0xae
#3 trap+0x2c4
trap() reported the fault with cprintf(), cprintf() takes the console lock,
and the faulting code was already holding it -- on a verbose boot every
character is drawn under that lock. acquire() panicked about a lock its own
call chain owned, and the trap number, the faulting address and the registers
were all lost. A kernel-mode trap ends in panic() a dozen lines later anyway,
so the console goes into panic mode *before* the report rather than during it.
**And a third, in the same boot.** The console reported "JetBrains Mono, 8x16
cells" -- a name from one font and metrics from another. The built-in bitmap
became a provider like any other in the previous commit, so draw_glyph_px()
installs it lazily on the first glyph; on a verbose boot that happens before
gfx_font_init() runs, which then returned early because a font was already
installed. The console spent the whole boot in the fallback while claiming not
to be. "A font is installed" and "a typeface was chosen" had been the same test
and quietly stopped being; they are two flags now.
Verified: verbose boot reaches a shell twice running with no traps and no
panics, and reports 10x21 cells -- the real typeface -- where it reported 8x16
before. Both supported configurations quiet: one boot each, no traps, no
panics, socktest and memfdtest green.
Written up as section 22 of docs/history/kernel.md, because the interesting
part is not the missing eight bytes but that three separate faults had to be
peeled off in order before the first one could be seen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
733 lines
25 KiB
C
733 lines
25 KiB
C
// 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 "logo.h"
|
|
#include "device.h"
|
|
#include "display.h"
|
|
#include "gfx.h"
|
|
#include "vt.h"
|
|
#include "text.h"
|
|
#include "schboot.h"
|
|
#include "schboot.h"
|
|
#include "memlayout.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, 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;
|
|
|
|
// 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;
|
|
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));
|
|
|
|
damage(y, 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++;
|
|
}
|
|
}
|
|
|
|
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.
|
|
// 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);
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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, data, len, 16) != 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, 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;
|
|
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;
|
|
|
|
memmove((void*)fb, (void*)(fb + line_size), total_size - line_size);
|
|
damage(0, (int)scanout.height - 1); // a scroll moves every row
|
|
|
|
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;
|
|
|
|
damage(y0, 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);
|
|
}
|