Files
LithosAnanake/kernel/src/hal/framebuffer.c
T
rajamesandJunie a8b70e88d3 Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
2026-10-01 15:40:09 -04:00

369 lines
13 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-0.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-0.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-0.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 `pixel_rows` pixel rows.
* The vacated rows at the bottom are filled with bg.
*
* Takes a pixel-row count directly rather than a character-row count
* scaled by a hardcoded 16px cell height: the caller (vt100.c's
* scroll_up()) knows the actual active glyph cell height (16 × scale for
* bitmap mode, 24 for TTF mode), and a mismatch between the height this
* function scrolls by and the height the text model actually draws rows
* at compounds by the gap on every single scroll -- a few scrolls barely
* show it, but enough scrolls (or scrolling quickly) accumulates visible
* pixel overlap between rows.
*
* Copies through non-volatile pointers (FABRIC-0.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 pixel_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 || pixel_rows == 0) return;
pixel_rows_to_scroll = pixel_rows;
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;
}
}
}
/**
* fb_scroll_rect — box-confined scroll (FABRIC-0.md item 4.4t).
*
* Same non-volatile bulk-copy rationale as fb_scroll_rows() above, but
* bounded to a caller-supplied rectangle instead of the whole framebuffer --
* needed once REPL text is confined to the 4.4o CANVAS box, since a
* whole-framebuffer scroll would drag pixels from outside the box (e.g. a
* future 4.4r border) into the text area. Takes pixel_rows directly rather
* than char_rows * a hardcoded cell height, since TTF mode's cell height
* (24px) differs from bitmap mode's (16px * scale).
*/
void fb_scroll_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
uint32_t pixel_rows, uint32_t bg)
{
uint32_t src_y, dst_y, px;
uint32_t packed_bg;
uint32_t *fb = (uint32_t *)(uintptr_t)g_fb.base;
uint32_t x_end, y_end;
if (!g_fb.ready || pixel_rows == 0 || w == 0 || h == 0) return;
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;
if (x >= x_end || y >= y_end) return;
if (pixel_rows >= (y_end - y)) {
fb_fill_rect(x, y, x_end - x, y_end - y, bg);
return;
}
packed_bg = pack_pixel(bg);
/* Copy pixel rows upward, bounded to [x, x_end) */
for (dst_y = y; dst_y + pixel_rows < y_end; dst_y++) {
src_y = dst_y + pixel_rows;
uint32_t *src = fb + src_y * g_fb.stride + x;
uint32_t *dst = fb + dst_y * g_fb.stride + x;
for (px = 0; px < (x_end - x); px++) {
dst[px] = src[px];
}
}
/* Clear the newly exposed rows at the bottom of the rect */
for (dst_y = y_end - pixel_rows; dst_y < y_end; dst_y++) {
uint32_t *row = fb + dst_y * g_fb.stride + x;
for (px = 0; px < (x_end - x); px++) {
row[px] = packed_bg;
}
}
}