Files
LithosAnanke/src/starkernel/hal/framebuffer.c
T
Robert Allan JamesandClaude Sonnet 5 e211707e2d starkernel: fb_scroll_rows() -- drop unnecessary volatile from the bulk copy/fill
Found while investigating item 4.4g's boot stall (moving console_fb_init()
earlier caused a >3-minute hang scrolling the fleet-birth transcript). The
GOP framebuffer is mapped write-back RAM (vmm.c:350-363), not
cache-disabled MMIO with side effects, so there's no correctness reason for
the scroll/fill loops to force one un-batchable volatile access per pixel.
g_fb.base stays volatile for other call sites; this function now casts to a
plain pointer for its bulk copy only.

Confirmed this alone does not fix the 4.4g stall -- the kernel builds at
-O0 (no optimization flag anywhere in Makefile.starkernel), so nothing
here gets vectorized regardless of the qualifier. That's now documented as
its own item, FABRIC.md 4.6. Keeping this fix regardless: it's correct on
its own terms independent of 4.4g's outcome.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-11 09:42:19 -04:00

308 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* framebuffer.c — UEFI GOP framebuffer pixel driver
*
* Supports BGRX32 and RGBX32 pixel formats.
* Pixel scale factor allows 1×/2×/4× rendering for hi-res displays.
*
* Hi-res mode:
* 1080p (1920×1080) @ scale=1 → 240 cols × 67 rows (8×16 cells)
* 1080p (1920×1080) @ scale=2 → 120 cols × 33 rows (16×32 cells)
* 4K (3840×2160) @ scale=2 → 240 cols × 67 rows (16×32 cells)
* 4K (3840×2160) @ scale=4 → 120 cols × 33 rows (32×64 cells)
*/
#include "framebuffer.h"
#include <stdint.h>
/* Font API from font_8x16.c */
extern const uint8_t *font_8x16_glyph(uint8_t ch);
/* ANSI 16-color palette (indices 0–15, packed 0x00RRGGBB) */
const uint32_t FB_ANSI_PALETTE[16] __attribute__((visibility("hidden"))) = {
/* 0 Black */ FB_RGB(0x00, 0x00, 0x00),
/* 1 Red */ FB_RGB(0xAA, 0x00, 0x00),
/* 2 Green */ FB_RGB(0x00, 0xAA, 0x00),
/* 3 Yellow */ FB_RGB(0xAA, 0x55, 0x00),
/* 4 Blue */ FB_RGB(0x00, 0x00, 0xAA),
/* 5 Magenta */ FB_RGB(0xAA, 0x00, 0xAA),
/* 6 Cyan */ FB_RGB(0x00, 0xAA, 0xAA),
/* 7 White */ FB_RGB(0xAA, 0xAA, 0xAA),
/* 8 Br.Black */ FB_RGB(0x55, 0x55, 0x55),
/* 9 Br.Red */ FB_RGB(0xFF, 0x55, 0x55),
/* 10 Br.Green */ FB_RGB(0x55, 0xFF, 0x55),
/* 11 Br.Yellow */ FB_RGB(0xFF, 0xFF, 0x55),
/* 12 Br.Blue */ FB_RGB(0x55, 0x55, 0xFF),
/* 13 Br.Magenta */ FB_RGB(0xFF, 0x55, 0xFF),
/* 14 Br.Cyan */ FB_RGB(0x55, 0xFF, 0xFF),
/* 15 Br.White */ FB_RGB(0xFF, 0xFF, 0xFF),
};
/* -----------------------------------------------------------------------
* Module state
* --------------------------------------------------------------------- */
typedef struct {
volatile uint32_t *base; /* GOP framebuffer base (32-bit pixels) */
uint32_t stride; /* pixels_per_scanline */
uint32_t width; /* horizontal resolution in pixels */
uint32_t height; /* vertical resolution in pixels */
FbPixelFormat fmt; /* pixel format (BGRX vs RGBX) */
uint32_t scale; /* pixel scale: 1, 2, or 4 (hi-res) */
int ready;
} FbState;
static FbState g_fb;
/* -----------------------------------------------------------------------
* Internal helpers
* --------------------------------------------------------------------- */
/*
* Convert our 0x00RRGGBB to the hardware 32-bit word.
*
* GOP format names describe increasing byte address in memory (little-endian
* uint32 store), e.g. PixelRedGreenBlueReserved8BitPerColor means byte0=R,
* byte1=G, byte2=B, byte3=X -- which as a little-endian uint32 puts R in
* bits[7:0], G in bits[15:8], B in bits[23:16]. The two branches below were
* previously swapped relative to that (found via FABRIC.md item 4.3.1's
* orientation test: corners rendered with R and B channels exchanged).
*/
static uint32_t pack_pixel(uint32_t rgb)
{
uint32_t r = FB_R(rgb);
uint32_t g = FB_G(rgb);
uint32_t b = FB_B(rgb);
if (g_fb.fmt == FB_PIXEL_RGBX32) {
/* RGBX byte order R,G,B: byte0=R@[7:0] byte1=G@[15:8] byte2=B@[23:16] */
return (b << 16) | (g << 8) | r;
}
/* BGRX (default), byte order B,G,R: byte0=B@[7:0] byte1=G@[15:8] byte2=R@[23:16] */
return (r << 16) | (g << 8) | b;
}
/* -----------------------------------------------------------------------
* Lifecycle
* --------------------------------------------------------------------- */
void fb_init(const FramebufferInfo *info, FbPixelFormat fmt)
{
if (!info || !info->base || info->width == 0 || info->height == 0) {
return;
}
g_fb.base = (volatile uint32_t *)info->base;
g_fb.stride = info->pixels_per_scanline;
g_fb.width = info->width;
g_fb.height = info->height;
g_fb.fmt = fmt;
g_fb.ready = 1;
/*
* Auto-select scale factor based on resolution.
* ≥ 2560 wide → 2× (gives readable 16px cells on QHD/4K)
* ≥ 3840 wide → 4× (gives readable 32px cells on 4K at density)
* Otherwise → 1× (1080p and below)
*/
if (info->width >= 3840) {
g_fb.scale = 4;
} else if (info->width >= 2560) {
g_fb.scale = 2;
} else {
g_fb.scale = 1;
}
}
int fb_is_available(void) { return g_fb.ready; }
uint32_t fb_width(void) { return g_fb.width; }
uint32_t fb_height(void) { return g_fb.height; }
/** Effective character cell dimensions in pixels (accounts for scale). */
uint32_t fb_cell_w(void) { return 8u * g_fb.scale; }
uint32_t fb_cell_h(void) { return 16u * g_fb.scale; }
/* -----------------------------------------------------------------------
* Pixel-level primitives
* --------------------------------------------------------------------- */
void fb_put_pixel(uint32_t x, uint32_t y, uint32_t rgb)
{
if (!g_fb.ready || x >= g_fb.width || y >= g_fb.height) return;
g_fb.base[y * g_fb.stride + x] = pack_pixel(rgb);
}
void fb_fill_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
uint32_t rgb)
{
uint32_t px, py;
uint32_t packed;
uint32_t x_end, y_end;
if (!g_fb.ready) return;
packed = pack_pixel(rgb);
x_end = x + w;
y_end = y + h;
if (x_end > g_fb.width) x_end = g_fb.width;
if (y_end > g_fb.height) y_end = g_fb.height;
for (py = y; py < y_end; py++) {
volatile uint32_t *row = g_fb.base + py * g_fb.stride + x;
for (px = 0; px < (x_end - x); px++) {
row[px] = packed;
}
}
}
/* -----------------------------------------------------------------------
* Glyph rendering
* --------------------------------------------------------------------- */
/**
* Draw one 8×16 glyph at pixel position (px, py).
* Scales by g_fb.scale so the same font works at any resolution.
*/
void fb_draw_glyph(uint32_t px, uint32_t py, uint8_t ch,
uint32_t fg, uint32_t bg)
{
const uint8_t *glyph;
uint32_t row, col, sr, sc;
uint32_t pfg, pbg;
if (!g_fb.ready) return;
glyph = font_8x16_glyph(ch);
pfg = pack_pixel(fg);
pbg = pack_pixel(bg);
for (row = 0; row < 16u; row++) {
uint8_t bits = glyph[row];
for (col = 0; col < 8u; col++) {
uint32_t on = (bits >> (7u - col)) & 1u;
uint32_t packed = on ? pfg : pbg;
uint32_t base_x = px + col * g_fb.scale;
/* font_8x16_data stores each glyph's scanlines bottom-to-top
* (FABRIC.md item 4.4f) -- row 0 is the glyph's bottom row, so
* it maps to the cell's last pixel row, not its first. */
uint32_t base_y = py + (15u - row) * g_fb.scale;
/* pixel block for scale > 1 */
for (sr = 0; sr < g_fb.scale; sr++) {
uint32_t fy = base_y + sr;
if (fy >= g_fb.height) continue;
volatile uint32_t *line =
g_fb.base + fy * g_fb.stride + base_x;
for (sc = 0; sc < g_fb.scale; sc++) {
if (base_x + sc < g_fb.width) {
line[sc] = packed;
}
}
}
}
}
}
/* -----------------------------------------------------------------------
* Boot diagnostic: orientation test pattern
* --------------------------------------------------------------------- */
/**
* fb_draw_orientation_test — one-time boot diagnostic (FABRIC.md item 4.3.1).
*
* Fills each raster corner with a distinct solid color so a screendump
* immediately reveals whether the image is flipped or rotated. Not part of
* the Console drawing fabric itself -- diagnostic-only, analogous to item
* 4.2's self-test scaffolding.
*
* Corner colors (raster/top-left-origin coordinates, pre-Cartesian-flip):
* top-left RED
* top-right GREEN
* bottom-left BLUE
* bottom-right YELLOW
*/
void fb_draw_orientation_test(void)
{
uint32_t block_w, block_h;
if (!g_fb.ready) return;
block_w = g_fb.width / 8u;
block_h = g_fb.height / 8u;
if (block_w == 0) block_w = 1;
if (block_h == 0) block_h = 1;
fb_fill_rect(0, 0, block_w, block_h, FB_RGB(0xFF, 0x00, 0x00)); /* top-left: RED */
fb_fill_rect(g_fb.width - block_w, 0, block_w, block_h, FB_RGB(0x00, 0xFF, 0x00)); /* top-right: GREEN */
fb_fill_rect(0, g_fb.height - block_h, block_w, block_h, FB_RGB(0x00, 0x00, 0xFF)); /* bottom-left: BLUE */
fb_fill_rect(g_fb.width - block_w, g_fb.height - block_h, block_w, block_h,
FB_RGB(0xFF, 0xFF, 0x00)); /* bottom-right: YELLOW */
}
/* -----------------------------------------------------------------------
* Scrolling
* --------------------------------------------------------------------- */
/**
* Scroll the framebuffer up by `char_rows` character rows.
* Each character row is (16 × scale) pixels tall.
* The vacated rows at the bottom are filled with bg.
*
* Copies through non-volatile pointers (FABRIC.md item 4.4g performance
* fix, 2026-08-11): the GOP framebuffer is mapped write-back, not
* cache-disabled MMIO (vmm.c:350-363 -- "QEMU's VGA emulation is coherent
* and UC- mapping causes #GP"), so it behaves as ordinary RAM and does not
* need per-word volatile access. `g_fb.base` stays `volatile` for
* single-pixel writes elsewhere in this file (draw calls that must not be
* reordered relative to other framebuffer state), but that qualifier forced
* this copy into one un-batchable 4-byte access per element with no
* compiler vectorization -- catastrophic once boot output exceeds one
* screen (~10^10 volatile accesses for an 18,000-line boot transcript
* under TCG). Casting away volatile here for the bulk copy/fill lets the
* compiler batch these loops normally.
*/
void fb_scroll_rows(uint32_t char_rows, uint32_t bg)
{
uint32_t pixel_rows_to_scroll;
uint32_t src_y, dst_y;
uint32_t x;
uint32_t packed_bg;
uint32_t *fb = (uint32_t *)(uintptr_t)g_fb.base;
if (!g_fb.ready || char_rows == 0) return;
pixel_rows_to_scroll = char_rows * 16u * g_fb.scale;
if (pixel_rows_to_scroll >= g_fb.height) {
fb_fill_rect(0, 0, g_fb.width, g_fb.height, bg);
return;
}
packed_bg = pack_pixel(bg);
/* Copy pixel rows upward */
for (dst_y = 0; dst_y + pixel_rows_to_scroll < g_fb.height; dst_y++) {
src_y = dst_y + pixel_rows_to_scroll;
uint32_t *src = fb + src_y * g_fb.stride;
uint32_t *dst = fb + dst_y * g_fb.stride;
for (x = 0; x < g_fb.width; x++) {
dst[x] = src[x];
}
}
/* Clear the newly exposed rows at the bottom */
for (dst_y = g_fb.height - pixel_rows_to_scroll;
dst_y < g_fb.height; dst_y++) {
uint32_t *row = fb + dst_y * g_fb.stride;
for (x = 0; x < g_fb.width; x++) {
row[x] = packed_bg;
}
}
}