- tty1-tty12 all run login; the image has only root. /dev/tty7..12 could not be written (cdev.c knew minors 10-16 only), which sent their jobs into a restart storm that took the supervisor down; fixed, and stat reports them as Linux's tty7..12. - Erasing text at a prompt did nothing: erase_one sent a bare backspace, which only moves; it and the echo path now send back-blank-back through vt_feed. - A white cursor block was left at the end of every line of a verbose boot: a line feed at the bottom scrolls the pixels, cursor included, and the cursor was erased after the scroll. It comes off first now. A blank cell is also marked dirty, which it was not. - ldlsd died with a general protection fault on real hardware in its variadic log function: Linux module code calls with an 8-byte-aligned stack and an unset %al, and the prologue's movaps faults. LDLS guest code is built with -mstackrealign. - make iso took over a minute for any change, because staging r8169 rewrote the same files and every mtime moved; it now replaces files only if their contents differ (about 10 s). - The loader's splash is redrawn in the boot manager's style, and the boot manager shows its cursor whenever a pointer device exists. - xf86-input-libinput is out of the image again: with it Xorg took both keyboard and pointer from libinput and, on the user's laptop, neither worked. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
925 lines
32 KiB
C
925 lines
32 KiB
C
// 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);
|
|
}
|