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>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# src/starkernel/hal/
|
||||
|
||||
Hardware-abstraction-layer implementation, backing `include/starkernel/hal/hal.h`.
|
||||
|
||||
- `hal.c` — top-level HAL dispatch.
|
||||
- `console.c` — UART 16550 console driver.
|
||||
- `framebuffer.c`, `font_8x16.c`, `vt100.c` — framebuffer console driver,
|
||||
its bitmap font, and VT100 terminal emulation over the framebuffer.
|
||||
- `memory.c` — HAL-level memory query interface.
|
||||
- `host_services.c` — host-service shims used by kernel code that needs a
|
||||
narrow, testable interface to hardware/firmware services.
|
||||
@@ -0,0 +1,462 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* console.c - Serial console implementation (UART 16550)
|
||||
* Supports amd64 via I/O ports
|
||||
*/
|
||||
|
||||
#include "console.h"
|
||||
#include "framebuffer.h"
|
||||
#include "vt100.h"
|
||||
#include "arch.h"
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#define SERIAL_SUPPORTED 1
|
||||
|
||||
/* UART 16550 I/O ports (COM1) */
|
||||
#define SERIAL_PORT_BASE 0x3F8
|
||||
|
||||
#define SERIAL_DATA_PORT (SERIAL_PORT_BASE + 0)
|
||||
#define SERIAL_INT_ENABLE_PORT (SERIAL_PORT_BASE + 1)
|
||||
#define SERIAL_FIFO_CTRL_PORT (SERIAL_PORT_BASE + 2)
|
||||
#define SERIAL_LINE_CTRL_PORT (SERIAL_PORT_BASE + 3)
|
||||
#define SERIAL_MODEM_CTRL_PORT (SERIAL_PORT_BASE + 4)
|
||||
#define SERIAL_LINE_STATUS_PORT (SERIAL_PORT_BASE + 5)
|
||||
|
||||
/* Line Status Register bits */
|
||||
#define SERIAL_LSR_DATA_READY (1 << 0)
|
||||
#define SERIAL_LSR_THR_EMPTY (1 << 5)
|
||||
|
||||
static inline void outb(uint16_t port, uint8_t val) {
|
||||
__asm__ volatile ("outb %0, %1" : : "a"(val), "Nd"(port));
|
||||
}
|
||||
|
||||
static inline uint8_t inb(uint16_t port) {
|
||||
uint8_t ret;
|
||||
__asm__ volatile ("inb %1, %0" : "=a"(ret) : "Nd"(port));
|
||||
return ret;
|
||||
}
|
||||
|
||||
#elif defined(__aarch64__)
|
||||
#define SERIAL_SUPPORTED 1
|
||||
|
||||
/* PL011 UART — QEMU virt machine UART0 at 0x09000000 */
|
||||
#define PL011_BASE ((volatile unsigned int *)0x09000000UL)
|
||||
#define PL011_DR (PL011_BASE + 0) /* Data Register (offset 0x000) */
|
||||
#define PL011_FR (PL011_BASE + 6) /* Flag Register (offset 0x018) */
|
||||
#define PL011_FR_TXFF (1u << 5) /* TX FIFO full */
|
||||
|
||||
static inline void pl011_putc(char c)
|
||||
{
|
||||
while (*PL011_FR & PL011_FR_TXFF) { }
|
||||
*PL011_DR = (unsigned int)(unsigned char)c;
|
||||
}
|
||||
|
||||
#elif defined(__riscv)
|
||||
#define SERIAL_SUPPORTED 1
|
||||
|
||||
/* NS16550 UART — QEMU virt machine UART0 at 0x10000000 (MMIO, byte-wide) */
|
||||
#define NS16550_BASE ((volatile unsigned char *)0x10000000UL)
|
||||
#define NS16550_THR (NS16550_BASE + 0) /* Transmit Holding Register */
|
||||
#define NS16550_LSR (NS16550_BASE + 5) /* Line Status Register */
|
||||
#define NS16550_LSR_THRE (1u << 5) /* Transmit Holding Register Empty */
|
||||
#define NS16550_LSR_DR (1u << 0) /* Data Ready */
|
||||
|
||||
static inline void ns16550_putc(char c)
|
||||
{
|
||||
while (!(*NS16550_LSR & NS16550_LSR_THRE)) { }
|
||||
*NS16550_THR = (unsigned char)c;
|
||||
}
|
||||
|
||||
#else
|
||||
#define SERIAL_SUPPORTED 0
|
||||
|
||||
/* Stubs for other architectures */
|
||||
static inline void outb(uint16_t port, uint8_t val) {
|
||||
(void)port;
|
||||
(void)val;
|
||||
}
|
||||
|
||||
static inline uint8_t inb(uint16_t port) {
|
||||
(void)port;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Initialize serial console
|
||||
* x86: programs 16550 UART.
|
||||
* aarch64: PL011 is already initialized by UEFI firmware on QEMU virt.
|
||||
*/
|
||||
void console_init(void) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
outb(SERIAL_INT_ENABLE_PORT, 0x00);
|
||||
outb(SERIAL_LINE_CTRL_PORT, 0x80);
|
||||
outb(SERIAL_DATA_PORT, 0x01);
|
||||
outb(SERIAL_INT_ENABLE_PORT, 0x00);
|
||||
outb(SERIAL_LINE_CTRL_PORT, 0x03);
|
||||
outb(SERIAL_FIFO_CTRL_PORT, 0xC7);
|
||||
outb(SERIAL_MODEM_CTRL_PORT, 0x0B);
|
||||
outb(SERIAL_MODEM_CTRL_PORT, 0x1E);
|
||||
outb(SERIAL_DATA_PORT, 0xAE);
|
||||
if (inb(SERIAL_DATA_PORT) != 0xAE) {
|
||||
return;
|
||||
}
|
||||
outb(SERIAL_MODEM_CTRL_PORT, 0x0F);
|
||||
#elif defined(__aarch64__)
|
||||
/* PL011 already enabled by AAVMF; nothing to do */
|
||||
(void)0;
|
||||
#elif defined(__riscv)
|
||||
/* NS16550 already enabled by UEFI firmware on QEMU virt; nothing to do */
|
||||
(void)0;
|
||||
#else
|
||||
(void)SERIAL_SUPPORTED;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
static int serial_transmit_empty(void) {
|
||||
return inb(SERIAL_LINE_STATUS_PORT) & SERIAL_LSR_THR_EMPTY;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Active VM name for [Name] line prefix; NULL = no prefix.
|
||||
*
|
||||
* g_active_vm_name_buf owns the storage -- console_set_vm_name() copies
|
||||
* into it rather than storing the caller's own pointer. Found live
|
||||
* 2026-08-28 (FABRIC-2.md Phase F): mama_word_use() (USE) passes
|
||||
* entry.name, a local VMRegistryEntry's own field -- once USE returns,
|
||||
* that stack frame is reused and the old raw-pointer version left
|
||||
* g_active_vm_name dangling, corrupting every console tag after the
|
||||
* first USE (observed as garbled "[[]"/binary-looking prefixes). A
|
||||
* caller passing a string literal (e.g. console_set_vm_name("Hermes"))
|
||||
* was always safe; this fixes every caller uniformly instead of relying
|
||||
* on each one happening to pass static storage. */
|
||||
#define CONSOLE_VM_NAME_BUF 64
|
||||
static char g_active_vm_name_buf[CONSOLE_VM_NAME_BUF];
|
||||
static const char *g_active_vm_name = (void *)0;
|
||||
static int g_line_start = 1;
|
||||
static uint64_t g_console_tx_count; /* monotonic console_putc() counter (BSS) */
|
||||
|
||||
void console_set_vm_name(const char *name) {
|
||||
/* Empty string treated the same as NULL: console_save_vm_name()
|
||||
* writes "" for "there was no active name," so this keeps that
|
||||
* round-trip correct (save-empty then restore-empty must mean
|
||||
* "still no prefix," not "prefix is now the empty string"). */
|
||||
if (!name || !name[0]) { g_active_vm_name = (void *)0; return; }
|
||||
size_t i;
|
||||
for (i = 0; i < CONSOLE_VM_NAME_BUF - 1u && name[i]; i++)
|
||||
g_active_vm_name_buf[i] = name[i];
|
||||
g_active_vm_name_buf[i] = '\0';
|
||||
g_active_vm_name = g_active_vm_name_buf;
|
||||
}
|
||||
|
||||
const char *console_get_vm_name(void) {
|
||||
return g_active_vm_name;
|
||||
}
|
||||
|
||||
/* FABRIC-3.md SXXV follow-up: see console.h's own doc comment on
|
||||
* console_set_user_prefix_provider() for why this is a registered
|
||||
* callback rather than console.c calling into capsule/WIREBIND logic
|
||||
* directly. NULL (the default, until repl.c registers one at boot) means
|
||||
* "no provider yet" -- emit_prefix() falls back to the bare "[VMName] "
|
||||
* form, identical to today's behavior. */
|
||||
static console_user_prefix_fn g_user_prefix_fn = (void *)0;
|
||||
|
||||
void console_set_user_prefix_provider(console_user_prefix_fn fn) {
|
||||
g_user_prefix_fn = fn;
|
||||
}
|
||||
|
||||
void console_save_vm_name(char *out, size_t cap) {
|
||||
if (!out || cap == 0) return;
|
||||
size_t i = 0;
|
||||
if (g_active_vm_name) {
|
||||
for (; i < cap - 1u && g_active_vm_name[i]; i++)
|
||||
out[i] = g_active_vm_name[i];
|
||||
}
|
||||
out[i] = '\0';
|
||||
}
|
||||
|
||||
/* Raw single-character write — no prefix logic, called by emit_prefix() */
|
||||
static void raw_putc(char c) {
|
||||
#if defined(__aarch64__)
|
||||
if (c == '\n') pl011_putc('\r');
|
||||
pl011_putc(c);
|
||||
#elif defined(__riscv)
|
||||
if (c == '\n') ns16550_putc('\r');
|
||||
ns16550_putc(c);
|
||||
#elif defined(__x86_64__) || defined(__i386__)
|
||||
if (c == '\n') {
|
||||
while (!serial_transmit_empty()) { arch_relax(); }
|
||||
outb(SERIAL_DATA_PORT, '\r');
|
||||
}
|
||||
while (!serial_transmit_empty()) { arch_relax(); }
|
||||
outb(SERIAL_DATA_PORT, (uint8_t)c);
|
||||
#else
|
||||
(void)c;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Emit "[VMName] " to both serial (raw_putc) and, when available, the
|
||||
* framebuffer (vt100_putc) -- mirrors console_putc()'s own serial/framebuffer
|
||||
* split so the prefix reaches both outputs, not serial only. No recursion
|
||||
* into console_putc itself (would re-trigger the line-start prefix check). */
|
||||
/* FABRIC-0.md 4.4: the bracketed VM name (brackets included) renders in
|
||||
* standard web orange, 0xFFA500 -- not in the classic 16-color ANSI
|
||||
* palette, so sent as a literal 24-bit SGR sequence rather than a palette
|
||||
* index. Same dual serial+framebuffer send pattern as the rest of this
|
||||
* function. */
|
||||
static void emit_prefix(void) {
|
||||
const char *p;
|
||||
int fb = fb_is_available();
|
||||
static const char *color_on = "\x1b[38;2;255;165;0m";
|
||||
static const char *color_off = "\x1b[39m";
|
||||
const char *user = g_user_prefix_fn ? g_user_prefix_fn() : (void *)0;
|
||||
|
||||
for (p = color_on; *p; p++) { raw_putc(*p); if (fb) vt100_putc(*p); }
|
||||
raw_putc('[');
|
||||
if (fb) vt100_putc('[');
|
||||
if (user && user[0]) {
|
||||
for (p = user; *p; p++) {
|
||||
raw_putc(*p);
|
||||
if (fb) vt100_putc(*p);
|
||||
}
|
||||
raw_putc('@');
|
||||
if (fb) vt100_putc('@');
|
||||
}
|
||||
for (p = g_active_vm_name; *p; p++) {
|
||||
raw_putc(*p);
|
||||
if (fb) vt100_putc(*p);
|
||||
}
|
||||
raw_putc(']');
|
||||
if (fb) vt100_putc(']');
|
||||
for (p = color_off; *p; p++) { raw_putc(*p); if (fb) vt100_putc(*p); }
|
||||
raw_putc(' ');
|
||||
if (fb) vt100_putc(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a single character to serial console.
|
||||
* Emits "[VMName] " at the start of each new line when a VM name is set.
|
||||
* Also mirrors output to the framebuffer VT100 terminal when available.
|
||||
* Serial output is ALWAYS active regardless of framebuffer state.
|
||||
*
|
||||
* console_putc_inner(): the same body minus the tx-counter increment --
|
||||
* used internally for the newline console_putc() itself realizes out of
|
||||
* g_pending_line_close below (see console_ensure_line_start()), so that
|
||||
* one real character write emits at most one counted "start" (the '\n'
|
||||
* plus the character together read as a single unit of real output, not
|
||||
* two, to console_tx_count()'s callers).
|
||||
*/
|
||||
static void console_putc_inner(char c);
|
||||
|
||||
/* FABRIC-2.md §I.9 fix, 2026-09-05: set by console_ensure_line_start(),
|
||||
* cleared either by console_cancel_deferred_line_start() (no output
|
||||
* followed -- drop the newline silently) or here in console_putc() (real
|
||||
* output followed -- realize it for real, counted, immediately before the
|
||||
* character that triggered it). See console.h's doc comments on both
|
||||
* functions for the full rationale; the short version is that the old
|
||||
* unconditional-and-immediate version of this newline fired every idle
|
||||
* heartbeat regardless of whether anything was actually about to be
|
||||
* printed, visibly snapping a bare REPL prompt to a fresh blank line on
|
||||
* its own. */
|
||||
static int g_pending_line_close = 0;
|
||||
|
||||
void console_putc(char c) {
|
||||
if (g_pending_line_close) {
|
||||
g_pending_line_close = 0;
|
||||
g_console_tx_count++;
|
||||
console_putc_inner('\n');
|
||||
}
|
||||
g_console_tx_count++;
|
||||
console_putc_inner(c);
|
||||
}
|
||||
|
||||
static void console_putc_inner(char c)
|
||||
{
|
||||
/* --- serial UART path (always on) --- */
|
||||
if (g_active_vm_name && g_line_start && c != '\n') {
|
||||
emit_prefix();
|
||||
g_line_start = 0;
|
||||
}
|
||||
raw_putc(c);
|
||||
if (c == '\n') {
|
||||
g_line_start = 1;
|
||||
}
|
||||
|
||||
/* --- framebuffer VT100 path (when available) --- */
|
||||
if (fb_is_available()) {
|
||||
if (c == '\n') vt100_putc('\r');
|
||||
vt100_putc(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a null-terminated string to serial console
|
||||
*/
|
||||
void console_puts(const char *s) {
|
||||
if (!s) return;
|
||||
|
||||
while (*s) {
|
||||
console_putc(*s++);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t console_tx_count(void)
|
||||
{
|
||||
return g_console_tx_count;
|
||||
}
|
||||
|
||||
void console_ensure_line_start(void)
|
||||
{
|
||||
if (!g_line_start)
|
||||
{
|
||||
g_pending_line_close = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void console_cancel_deferred_line_start(void)
|
||||
{
|
||||
g_pending_line_close = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string with newline to serial console
|
||||
*/
|
||||
void console_println(const char *s) {
|
||||
console_puts(s);
|
||||
console_putc('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if data is available to read
|
||||
*/
|
||||
int console_poll(void) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
return inb(SERIAL_LINE_STATUS_PORT) & SERIAL_LSR_DATA_READY;
|
||||
#elif defined(__aarch64__)
|
||||
/* PL011_FR bit 4 = RXFE (RX FIFO empty); data available when RXFE == 0 */
|
||||
#define PL011_FR_RXFE (1u << 4)
|
||||
return !(*PL011_FR & PL011_FR_RXFE);
|
||||
#elif defined(__riscv)
|
||||
return *NS16550_LSR & NS16550_LSR_DR;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the framebuffer VT100 terminal and attach it to the console.
|
||||
* Safe to call with info == NULL or when the framebuffer base is zero (no-op).
|
||||
* After this call every console_putc / console_puts output is mirrored to
|
||||
* the screen; the serial UART continues to function concurrently.
|
||||
*/
|
||||
void console_fb_init(const FramebufferInfo *info, FbPixelFormat fmt)
|
||||
{
|
||||
if (!info || !info->base) return;
|
||||
fb_init(info, fmt);
|
||||
if (fb_is_available()) {
|
||||
vt100_init();
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_enable_ttf(void)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_enable_ttf();
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_scroll_back(uint32_t n)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_scroll_back(n);
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_scroll_fwd(uint32_t n)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_scroll_fwd(n);
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_toggle_graphics(void)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_toggle_graphics();
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_draw_cursor(void)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_draw_cursor();
|
||||
}
|
||||
}
|
||||
|
||||
void console_fb_erase_cursor(void)
|
||||
{
|
||||
if (fb_is_available()) {
|
||||
vt100_erase_cursor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single character from serial console (non-blocking)
|
||||
* Returns -1 if no character available
|
||||
*/
|
||||
int console_getc(void) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (!console_poll()) return -1;
|
||||
return (int)(unsigned char)inb(SERIAL_DATA_PORT);
|
||||
#elif defined(__aarch64__)
|
||||
if (!console_poll()) return -1;
|
||||
return (int)(*PL011_DR & 0xFF);
|
||||
#elif defined(__riscv)
|
||||
if (!console_poll()) return -1;
|
||||
return (int)(*NS16550_THR & 0xFF);
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,556 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* fdt.c - Minimal flattened-devicetree reader
|
||||
*
|
||||
* Devicetree Specification v0.4 §5. Everything in the blob is big-endian; the
|
||||
* kernel targets are little-endian, so every field goes through be32().
|
||||
*/
|
||||
|
||||
#include "starkernel/fdt.h"
|
||||
|
||||
/* Structure block tokens (DT spec §5.4.1) */
|
||||
#define FDT_BEGIN_NODE 0x00000001u
|
||||
#define FDT_END_NODE 0x00000002u
|
||||
#define FDT_PROP 0x00000003u
|
||||
#define FDT_NOP 0x00000004u
|
||||
#define FDT_END 0x00000009u
|
||||
|
||||
#define FDT_MAGIC 0xd00dfeedu
|
||||
|
||||
/* Header layout (DT spec §5.2), all fields big-endian u32 */
|
||||
typedef struct
|
||||
{
|
||||
uint32_t magic;
|
||||
uint32_t totalsize;
|
||||
uint32_t off_dt_struct;
|
||||
uint32_t off_dt_strings;
|
||||
uint32_t off_mem_rsvmap;
|
||||
uint32_t version;
|
||||
uint32_t last_comp_version;
|
||||
uint32_t boot_cpuid_phys;
|
||||
uint32_t size_dt_strings;
|
||||
uint32_t size_dt_struct;
|
||||
} fdt_header_t;
|
||||
|
||||
/**
|
||||
* @brief Byte-swap a big-endian 32-bit field from the blob.
|
||||
*
|
||||
* Read bytewise rather than as a @c uint32_t load: structure-block tokens are
|
||||
* only guaranteed 4-byte aligned relative to the block start, and property
|
||||
* values carry no alignment guarantee at all.
|
||||
*/
|
||||
static uint32_t be32(const void* p)
|
||||
{
|
||||
const unsigned char* b = (const unsigned char*)p;
|
||||
return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
|
||||
((uint32_t)b[2] << 8) | (uint32_t)b[3];
|
||||
}
|
||||
|
||||
/** @brief Freestanding string compare; returns 1 when equal. */
|
||||
static int str_eq(const char* a, const char* b)
|
||||
{
|
||||
while (*a && (*a == *b))
|
||||
{
|
||||
a++;
|
||||
b++;
|
||||
}
|
||||
return *a == *b;
|
||||
}
|
||||
|
||||
int fdt_valid(const void* fdt)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
uint32_t total, off_struct, size_struct, off_strings, size_strings;
|
||||
|
||||
if (!fdt) return 0;
|
||||
if (be32(&h->magic) != FDT_MAGIC) return 0;
|
||||
|
||||
total = be32(&h->totalsize);
|
||||
off_struct = be32(&h->off_dt_struct);
|
||||
size_struct = be32(&h->size_dt_struct);
|
||||
off_strings = be32(&h->off_dt_strings);
|
||||
size_strings = be32(&h->size_dt_strings);
|
||||
|
||||
/* Both blocks must lie inside the blob. Written as subtraction against
|
||||
* total so a wrapped sum cannot smuggle an out-of-range block past the
|
||||
* check. */
|
||||
if (total < sizeof(fdt_header_t)) return 0;
|
||||
if (off_struct > total || size_struct > total - off_struct) return 0;
|
||||
if (off_strings > total || size_strings > total - off_strings) return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
const void* fdt_find_prop(const void* fdt, const char* name, uint32_t* len_out)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
const unsigned char *base, *p, *end, *strings;
|
||||
uint32_t size_struct;
|
||||
|
||||
if (!fdt_valid(fdt) || !name) return (void*)0;
|
||||
|
||||
base = (const unsigned char*)fdt;
|
||||
size_struct = be32(&h->size_dt_struct);
|
||||
p = base + be32(&h->off_dt_struct);
|
||||
end = p + size_struct;
|
||||
strings = base + be32(&h->off_dt_strings);
|
||||
|
||||
while (p + 4 <= end)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_BEGIN_NODE)
|
||||
{
|
||||
/* NUL-terminated node name, padded to a 4-byte boundary */
|
||||
const unsigned char* q = p;
|
||||
while (q < end && *q) q++;
|
||||
if (q >= end) break;
|
||||
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_PROP)
|
||||
{
|
||||
uint32_t len, nameoff;
|
||||
const unsigned char* val;
|
||||
|
||||
if (p + 8 > end) break;
|
||||
len = be32(p);
|
||||
nameoff = be32(p + 4);
|
||||
p += 8;
|
||||
val = p;
|
||||
if (len > (uint32_t)(end - p)) break;
|
||||
|
||||
if (str_eq((const char*)(strings + nameoff), name))
|
||||
{
|
||||
if (len_out) *len_out = len;
|
||||
return (const void*)val;
|
||||
}
|
||||
|
||||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_END_NODE || token == FDT_NOP)
|
||||
{
|
||||
/* no payload */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* FDT_END, or a token this reader does not know: stop. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out)
|
||||
{
|
||||
uint32_t len = 0;
|
||||
const void* val = fdt_find_prop(fdt, name, &len);
|
||||
|
||||
if (!val || len != 4 || !out) return 0;
|
||||
*out = be32(val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test whether NUL-separated string list @p list (length @p len)
|
||||
* contains @p want as one of its entries.
|
||||
*
|
||||
* "compatible" properties are formatted as one or more NUL-terminated
|
||||
* strings concatenated (DT spec §2.3.1) -- each entry's own embedded NUL
|
||||
* is real property data, not synthesized, so str_eq() (which walks until
|
||||
* either side's NUL) terminates correctly at each entry's real boundary.
|
||||
*/
|
||||
static int compat_list_contains(const char* list, uint32_t len, const char* want)
|
||||
{
|
||||
uint32_t i = 0;
|
||||
|
||||
while (i < len)
|
||||
{
|
||||
const char* s = list + i;
|
||||
uint32_t slen = 0;
|
||||
while (i + slen < len && s[slen]) slen++;
|
||||
if (str_eq(s, want)) return 1;
|
||||
i += slen + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
const unsigned char *base, *p, *end, *strings;
|
||||
uint32_t size_struct;
|
||||
int fresh_node = 0; /* 1 = haven't yet seen a non-PROP token since the
|
||||
* last FDT_BEGIN_NODE -- i.e. still inside that
|
||||
* node's own leading property list, per the DT
|
||||
* spec's ordering guarantee (all of a node's
|
||||
* direct properties precede its children). */
|
||||
const void* node_body_start = (void*)0;
|
||||
|
||||
if (!fdt_valid(fdt) || !compatible) return (void*)0;
|
||||
|
||||
base = (const unsigned char*)fdt;
|
||||
size_struct = be32(&h->size_dt_struct);
|
||||
p = base + be32(&h->off_dt_struct);
|
||||
end = p + size_struct;
|
||||
strings = base + be32(&h->off_dt_strings);
|
||||
|
||||
while (p + 4 <= end)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_BEGIN_NODE)
|
||||
{
|
||||
const unsigned char* q = p;
|
||||
while (q < end && *q) q++;
|
||||
if (q >= end) break;
|
||||
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
fresh_node = 1;
|
||||
node_body_start = (const void*)p;
|
||||
}
|
||||
else if (token == FDT_PROP)
|
||||
{
|
||||
uint32_t len, nameoff;
|
||||
const unsigned char* val;
|
||||
|
||||
if (p + 8 > end) break;
|
||||
len = be32(p);
|
||||
nameoff = be32(p + 4);
|
||||
p += 8;
|
||||
val = p;
|
||||
if (len > (uint32_t)(end - p)) break;
|
||||
|
||||
if (fresh_node &&
|
||||
str_eq((const char*)(strings + nameoff), "compatible") &&
|
||||
compat_list_contains((const char*)val, len, compatible))
|
||||
{
|
||||
return node_body_start;
|
||||
}
|
||||
|
||||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||||
/* fresh_node stays 1 -- still within this same node's own
|
||||
* leading property list. */
|
||||
}
|
||||
else if (token == FDT_END_NODE)
|
||||
{
|
||||
fresh_node = 0;
|
||||
}
|
||||
else if (token == FDT_NOP)
|
||||
{
|
||||
/* no payload; does not end the leading property list */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* FDT_END, or a token this reader does not know: stop. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
const void* fdt_find_node_by_device_type(const void* fdt, const char* type)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
const unsigned char *base, *p, *end, *strings;
|
||||
uint32_t size_struct;
|
||||
int fresh_node = 0;
|
||||
const void* node_body_start = (void*)0;
|
||||
|
||||
if (!fdt_valid(fdt) || !type) return (void*)0;
|
||||
|
||||
base = (const unsigned char*)fdt;
|
||||
size_struct = be32(&h->size_dt_struct);
|
||||
p = base + be32(&h->off_dt_struct);
|
||||
end = p + size_struct;
|
||||
strings = base + be32(&h->off_dt_strings);
|
||||
|
||||
while (p + 4 <= end)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_BEGIN_NODE)
|
||||
{
|
||||
const unsigned char* q = p;
|
||||
while (q < end && *q) q++;
|
||||
if (q >= end) break;
|
||||
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
fresh_node = 1;
|
||||
node_body_start = (const void*)p;
|
||||
}
|
||||
else if (token == FDT_PROP)
|
||||
{
|
||||
uint32_t len, nameoff;
|
||||
const unsigned char* val;
|
||||
|
||||
if (p + 8 > end) break;
|
||||
len = be32(p);
|
||||
nameoff = be32(p + 4);
|
||||
p += 8;
|
||||
val = p;
|
||||
if (len > (uint32_t)(end - p)) break;
|
||||
|
||||
if (fresh_node &&
|
||||
str_eq((const char*)(strings + nameoff), "device_type") &&
|
||||
str_eq((const char*)val, type))
|
||||
{
|
||||
return node_body_start;
|
||||
}
|
||||
|
||||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_END_NODE)
|
||||
{
|
||||
fresh_node = 0;
|
||||
}
|
||||
else if (token == FDT_NOP)
|
||||
{
|
||||
/* no payload; does not end the leading property list */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* FDT_END, or a token this reader does not know: stop. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test whether a node-name string (blob text, possibly followed by
|
||||
* an `@unit-address`) matches @p want up to that boundary.
|
||||
*/
|
||||
static int node_name_eq(const char* node_name, const char* want)
|
||||
{
|
||||
while (*want && *node_name && *node_name == *want)
|
||||
{
|
||||
node_name++;
|
||||
want++;
|
||||
}
|
||||
if (*want) return 0;
|
||||
return *node_name == '\0' || *node_name == '@';
|
||||
}
|
||||
|
||||
const void* fdt_find_node_by_name(const void* fdt, const char* name)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
const unsigned char *base, *p, *end;
|
||||
uint32_t size_struct;
|
||||
|
||||
if (!fdt_valid(fdt) || !name) return (void*)0;
|
||||
|
||||
base = (const unsigned char*)fdt;
|
||||
size_struct = be32(&h->size_dt_struct);
|
||||
p = base + be32(&h->off_dt_struct);
|
||||
end = p + size_struct;
|
||||
/* Matching by the node's own inline name needs no strings-block
|
||||
* lookup, unlike the compatible/device_type finders above. */
|
||||
|
||||
while (p + 4 <= end)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_BEGIN_NODE)
|
||||
{
|
||||
const unsigned char* q = p;
|
||||
while (q < end && *q) q++;
|
||||
if (q >= end) break;
|
||||
|
||||
if (node_name_eq((const char*)p, name))
|
||||
{
|
||||
return (const void*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
|
||||
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_PROP)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
if (p + 8 > end) break;
|
||||
len = be32(p);
|
||||
p += 8;
|
||||
if (len > (uint32_t)(end - p)) break;
|
||||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_END_NODE || token == FDT_NOP)
|
||||
{
|
||||
/* no payload */
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Advance @p p past one complete node body (properties, then every
|
||||
* descendant, recursively via a depth counter) starting just inside it
|
||||
* (i.e. @p p is a node handle, right after its own FDT_BEGIN_NODE name),
|
||||
* stopping just after the matching FDT_END_NODE.
|
||||
*
|
||||
* Internal to @c fdt_next_child_node() — a node handle's own children may
|
||||
* have further descendants even though this reader's public node-lookup
|
||||
* functions never return anything but direct-child handles, so finding
|
||||
* the *next sibling* after a child still requires skipping past whatever
|
||||
* that child itself contains.
|
||||
*/
|
||||
static const unsigned char* skip_node_body(const unsigned char* p, const unsigned char* end)
|
||||
{
|
||||
int depth = 1;
|
||||
|
||||
while (p + 4 <= end && depth > 0)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_BEGIN_NODE)
|
||||
{
|
||||
const unsigned char* q = p;
|
||||
while (q < end && *q) q++;
|
||||
if (q >= end) return end;
|
||||
p = (const unsigned char*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
depth++;
|
||||
}
|
||||
else if (token == FDT_PROP)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
if (p + 8 > end) return end;
|
||||
len = be32(p);
|
||||
p += 8;
|
||||
if (len > (uint32_t)(end - p)) return end;
|
||||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_END_NODE)
|
||||
{
|
||||
depth--;
|
||||
}
|
||||
else if (token == FDT_NOP)
|
||||
{
|
||||
/* no payload */
|
||||
}
|
||||
else
|
||||
{
|
||||
return end;
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
const void* fdt_next_child_node(const void* fdt, const void* parent,
|
||||
const void* prev_child)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
const unsigned char *end, *p;
|
||||
|
||||
if (!fdt_valid(fdt) || !parent) return (void*)0;
|
||||
|
||||
end = (const unsigned char*)fdt + be32(&h->off_dt_struct) + be32(&h->size_dt_struct);
|
||||
p = prev_child ? skip_node_body((const unsigned char*)prev_child, end)
|
||||
: (const unsigned char*)parent;
|
||||
|
||||
/* If prev_child is NULL, p starts at parent's own leading property
|
||||
* list -- skip past those the same way fdt_find_prop_in_node() does.
|
||||
* If prev_child is non-NULL, skip_node_body() already consumed that
|
||||
* child's own properties and descendants, so this loop sees only
|
||||
* FDT_BEGIN_NODE (the next sibling) or FDT_END_NODE (parent closing)
|
||||
* immediately. */
|
||||
while (p + 4 <= end)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_BEGIN_NODE)
|
||||
{
|
||||
const unsigned char* q = p;
|
||||
while (q < end && *q) q++;
|
||||
if (q >= end) return (void*)0;
|
||||
return (const void*)(((uintptr_t)(q + 1) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_PROP)
|
||||
{
|
||||
uint32_t len;
|
||||
|
||||
if (p + 8 > end) break;
|
||||
len = be32(p);
|
||||
p += 8;
|
||||
if (len > (uint32_t)(end - p)) break;
|
||||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
else if (token == FDT_NOP)
|
||||
{
|
||||
/* no payload */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* FDT_END_NODE (parent closing), FDT_END, or unknown */
|
||||
return (void*)0;
|
||||
}
|
||||
}
|
||||
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
|
||||
const char* name, uint32_t* len_out)
|
||||
{
|
||||
const fdt_header_t* h = (const fdt_header_t*)fdt;
|
||||
const unsigned char *p, *end, *strings;
|
||||
|
||||
if (!fdt_valid(fdt) || !node || !name) return (void*)0;
|
||||
|
||||
p = (const unsigned char*)node;
|
||||
end = (const unsigned char*)fdt + be32(&h->off_dt_struct) + be32(&h->size_dt_struct);
|
||||
strings = (const unsigned char*)fdt + be32(&h->off_dt_strings);
|
||||
|
||||
/* node points just past this node's own FDT_BEGIN_NODE name -- scan
|
||||
* only its leading property list; stop at the first non-PROP/NOP
|
||||
* token (a child's FDT_BEGIN_NODE, or this node's own FDT_END_NODE),
|
||||
* matching fdt_find_node_by_compatible()'s own scoping rule. Never
|
||||
* descends into children, never continues into a sibling. */
|
||||
while (p + 4 <= end)
|
||||
{
|
||||
uint32_t token = be32(p);
|
||||
p += 4;
|
||||
|
||||
if (token == FDT_NOP) continue;
|
||||
if (token != FDT_PROP) break;
|
||||
|
||||
{
|
||||
uint32_t len, nameoff;
|
||||
const unsigned char* val;
|
||||
|
||||
if (p + 8 > end) break;
|
||||
len = be32(p);
|
||||
nameoff = be32(p + 4);
|
||||
p += 8;
|
||||
val = p;
|
||||
if (len > (uint32_t)(end - p)) break;
|
||||
|
||||
if (str_eq((const char*)(strings + nameoff), name))
|
||||
{
|
||||
if (len_out) *len_out = len;
|
||||
return (const void*)val;
|
||||
}
|
||||
|
||||
p = (const unsigned char*)(((uintptr_t)(p + len) + 3u) & ~(uintptr_t)3u);
|
||||
}
|
||||
}
|
||||
|
||||
return (void*)0;
|
||||
}
|
||||
@@ -0,0 +1,544 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* font_8x16.c — Embedded IBM CP437 VGA 8×16 bitmap font (256 glyphs × 16 bytes)
|
||||
*
|
||||
* Format: one byte per scanline, MSB = leftmost pixel.
|
||||
* Glyph for character N starts at byte N*16.
|
||||
*
|
||||
* Derived from the IBM PC BIOS ROM font, which is in the public domain.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* 256 glyphs × 16 rows = 4096 bytes */
|
||||
const uint8_t font_8x16_data[256 * 16] = {
|
||||
/* 0x00 NUL */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x01 ☺ */
|
||||
0x00,0x00,0x7E,0x81,0xA5,0x81,0x81,0xBD,0x99,0x81,0x81,0x7E,0x00,0x00,0x00,0x00,
|
||||
/* 0x02 ☻ */
|
||||
0x00,0x00,0x7E,0xFF,0xDB,0xFF,0xFF,0xC3,0xE7,0xFF,0xFF,0x7E,0x00,0x00,0x00,0x00,
|
||||
/* 0x03 ♥ */
|
||||
0x00,0x00,0x00,0x6C,0xFE,0xFE,0xFE,0xFE,0x7C,0x38,0x10,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x04 ♦ */
|
||||
0x00,0x00,0x00,0x10,0x38,0x7C,0xFE,0x7C,0x38,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x05 ♣ */
|
||||
0x00,0x00,0x18,0x3C,0x3C,0xE7,0xE7,0xFF,0xFF,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
|
||||
/* 0x06 ♠ */
|
||||
0x00,0x00,0x18,0x3C,0x7E,0xFF,0xFF,0x7E,0x18,0x18,0x18,0x3C,0x00,0x00,0x00,0x00,
|
||||
/* 0x07 • */
|
||||
0x00,0x00,0x00,0x00,0x18,0x3C,0x3C,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x08 ◘ */
|
||||
0xFF,0xFF,0xFF,0xFF,0xE7,0xC3,0xC3,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
/* 0x09 ○ */
|
||||
0x00,0x00,0x00,0x3C,0x66,0x42,0x42,0x66,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x0A ◙ */
|
||||
0xFF,0xFF,0xFF,0xC3,0x99,0xBD,0xBD,0x99,0xC3,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
/* 0x0B ♂ */
|
||||
0x00,0x00,0x1E,0x0E,0x1A,0x32,0x78,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x0C ♀ */
|
||||
0x00,0x00,0x3C,0x66,0x66,0x66,0x3C,0x18,0x7E,0x18,0x18,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x0D ♪ */
|
||||
0x00,0x00,0x0F,0x07,0x0F,0x7D,0xCC,0xCC,0xCC,0xCC,0x78,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x0E ♫ */
|
||||
0x00,0x00,0x3F,0x33,0x3F,0x30,0x30,0x70,0xF0,0xE0,0xC0,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x0F ☼ */
|
||||
0x00,0x00,0x7F,0x63,0x7F,0x63,0x63,0x67,0xE7,0xE6,0xC0,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x10 ► */
|
||||
0x00,0x00,0x00,0x18,0x0C,0xFE,0x0C,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x11 ◄ */
|
||||
0x00,0x00,0x00,0x30,0x60,0xFE,0x60,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x12 ↕ */
|
||||
0x00,0x00,0x00,0x00,0xFF,0x81,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x13 ‼ */
|
||||
0x00,0x00,0x00,0x22,0x77,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x14 ¶ */
|
||||
0x00,0x00,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x15 § */
|
||||
0x00,0x00,0xFF,0x81,0xFF,0x81,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x16 ▬ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x17 ↨ */
|
||||
0x00,0x00,0x10,0x38,0x7C,0x10,0x10,0x10,0x7C,0x38,0x10,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x18 ↑ */
|
||||
0x00,0x00,0x10,0x38,0x7C,0xFE,0x10,0x10,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x19 ↓ */
|
||||
0x00,0x00,0x10,0x10,0x10,0x10,0xFE,0x7C,0x38,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x1A → */
|
||||
0x00,0x00,0x00,0x10,0x30,0x7F,0x30,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x1B ← */
|
||||
0x00,0x00,0x00,0x08,0x0C,0xFE,0x0C,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x1C └ */
|
||||
0x00,0x00,0x00,0x00,0xC0,0xC0,0xC0,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x1D ↔ */
|
||||
0x00,0x00,0x00,0x24,0x66,0xFF,0x66,0x24,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x1E ▲ */
|
||||
0x00,0x00,0x00,0x10,0x38,0x7C,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x1F ▼ */
|
||||
0x00,0x00,0x00,0xFE,0x7C,0x38,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x20 */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x21 ! */
|
||||
0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0x22 " */
|
||||
0x00,0x63,0x63,0x63,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x23 # */
|
||||
0x00,0x00,0x00,0x36,0x36,0x7F,0x36,0x36,0x7F,0x36,0x36,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x24 $ */
|
||||
0x18,0x18,0x7C,0xC6,0xC2,0xC0,0x7C,0x06,0x06,0xC6,0x7C,0x18,0x18,0x00,0x00,0x00,
|
||||
/* 0x25 % */
|
||||
0x00,0x00,0x00,0x00,0xC2,0xC6,0x0C,0x18,0x30,0x66,0xC6,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x26 & */
|
||||
0x00,0x00,0x38,0x6C,0x6C,0x38,0x76,0xDC,0xCC,0xCC,0x76,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x27 ' */
|
||||
0x00,0x30,0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x28 ( */
|
||||
0x00,0x00,0x0C,0x18,0x30,0x30,0x30,0x30,0x30,0x18,0x0C,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x29 ) */
|
||||
0x00,0x00,0x30,0x18,0x0C,0x0C,0x0C,0x0C,0x0C,0x18,0x30,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x2A * */
|
||||
0x00,0x00,0x00,0x00,0x99,0x5A,0x3C,0xFF,0x3C,0x5A,0x99,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x2B + */
|
||||
0x00,0x00,0x00,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x2C , */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00,
|
||||
/* 0x2D - */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x2E . */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x2F / */
|
||||
0x00,0x00,0x00,0x03,0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x30 0 */
|
||||
0x00,0x00,0x3C,0x66,0xC3,0xE3,0xF3,0xDB,0xCF,0xC7,0x66,0x3C,0x00,0x00,0x00,0x00,
|
||||
/* 0x31 1 */
|
||||
0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x78,0x38,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0x32 2 */
|
||||
0x00,0x00,0xFF,0xC0,0xC0,0x60,0x30,0x18,0x0C,0x06,0xC3,0x7E,0x00,0x00,0x00,0x00,
|
||||
/* 0x33 3 */
|
||||
0x00,0x00,0x7E,0xC3,0x03,0x03,0x1C,0x03,0x03,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x34 4 */
|
||||
0x00,0x00,0x0E,0x0F,0x0F,0xFF,0x0C,0x0C,0x0C,0x6C,0x3C,0x1C,0x00,0x00,0x00,0x00,
|
||||
/* 0x35 5 */
|
||||
0x00,0x00,0x7E,0xC3,0x03,0x03,0x7E,0xC0,0xC0,0xC0,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x36 6 */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC0,0xFE,0xC3,0xC3,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x37 7 */
|
||||
0x00,0x00,0x18,0x18,0x18,0x18,0x0C,0x06,0x03,0xC3,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x38 8 */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0x7E,0xC3,0xC3,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x39 9 */
|
||||
0x00,0x00,0x7E,0xC3,0x03,0x03,0x7F,0xC3,0xC3,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x3A : */
|
||||
0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x3B ; */
|
||||
0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x30,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x3C < */
|
||||
0x00,0x00,0x06,0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x3D = */
|
||||
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x3E > */
|
||||
0x00,0x00,0x60,0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x60,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x3F ? */
|
||||
0x00,0x00,0x18,0x00,0x18,0x18,0x0C,0x06,0x03,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x40 @ */
|
||||
0x00,0x00,0x7E,0x03,0x03,0xDB,0xDB,0xDB,0x7B,0x43,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x41 A */
|
||||
0x00,0x00,0xC3,0xC3,0xC3,0xC3,0xFF,0xC3,0xC3,0x66,0x3C,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0x42 B */
|
||||
0x00,0x00,0xFE,0xC3,0xC3,0xC3,0xFE,0xC3,0xC3,0xC3,0xFE,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x43 C */
|
||||
0x00,0x00,0x3C,0x66,0xC3,0xC0,0xC0,0xC0,0xC3,0x66,0x3C,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x44 D */
|
||||
0x00,0x00,0xF8,0xCC,0xC6,0xC3,0xC3,0xC3,0xC6,0xCC,0xF8,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x45 E */
|
||||
0x00,0x00,0xFF,0xC0,0xC0,0xC0,0xFC,0xC0,0xC0,0xC0,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x46 F */
|
||||
0x00,0x00,0xC0,0xC0,0xC0,0xC0,0xFC,0xC0,0xC0,0xC0,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x47 G */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC0,0xCF,0xC3,0xC3,0x66,0x3C,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x48 H */
|
||||
0x00,0x00,0xC3,0xC3,0xC3,0xC3,0xFF,0xC3,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x49 I */
|
||||
0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x4A J */
|
||||
0x00,0x00,0x7C,0xEE,0xC6,0x06,0x06,0x06,0x06,0x06,0x1E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x4B K */
|
||||
0x00,0x00,0xC3,0xC6,0xCC,0xD8,0xF0,0xD8,0xCC,0xC6,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x4C L */
|
||||
0x00,0x00,0xFF,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x4D M */
|
||||
0x00,0x00,0xC3,0xC3,0xC3,0xDB,0xFF,0xFF,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x4E N */
|
||||
0x00,0x00,0xC3,0xC3,0xE3,0xF3,0xDB,0xCF,0xC7,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x4F O */
|
||||
0x00,0x00,0x3C,0x66,0xC3,0xC3,0xC3,0xC3,0xC3,0x66,0x3C,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x50 P */
|
||||
0x00,0x00,0xC0,0xC0,0xC0,0xFE,0xC3,0xC3,0xC3,0xC3,0xFE,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x51 Q */
|
||||
0x00,0x00,0x77,0x66,0xC3,0xC3,0xC3,0xC3,0xC3,0x66,0x3C,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x52 R */
|
||||
0x00,0x00,0xC3,0xC6,0xCC,0xFC,0xC3,0xC3,0xC3,0xC3,0xFE,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x53 S */
|
||||
0x00,0x00,0x7E,0xC3,0x03,0x03,0x7E,0xC0,0xC0,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x54 T */
|
||||
0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x55 U */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x56 V */
|
||||
0x00,0x00,0x18,0x3C,0x66,0x66,0xC3,0xC3,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x57 W */
|
||||
0x00,0x00,0xC3,0xE7,0xFF,0xFF,0xDB,0xC3,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x58 X */
|
||||
0x00,0x00,0xC3,0xC3,0x66,0x3C,0x18,0x3C,0x66,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x59 Y */
|
||||
0x00,0x00,0x18,0x18,0x18,0x18,0x3C,0x66,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x5A Z */
|
||||
0x00,0x00,0xFF,0xC3,0x86,0x0C,0x18,0x30,0x61,0xC3,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x5B [ */
|
||||
0x00,0x00,0x3C,0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x3C,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x5C \ */
|
||||
0x00,0x00,0x00,0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x03,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x5D ] */
|
||||
0x00,0x00,0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x5E ^ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xC3,0x66,0x3C,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x5F _ */
|
||||
0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x60 ` */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x18,0x30,0x60,0x00,0x00,0x00,0x00,
|
||||
/* 0x61 a */
|
||||
0x00,0x00,0x7F,0xC3,0xC3,0x7F,0x03,0x03,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x62 b */
|
||||
0x00,0x00,0xFE,0xC3,0xC3,0xC3,0xFE,0xC0,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x63 c */
|
||||
0x00,0x00,0x7E,0xC3,0xC0,0xC0,0xC0,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x64 d */
|
||||
0x00,0x00,0x7F,0xC3,0xC3,0xC3,0x7F,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x65 e */
|
||||
0x00,0x00,0x7E,0xC0,0xC0,0xFF,0xC3,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x66 f */
|
||||
0x00,0x00,0x30,0x30,0x30,0x30,0xFC,0x30,0x30,0x36,0x1C,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x67 g */
|
||||
0x7E,0xC3,0x03,0x03,0x7F,0xC3,0xC3,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x68 h */
|
||||
0x00,0x00,0xC3,0xC3,0xC3,0xC3,0xFE,0xC0,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x69 i */
|
||||
0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x18,0x78,0x00,0x18,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x6A j */
|
||||
0x7C,0xC6,0x06,0x06,0x06,0x06,0x06,0x06,0x1E,0x00,0x06,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x6B k */
|
||||
0x00,0x00,0xC3,0xC6,0xCC,0xF0,0xCC,0xC6,0xC3,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x6C l */
|
||||
0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x78,0x38,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x6D m */
|
||||
0x00,0x00,0xDB,0xDB,0xDB,0xDB,0xFF,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x6E n */
|
||||
0x00,0x00,0xC3,0xC3,0xC3,0xC3,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x6F o */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x70 p */
|
||||
0xC0,0xC0,0xFE,0xC3,0xC3,0xC3,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x71 q */
|
||||
0x03,0x03,0x7F,0xC3,0xC3,0xC3,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x72 r */
|
||||
0x00,0x00,0x30,0x30,0x30,0x30,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x73 s */
|
||||
0x00,0x00,0x7E,0xC3,0x03,0x7E,0xC0,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x74 t */
|
||||
0x00,0x00,0x1C,0x36,0x30,0x30,0x30,0x30,0xFC,0x30,0x30,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x75 u */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x76 v */
|
||||
0x00,0x00,0x18,0x3C,0x66,0x66,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x77 w */
|
||||
0x00,0x00,0xC3,0xE7,0xFF,0xDB,0xDB,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x78 x */
|
||||
0x00,0x00,0xC3,0xC3,0x66,0x3C,0x3C,0x66,0xC3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x79 y */
|
||||
0x7E,0xC3,0x03,0x3F,0xC3,0xC3,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x7A z */
|
||||
0x00,0x00,0xFF,0xC0,0x60,0x30,0x18,0x0C,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x7B { */
|
||||
0x00,0x00,0x0E,0x18,0x18,0x18,0x70,0x18,0x18,0x18,0x0E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x7C | */
|
||||
0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0x7D } */
|
||||
0x00,0x00,0x70,0x18,0x18,0x18,0x0E,0x18,0x18,0x18,0x70,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x7E ~ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xDB,0x76,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x7F DEL */
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
/* 0x80 Ç */
|
||||
0x00,0x00,0x3E,0x63,0x60,0x60,0x60,0x63,0x3E,0x18,0x0C,0x1C,0x00,0x00,0x00,0x00,
|
||||
/* 0x81 ü */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0x66,0x24,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x82 é */
|
||||
0x00,0x00,0x7E,0xC0,0xFE,0xC3,0xC3,0xFE,0x00,0x0C,0x18,0x30,0x00,0x00,0x00,0x00,
|
||||
/* 0x83 â */
|
||||
0x00,0x00,0x7F,0xC3,0xC3,0x7F,0x03,0x7E,0x00,0x3C,0x66,0x3C,0x00,0x00,0x00,0x00,
|
||||
/* 0x84 ä */
|
||||
0x00,0x00,0x7F,0xC3,0xC3,0x7F,0x03,0x7E,0x00,0x66,0x24,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x85 à */
|
||||
0x00,0x00,0x7F,0xC3,0xC3,0x7F,0x03,0x7E,0x00,0x30,0x18,0x0C,0x00,0x00,0x00,0x00,
|
||||
/* 0x86 å */
|
||||
0x00,0x00,0x7F,0xC3,0xC3,0x7F,0x03,0x7E,0x00,0x18,0x24,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0x87 ç */
|
||||
0x00,0x00,0x3E,0x63,0x60,0x60,0x60,0x63,0x3E,0x18,0x0C,0x1C,0x00,0x00,0x00,0x00,
|
||||
/* 0x88 ê */
|
||||
0x00,0x00,0x7E,0xC0,0xFE,0xC3,0xC3,0xFE,0x00,0x18,0x3C,0x66,0x00,0x00,0x00,0x00,
|
||||
/* 0x89 ë */
|
||||
0x00,0x00,0x7E,0xC0,0xFE,0xC3,0xC3,0xFE,0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x8A è */
|
||||
0x00,0x00,0x7E,0xC0,0xFE,0xC3,0xC3,0xFE,0x00,0x18,0x30,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x8B ï */
|
||||
0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x78,0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x8C î */
|
||||
0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x78,0x00,0x18,0x3C,0x66,0x00,0x00,0x00,0x00,
|
||||
/* 0x8D ì */
|
||||
0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x78,0x00,0x18,0x30,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x8E Ä */
|
||||
0x00,0x00,0xC3,0xC3,0xFF,0xC3,0xC3,0x66,0x3C,0x66,0x24,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x8F Å */
|
||||
0x00,0x00,0xC3,0xC3,0xFF,0xC3,0xC3,0x66,0x3C,0x18,0x24,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0x90 É */
|
||||
0x00,0x00,0xFF,0xC0,0xFC,0xC0,0xFF,0x00,0x18,0x30,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x91 æ */
|
||||
0x00,0x00,0x7E,0xDB,0xDB,0xFF,0xDB,0xDB,0xDE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x92 Æ */
|
||||
0x00,0x00,0xFF,0xDB,0xDB,0xFF,0xDB,0xDB,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x93 ô */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0x7E,0x00,0x18,0x3C,0x00,0x00,0x00,0x00,
|
||||
/* 0x94 ö */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0x7E,0x00,0x66,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x95 ò */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0x7E,0x00,0x30,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0x96 û */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0x00,0x18,0x3C,0x00,0x00,0x00,0x00,
|
||||
/* 0x97 ù */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0x00,0x30,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0x98 ÿ */
|
||||
0x7E,0xC3,0x03,0x3F,0xC3,0xC3,0xC3,0x7E,0x00,0x66,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x99 Ö */
|
||||
0x00,0x00,0x3C,0x66,0xC3,0xC3,0xC3,0xC3,0xC3,0x66,0x3C,0x00,0x66,0x00,0x00,0x00,
|
||||
/* 0x9A Ü */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0x00,0x66,0x00,0x00,0x00,
|
||||
/* 0x9B ¢ */
|
||||
0x00,0x00,0x7E,0x66,0x60,0x7E,0x66,0x66,0x7E,0x06,0x66,0x3C,0x00,0x00,0x00,0x00,
|
||||
/* 0x9C £ */
|
||||
0x00,0x00,0xFF,0x18,0x18,0x18,0x7E,0x18,0x18,0x1A,0x0C,0x38,0x00,0x00,0x00,0x00,
|
||||
/* 0x9D ¥ */
|
||||
0x00,0x00,0x18,0x18,0x7E,0x18,0x7E,0x18,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x9E ₧ */
|
||||
0x00,0x00,0xC0,0xC0,0xCE,0xD3,0xD3,0xCE,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0x9F ƒ */
|
||||
0x00,0x00,0x18,0x30,0x30,0x30,0x7C,0x30,0x30,0x33,0x1E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xA0 á */
|
||||
0x00,0x00,0x7F,0xC3,0xC3,0x7F,0x03,0x7E,0x00,0x0C,0x18,0x30,0x00,0x00,0x00,0x00,
|
||||
/* 0xA1 í */
|
||||
0x00,0x00,0x7E,0x18,0x18,0x18,0x18,0x78,0x00,0x0C,0x18,0x30,0x00,0x00,0x00,0x00,
|
||||
/* 0xA2 ó */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0x7E,0x00,0x0C,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0xA3 ú */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0x00,0x0C,0x18,0x00,0x00,0x00,0x00,
|
||||
/* 0xA4 ñ */
|
||||
0x00,0x00,0xC3,0xC3,0xC3,0xC3,0xFE,0x00,0x66,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xA5 Ñ */
|
||||
0x00,0x00,0xC3,0xC3,0xE3,0xF3,0xDB,0xCF,0xC7,0xC3,0xC3,0x00,0x66,0x3C,0x00,0x00,
|
||||
/* 0xA6 ª */
|
||||
0x00,0x00,0x38,0x6C,0x6C,0x38,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xA7 º */
|
||||
0x00,0x00,0x38,0x6C,0x6C,0x38,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xA8 ¿ */
|
||||
0x00,0x00,0x7E,0xC3,0xC0,0xC0,0x60,0x18,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xA9 ⌐ */
|
||||
0x00,0x00,0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xAA ¬ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x3E,0x06,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xAB ½ */
|
||||
0x00,0x00,0x60,0x66,0x6C,0x18,0x30,0x60,0xFE,0xC6,0x0C,0x1E,0x00,0x00,0x00,0x00,
|
||||
/* 0xAC ¼ */
|
||||
0x00,0x00,0x60,0x66,0x6C,0x18,0x30,0x66,0xCE,0x96,0x3E,0x06,0x00,0x00,0x00,0x00,
|
||||
/* 0xAD ¡ */
|
||||
0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xAE « */
|
||||
0x00,0x00,0x00,0x00,0x36,0x6C,0xD8,0x6C,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xAF » */
|
||||
0x00,0x00,0x00,0x00,0xD8,0x6C,0x36,0x6C,0xD8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xB0 ░ */
|
||||
0x11,0x44,0x11,0x44,0x11,0x44,0x11,0x44,0x11,0x44,0x11,0x44,0x11,0x44,0x11,0x44,
|
||||
/* 0xB1 ▒ */
|
||||
0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,
|
||||
/* 0xB2 ▓ */
|
||||
0xDD,0x77,0xDD,0x77,0xDD,0x77,0xDD,0x77,0xDD,0x77,0xDD,0x77,0xDD,0x77,0xDD,0x77,
|
||||
/* 0xB3 │ */
|
||||
0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xB4 ┤ */
|
||||
0x18,0x18,0x18,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xB5 ╡ */
|
||||
0x18,0x18,0x18,0x18,0xF8,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xB6 ╢ */
|
||||
0x36,0x36,0x36,0x36,0xF6,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xB7 ╖ */
|
||||
0x00,0x00,0x00,0x00,0xFE,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xB8 ╕ */
|
||||
0x00,0x00,0x00,0x00,0xF8,0x18,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xB9 ╣ */
|
||||
0x36,0x36,0x36,0x36,0xF6,0x06,0xF6,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xBA ║ */
|
||||
0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xBB ╗ */
|
||||
0x00,0x00,0x00,0x00,0xFE,0x06,0xF6,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xBC ╝ */
|
||||
0x36,0x36,0x36,0x36,0xF6,0x06,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xBD ╜ */
|
||||
0x36,0x36,0x36,0x36,0x36,0x36,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xBE ╛ */
|
||||
0x18,0x18,0x18,0x18,0xF8,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xBF ┐ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xC0 └ */
|
||||
0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xC1 ┴ */
|
||||
0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xC2 ┬ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xC3 ├ */
|
||||
0x18,0x18,0x18,0x18,0x18,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xC4 ─ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xC5 ┼ */
|
||||
0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xC6 ╞ */
|
||||
0x18,0x18,0x18,0x18,0x1F,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xC7 ╟ */
|
||||
0x36,0x36,0x36,0x36,0x36,0x36,0x37,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xC8 ╚ */
|
||||
0x36,0x36,0x36,0x36,0x37,0x30,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xC9 ╔ */
|
||||
0x00,0x00,0x00,0x00,0x3F,0x30,0x37,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xCA ╩ */
|
||||
0x36,0x36,0x36,0x36,0xF7,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xCB ╦ */
|
||||
0x00,0x00,0x00,0x00,0xFF,0x00,0xF7,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xCC ╠ */
|
||||
0x36,0x36,0x36,0x36,0x37,0x30,0x37,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xCD ═ */
|
||||
0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xCE ╬ */
|
||||
0x36,0x36,0x36,0x36,0xF7,0x00,0xF7,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xCF ╧ */
|
||||
0x18,0x18,0x18,0x18,0xFF,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xD0 ╨ */
|
||||
0x18,0x18,0x18,0x18,0x18,0x18,0xFF,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xD1 ╤ */
|
||||
0x00,0x00,0x00,0x00,0xFF,0x00,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xD2 ╥ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xD3 ╙ */
|
||||
0x36,0x36,0x36,0x36,0x36,0x36,0x3F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xD4 ╘ */
|
||||
0x18,0x18,0x18,0x18,0x18,0x1F,0x18,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xD5 ╒ */
|
||||
0x00,0x00,0x00,0x00,0x1F,0x18,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xD6 ╓ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x3F,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xD7 ╫ */
|
||||
0x36,0x36,0x36,0x36,0x36,0xFF,0x36,0xFF,0x36,0x36,0x36,0x36,0x36,0x36,0x36,0x36,
|
||||
/* 0xD8 ╪ */
|
||||
0x18,0x18,0x18,0x18,0xFF,0x18,0xFF,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xD9 ┘ */
|
||||
0x18,0x18,0x18,0x18,0x18,0x18,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xDA ┌ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,
|
||||
/* 0xDB █ */
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
/* 0xDC ▄ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
|
||||
/* 0xDD ▌ */
|
||||
0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,
|
||||
/* 0xDE ▐ */
|
||||
0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,
|
||||
/* 0xDF ▀ */
|
||||
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE0 α */
|
||||
0x00,0x00,0xFE,0xDB,0xDB,0xDB,0xDB,0xDB,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE1 ß */
|
||||
0x00,0x00,0x7E,0xDB,0xDB,0xDB,0xFE,0xDB,0xDB,0xDB,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE2 Γ */
|
||||
0x00,0x00,0xC0,0xC0,0xC0,0xC0,0xFC,0xC6,0xC6,0xC6,0xFE,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE3 π */
|
||||
0x00,0x00,0xC3,0xC3,0xC3,0xC3,0xFE,0xC3,0xC3,0xC3,0xFE,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE4 Σ */
|
||||
0x00,0x00,0xFF,0xC0,0x60,0x30,0x18,0x30,0x60,0xC0,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE5 σ */
|
||||
0x00,0x00,0x7F,0xDB,0xDB,0xDB,0x7B,0x1B,0x1B,0x1B,0x1B,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE6 µ */
|
||||
0x00,0x00,0xC0,0xC0,0x7E,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE7 τ */
|
||||
0x00,0x00,0x18,0x18,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE8 Φ */
|
||||
0x00,0x00,0x7E,0xDB,0xDB,0x7E,0x18,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xE9 Θ */
|
||||
0x00,0x00,0x7E,0xC3,0xC3,0xFF,0xC3,0xC3,0xC3,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xEA Ω */
|
||||
0x00,0x00,0x66,0x66,0x3C,0x3C,0x18,0x7E,0xDB,0xDB,0x7E,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xEB δ */
|
||||
0x00,0x00,0x3E,0x63,0x38,0x6C,0xC6,0xC6,0xC6,0x6C,0x38,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xEC ∞ */
|
||||
0x00,0x00,0x00,0x00,0x76,0xDC,0xC8,0xDC,0x76,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xED φ */
|
||||
0x00,0x06,0x0C,0x7E,0xDB,0xDB,0xDB,0x7E,0x60,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xEE ε */
|
||||
0x00,0x00,0x7C,0xC6,0x06,0x3C,0x06,0x06,0xC6,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xEF ∩ */
|
||||
0x00,0x00,0xC6,0xC6,0xC6,0xC6,0xFE,0xC6,0xC6,0xC6,0xC6,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF0 ≡ */
|
||||
0x00,0x00,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF1 ± */
|
||||
0x00,0x00,0x18,0x18,0x18,0xFF,0x18,0x18,0x18,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF2 ≥ */
|
||||
0x00,0x00,0xC0,0x60,0x30,0x18,0x30,0x60,0xC0,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF3 ≤ */
|
||||
0x00,0x00,0x06,0x0C,0x18,0x30,0x18,0x0C,0x06,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF4 ⌠ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xC0,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,
|
||||
/* 0xF5 ⌡ */
|
||||
0x00,0x00,0x03,0x03,0x03,0x03,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF6 ÷ */
|
||||
0x00,0x00,0x00,0x18,0x18,0x00,0xFF,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF7 ≈ */
|
||||
0x00,0x00,0x00,0x76,0xDC,0x00,0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF8 ° */
|
||||
0x00,0x00,0x00,0x38,0x6C,0x6C,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xF9 ∙ */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xFA · */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xFB √ */
|
||||
0x00,0x00,0x06,0x06,0x06,0x06,0x1E,0x36,0x66,0xC6,0xFE,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xFC ⁿ */
|
||||
0x00,0x00,0x00,0xDB,0xDB,0xDB,0xDB,0xFE,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xFD ² */
|
||||
0x00,0x00,0x7E,0xC3,0x06,0x3C,0x60,0xC3,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xFE ■ */
|
||||
0x00,0x00,0x00,0x00,0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
/* 0xFF NBSP */
|
||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||
};
|
||||
|
||||
/**
|
||||
* Return pointer to the 16-byte glyph bitmap for character ch.
|
||||
* Caller must not modify the returned data.
|
||||
*/
|
||||
const uint8_t *font_8x16_glyph(uint8_t ch)
|
||||
{
|
||||
return &font_8x16_data[(uint32_t)ch * 16u];
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "starkernel/hal/hal.h"
|
||||
#include "vm_host.h"
|
||||
#include "starkernel/timer.h"
|
||||
#include "starkernel/console.h"
|
||||
#include "starkernel/arch.h"
|
||||
#include "starkernel/vmm.h"
|
||||
#include "dictionary_management.h"
|
||||
|
||||
extern char __text_start[];
|
||||
extern char __text_end[];
|
||||
|
||||
/* Force PC-relative address load for linker-defined symbols.
|
||||
* With -fPIC, the compiler generates GOT-relative loads for extern symbols,
|
||||
* which loads CONTENTS instead of ADDRESS. These wrappers use inline asm
|
||||
* to ensure we get the actual addresses.
|
||||
*
|
||||
* Architecture-specific implementations:
|
||||
* - x86_64: LEA with RIP-relative addressing
|
||||
* - AArch64: ADRP + ADD for page-relative addressing
|
||||
* - RISC-V64: LA pseudo-instruction (AUIPC + ADDI)
|
||||
*/
|
||||
/**
|
||||
* @brief Return the runtime address of the @c __text_start linker symbol (x86_64).
|
||||
*
|
||||
* Uses a PC-relative @c LEA to load the address directly, bypassing the GOT
|
||||
* indirection that @c -fPIC would otherwise introduce for @c extern symbols.
|
||||
* Only compiled on x86_64 / _M_X64 targets.
|
||||
*
|
||||
* @return Physical address of the beginning of the kernel @c .text section
|
||||
*/
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
static inline uint64_t get_text_start_addr(void) {
|
||||
uint64_t addr;
|
||||
__asm__ volatile("leaq __text_start(%%rip), %0" : "=r"(addr));
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the runtime address of the @c __text_end linker symbol (x86_64).
|
||||
*
|
||||
* PC-relative @c LEA counterpart to @c get_text_start_addr() for the end of
|
||||
* the kernel @c .text section.
|
||||
*
|
||||
* @return Physical address one byte past the end of the kernel @c .text section
|
||||
*/
|
||||
static inline uint64_t get_text_end_addr(void) {
|
||||
uint64_t addr;
|
||||
__asm__ volatile("leaq __text_end(%%rip), %0" : "=r"(addr));
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the runtime address of @c __text_start (AArch64).
|
||||
*
|
||||
* Uses @c ADRP + @c ADD to form the page-relative address of @c __text_start,
|
||||
* avoiding @c -fPIC GOT indirection on AArch64 / _M_ARM64 targets.
|
||||
*
|
||||
* @return Physical address of the beginning of the kernel @c .text section
|
||||
*/
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
static inline uint64_t get_text_start_addr(void) {
|
||||
uint64_t addr;
|
||||
__asm__ volatile(
|
||||
"adrp %0, __text_start\n\t"
|
||||
"add %0, %0, :lo12:__text_start"
|
||||
: "=r"(addr)
|
||||
);
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the runtime address of @c __text_end (AArch64).
|
||||
*
|
||||
* @c ADRP + @c ADD counterpart to @c get_text_start_addr() for the end of
|
||||
* the kernel @c .text section on AArch64 / _M_ARM64 targets.
|
||||
*
|
||||
* @return Physical address one byte past the end of the kernel @c .text section
|
||||
*/
|
||||
static inline uint64_t get_text_end_addr(void) {
|
||||
uint64_t addr;
|
||||
__asm__ volatile(
|
||||
"adrp %0, __text_end\n\t"
|
||||
"add %0, %0, :lo12:__text_end"
|
||||
: "=r"(addr)
|
||||
);
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the runtime address of @c __text_start (RISC-V 64-bit).
|
||||
*
|
||||
* Uses the @c LA pseudo-instruction (expands to @c AUIPC + @c ADDI) to load
|
||||
* the PC-relative address of @c __text_start without GOT indirection.
|
||||
*
|
||||
* @return Physical address of the beginning of the kernel @c .text section
|
||||
*/
|
||||
#elif defined(__riscv) && (__riscv_xlen == 64)
|
||||
static inline uint64_t get_text_start_addr(void) {
|
||||
uint64_t addr;
|
||||
__asm__ volatile("la %0, __text_start" : "=r"(addr));
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the runtime address of @c __text_end (RISC-V 64-bit).
|
||||
*
|
||||
* @c LA counterpart to @c get_text_start_addr() for the end of the kernel
|
||||
* @c .text section on RISC-V 64-bit targets.
|
||||
*
|
||||
* @return Physical address one byte past the end of the kernel @c .text section
|
||||
*/
|
||||
static inline uint64_t get_text_end_addr(void) {
|
||||
uint64_t addr;
|
||||
__asm__ volatile("la %0, __text_end" : "=r"(addr));
|
||||
return addr;
|
||||
}
|
||||
|
||||
#else
|
||||
#error "Unsupported architecture for get_text_*_addr()"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint64_t start;
|
||||
uint64_t end;
|
||||
const char *name;
|
||||
} hal_exec_region_t;
|
||||
|
||||
#define HAL_MAX_EXEC_REGIONS 8
|
||||
static hal_exec_region_t hal_exec_regions[HAL_MAX_EXEC_REGIONS];
|
||||
static size_t hal_exec_region_count = 0;
|
||||
static bool hal_exec_range_frozen = false;
|
||||
static void hal_print_hex64(uint64_t value);
|
||||
|
||||
/**
|
||||
* @brief Return the kernel VM host-services vtable.
|
||||
*
|
||||
* Thin inline wrapper around @c sk_host_services() for use within this
|
||||
* translation unit.
|
||||
*
|
||||
* @return Pointer to the active @c VMHostServices struct, or NULL if not initialised
|
||||
*/
|
||||
static inline const VMHostServices *sk_hal_services(void) {
|
||||
return sk_host_services();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add an address range to the HAL executable-region whitelist.
|
||||
*
|
||||
* Inserts @c [start, end) into @c hal_exec_regions[] if not already present
|
||||
* and the table has capacity (@c HAL_MAX_EXEC_REGIONS). Duplicate ranges
|
||||
* (matching both @c start and @c end) are silently ignored. No-ops on
|
||||
* zero-size ranges or when the table is full.
|
||||
*
|
||||
* @param start Inclusive start address of the executable region
|
||||
* @param end Exclusive end address of the executable region; must be > @c start
|
||||
* @param name Human-readable label for diagnostic output (may be NULL)
|
||||
*/
|
||||
static void hal_exec_register(uint64_t start, uint64_t end, const char *name) {
|
||||
if (start >= end || hal_exec_region_count >= HAL_MAX_EXEC_REGIONS) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < hal_exec_region_count; ++i) {
|
||||
if (hal_exec_regions[i].start == start && hal_exec_regions[i].end == end) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
hal_exec_regions[hal_exec_region_count].start = start;
|
||||
hal_exec_regions[hal_exec_region_count].end = end;
|
||||
hal_exec_regions[hal_exec_region_count].name = name;
|
||||
hal_exec_region_count++;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Find the whitelisted executable region that contains @c addr.
|
||||
*
|
||||
* Performs a linear search over @c hal_exec_regions[].
|
||||
*
|
||||
* @param addr Address to locate
|
||||
* @return Pointer to the matching @c hal_exec_region_t, or NULL if not found
|
||||
*/
|
||||
static const hal_exec_region_t *hal_exec_region_for(uint64_t addr) {
|
||||
for (size_t i = 0; i < hal_exec_region_count; ++i) {
|
||||
const hal_exec_region_t *region = &hal_exec_regions[i];
|
||||
if (addr >= region->start && addr < region->end) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Publicly register an address range as an allowed execution target.
|
||||
*
|
||||
* Delegates to @c hal_exec_register(). Called by @c sk_hal_init() for the
|
||||
* kernel @c .text section, and may be called by capsule or module loaders to
|
||||
* whitelist their own code before the range is frozen by @c sk_hal_freeze_exec_range().
|
||||
*
|
||||
* @param start Inclusive start address of the executable region
|
||||
* @param end Exclusive end address; must be > @c start
|
||||
* @param name Diagnostic label (may be NULL)
|
||||
*/
|
||||
void sk_hal_whitelist_exec_region(uint64_t start, uint64_t end, const char *name) {
|
||||
hal_exec_register(start, end, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test whether @c addr falls within a whitelisted executable region.
|
||||
*
|
||||
* @param addr Address to test
|
||||
* @return @c true if the address is inside any registered region, @c false otherwise
|
||||
*/
|
||||
static bool hal_exec_addr_allowed(uint64_t addr) {
|
||||
return hal_exec_region_for(addr) != NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Freeze the executable-region whitelist and print the final set.
|
||||
*
|
||||
* Sets @c hal_exec_range_frozen = true on first call; subsequent calls are
|
||||
* no-ops. Prints all registered regions to the kernel console for audit.
|
||||
* After freezing, @c sk_hal_is_executable_ptr() rejects any pointer not in
|
||||
* the frozen set, so this should be called after all capsules and modules
|
||||
* have registered their code regions during boot.
|
||||
*/
|
||||
void sk_hal_freeze_exec_range(void) {
|
||||
if (!hal_exec_range_frozen) {
|
||||
hal_exec_range_frozen = true;
|
||||
console_puts("[HAL][exec] regions frozen:");
|
||||
console_println("");
|
||||
if (hal_exec_region_count == 0) {
|
||||
console_println(" (none registered)");
|
||||
}
|
||||
for (size_t i = 0; i < hal_exec_region_count; ++i) {
|
||||
const hal_exec_region_t *region = &hal_exec_regions[i];
|
||||
console_puts(" ");
|
||||
console_puts(region->name ? region->name : "region");
|
||||
console_puts(": [");
|
||||
hal_print_hex64(region->start);
|
||||
console_puts(", ");
|
||||
hal_print_hex64(region->end);
|
||||
console_println(")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Print a 64-bit value as a zero-padded 18-character hex string to the console.
|
||||
*
|
||||
* Formats @c value as @c "0xNNNNNNNNNNNNNNNN" (16 hex digits, zero-padded)
|
||||
* and outputs it via @c console_puts(). No newline is appended.
|
||||
*
|
||||
* @param value 64-bit value to print
|
||||
*/
|
||||
static void hal_print_hex64(uint64_t value) {
|
||||
char buf[19];
|
||||
buf[0] = '0';
|
||||
buf[1] = 'x';
|
||||
buf[18] = '\0';
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
uint8_t nibble = (uint8_t)((value >> ((15 - i) * 4)) & 0xF);
|
||||
buf[i + 2] = (nibble < 10) ? (char)('0' + nibble) : (char)('a' + nibble - 10);
|
||||
}
|
||||
console_puts(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Log an execution-target (XT) validation failure to the kernel console.
|
||||
*
|
||||
* Prints @c "[HAL][XT] REASON ptr=ADDR\n" using @c console_puts() and
|
||||
* @c hal_print_hex64(). Called by @c sk_hal_is_executable_ptr() on each
|
||||
* rejection path so each failure mode is distinguishable in the boot log.
|
||||
*
|
||||
* @param reason Short human-readable failure description
|
||||
* @param addr The rejected pointer address
|
||||
*/
|
||||
static void hal_log_xt_failure(const char *reason, uint64_t addr) {
|
||||
console_puts("[HAL][XT] ");
|
||||
console_puts(reason);
|
||||
console_puts(" ptr=");
|
||||
hal_print_hex64(addr);
|
||||
console_println("");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test whether a 64-bit virtual address is in canonical form.
|
||||
*
|
||||
* On x86_64, canonical addresses have bits [63:48] all 0 (user space) or
|
||||
* all 1 (kernel space). Non-canonical addresses trigger a General Protection
|
||||
* Fault when dereferenced. This check is used to pre-screen XT pointers
|
||||
* before querying the page tables.
|
||||
*
|
||||
* @param addr Address to check
|
||||
* @return 1 if canonical (bits [63:48] are all 0 or all 1), 0 otherwise
|
||||
*/
|
||||
static inline int addr_is_canonical(uint64_t addr) {
|
||||
uint64_t upper = addr >> 48;
|
||||
return (upper == 0x0000ULL) || (upper == 0xFFFFULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialise the StarKernel HAL.
|
||||
*
|
||||
* Calls @c sk_host_init() to wire up the @c VMHostServices vtable, then
|
||||
* whitelists the kernel @c .text section (@c __text_start … @c __text_end)
|
||||
* as the initial executable region. Must be called once during M7 bootstrap
|
||||
* before any @c sk_hal_is_executable_ptr() call.
|
||||
*/
|
||||
void sk_hal_init(void) {
|
||||
sk_host_init();
|
||||
/* Use inline asm wrappers to avoid -fPIC GOT references */
|
||||
uint64_t text_start_addr = get_text_start_addr();
|
||||
uint64_t text_end_addr = get_text_end_addr();
|
||||
sk_hal_whitelist_exec_region(text_start_addr, text_end_addr, "kernel.text");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Allocate aligned memory via the kernel VM host-services vtable.
|
||||
*
|
||||
* Delegates to @c svc->alloc(). Defaults to @c sizeof(void*) alignment when
|
||||
* @c align == 0. Returns NULL if the host-services vtable is not initialised,
|
||||
* @c svc->alloc is NULL, or @c size is zero.
|
||||
*
|
||||
* @param size Number of bytes to allocate
|
||||
* @param align Required byte alignment (0 = pointer-width default)
|
||||
* @return Aligned memory block, or NULL on failure
|
||||
*/
|
||||
void *sk_hal_alloc(size_t size, size_t align) {
|
||||
const VMHostServices *svc = sk_hal_services();
|
||||
if (!svc || !svc->alloc || size == 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (align == 0) {
|
||||
align = sizeof(void *);
|
||||
}
|
||||
return svc->alloc(size, align);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Free a block previously allocated by @c sk_hal_alloc().
|
||||
*
|
||||
* Delegates to @c svc->free(). No-op if @c ptr is NULL or the host-services
|
||||
* vtable is not initialised.
|
||||
*
|
||||
* @param ptr Block to release (may be NULL)
|
||||
*/
|
||||
void sk_hal_free(void *ptr) {
|
||||
const VMHostServices *svc = sk_hal_services();
|
||||
if (ptr && svc && svc->free) {
|
||||
svc->free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the current monotonic time in nanoseconds (kernel HAL).
|
||||
*
|
||||
* Delegates to @c svc->monotonic_ns(). Returns 0 if the host-services vtable
|
||||
* is not initialised or the callback is NULL.
|
||||
*
|
||||
* @return Nanoseconds since an arbitrary boot-time epoch, or 0 on error
|
||||
*/
|
||||
uint64_t sk_hal_time_ns(void) {
|
||||
const VMHostServices *svc = sk_hal_services();
|
||||
if (svc && svc->monotonic_ns) {
|
||||
return svc->monotonic_ns();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the current kernel heartbeat tick count.
|
||||
*
|
||||
* Delegates to @c heartbeat_ticks() (driven by the APIC timer interrupt at
|
||||
* 100 Hz). Used by the VM physics engine to track elapsed wall-clock intervals
|
||||
* on bare metal where @c clock_gettime() is unavailable.
|
||||
*
|
||||
* @return Current heartbeat tick counter value
|
||||
*/
|
||||
uint64_t sk_hal_heartbeat_ticks(void) {
|
||||
return heartbeat_ticks();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write @c len bytes from @c buf to the kernel console.
|
||||
*
|
||||
* Iterates over each byte and dispatches to @c svc->putc() if available,
|
||||
* falling back to @c console_putc() when only @c svc->puts() is provided.
|
||||
* Returns 0 if @c buf is NULL, @c len is 0, or the host-services vtable is
|
||||
* uninitialised; stops early if @c putc() returns a negative error code.
|
||||
*
|
||||
* @param buf Data to write (need not be NUL-terminated)
|
||||
* @param len Number of bytes to write
|
||||
* @return Number of bytes actually written
|
||||
*/
|
||||
size_t sk_hal_console_write(const char *buf, size_t len) {
|
||||
if (!buf || len == 0) {
|
||||
return 0;
|
||||
}
|
||||
const VMHostServices *svc = sk_hal_services();
|
||||
if (!svc) {
|
||||
return 0;
|
||||
}
|
||||
size_t written = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
int ch = (unsigned char)buf[i];
|
||||
if (svc->putc) {
|
||||
if (svc->putc(ch) < 0) {
|
||||
break;
|
||||
}
|
||||
} else if (svc->puts) {
|
||||
/* puts requires NUL-terminated strings; fall back to console_putc */
|
||||
console_putc((char)ch);
|
||||
}
|
||||
written++;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a single character to the kernel console.
|
||||
*
|
||||
* Delegates to @c svc->putc() when available; falls back to @c console_putc().
|
||||
* Returns the character written (as unsigned char promoted to int), mirroring
|
||||
* the @c fputc() convention.
|
||||
*
|
||||
* @param c Character to write (as @c int; only low byte is used)
|
||||
* @return @c c on success (always succeeds in current implementation)
|
||||
*/
|
||||
int sk_hal_console_putc(int c) {
|
||||
const VMHostServices *svc = sk_hal_services();
|
||||
if (svc && svc->putc) {
|
||||
return svc->putc(c);
|
||||
}
|
||||
console_putc((char)c);
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger a kernel panic — print message and halt all CPUs.
|
||||
*
|
||||
* Prints @c "[StarKernel HAL] PANIC: MESSAGE\nSystem halted.\n" to the console,
|
||||
* then enters an infinite spin loop calling @c arch_halt() on each iteration.
|
||||
* This function never returns. @c message may be NULL (prints "unknown").
|
||||
*
|
||||
* @param message Human-readable panic description, or NULL
|
||||
*/
|
||||
void sk_hal_panic(const char *message) {
|
||||
console_puts("\n[StarKernel HAL] PANIC: ");
|
||||
if (message) {
|
||||
console_puts(message);
|
||||
} else {
|
||||
console_puts("unknown");
|
||||
}
|
||||
console_puts("\nSystem halted.\n");
|
||||
#if 0 /* vm_dictionary_log_last_word not implemented in minimal kernel */
|
||||
#ifdef __STARKERNEL__
|
||||
extern void vm_dictionary_log_last_word(struct VM *vm, const char *tag);
|
||||
vm_dictionary_log_last_word(NULL, "hal_panic");
|
||||
#endif
|
||||
#endif
|
||||
while (1) {
|
||||
arch_halt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate that a function pointer is safe to call via EXECUTE.
|
||||
*
|
||||
* Performs a multi-stage check before the FORTH @c EXECUTE word is allowed
|
||||
* to dispatch through @c ptr:
|
||||
* 1. NULL rejection
|
||||
* 2. Canonical-address check (bits [63:48] must be all-zero or all-one)
|
||||
* 3. VMM page-table query (@c vmm_query_page()) — page must be present
|
||||
* 4. NX (no-execute) bit check — page must be marked executable
|
||||
* 5. Whitelist check — address must fall within a registered exec region
|
||||
*
|
||||
* Failures are logged to the console via @c hal_log_xt_failure() or inline
|
||||
* @c console_puts() calls. The allowed regions are printed on whitelist failure
|
||||
* to aid post-mortem analysis.
|
||||
*
|
||||
* @param ptr Pointer to validate
|
||||
* @return @c true if all checks pass, @c false on any failure
|
||||
*/
|
||||
bool sk_hal_is_executable_ptr(const void *ptr) {
|
||||
uint64_t addr = (uint64_t)(uintptr_t)ptr;
|
||||
|
||||
if (addr == 0) {
|
||||
hal_log_xt_failure("reject: NULL pointer", addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!addr_is_canonical(addr)) {
|
||||
hal_log_xt_failure("reject: non-canonical address", addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
vmm_page_info_t info;
|
||||
if (!vmm_query_page(addr, &info) || !info.present) {
|
||||
console_puts("[HAL][XT] reject: unmapped pointer=");
|
||||
hal_print_hex64(addr);
|
||||
console_println("");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!info.executable) {
|
||||
hal_log_xt_failure("reject: NX mapping", addr);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hal_exec_addr_allowed(addr)) {
|
||||
console_puts("[HAL][XT] reject: pointer ");
|
||||
hal_print_hex64(addr);
|
||||
console_puts(" outside allowed executable ranges");
|
||||
console_println("");
|
||||
for (size_t i = 0; i < hal_exec_region_count; ++i) {
|
||||
console_puts(" allowed ");
|
||||
console_puts(hal_exec_regions[i].name ? hal_exec_regions[i].name : "region");
|
||||
console_puts(": [");
|
||||
hal_print_hex64(hal_exec_regions[i].start);
|
||||
console_puts(", ");
|
||||
hal_print_hex64(hal_exec_regions[i].end);
|
||||
console_println(")");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the kernel VM host-services vtable (public accessor).
|
||||
*
|
||||
* @return Pointer to the active @c VMHostServices struct, or NULL if not initialised
|
||||
*/
|
||||
const VMHostServices *sk_hal_host_services(void) {
|
||||
return sk_hal_services();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the physical address of the kernel @c .text section start.
|
||||
*
|
||||
* Uses the ISA-appropriate @c get_text_start_addr() inline-asm wrapper to
|
||||
* avoid GOT indirection under @c -fPIC. Suitable for use as the lower bound
|
||||
* of an exec-region whitelist entry.
|
||||
*
|
||||
* @return Physical address of @c __text_start
|
||||
*/
|
||||
uint64_t sk_hal_text_start(void) {
|
||||
return get_text_start_addr();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the physical address one byte past the kernel @c .text section end.
|
||||
*
|
||||
* Uses the ISA-appropriate @c get_text_end_addr() inline-asm wrapper. The
|
||||
* returned value is the exclusive upper bound of the kernel text region.
|
||||
*
|
||||
* @return Physical address of @c __text_end
|
||||
*/
|
||||
uint64_t sk_hal_text_end(void) {
|
||||
return get_text_end_addr();
|
||||
}
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* vm_host.c - Kernel host services implementation
|
||||
*
|
||||
* Provides kernel implementations of host service hooks.
|
||||
* All VM allocations/time/mutex go through these functions.
|
||||
*/
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "vm_host.h"
|
||||
#include "starkernel/hal/hal.h"
|
||||
#include "kmalloc.h"
|
||||
#include "console.h"
|
||||
#include "timer.h"
|
||||
#include "arch.h"
|
||||
#include "starkernel/vm/arena.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Parity mode: deterministic fake time
|
||||
* Increments by 1000ns (1us) per call for reproducibility
|
||||
*/
|
||||
#ifndef PARITY_MODE
|
||||
#define PARITY_MODE 0
|
||||
#endif
|
||||
|
||||
static VMHostServices kernel_services;
|
||||
static uint64_t parity_fake_ns = 0;
|
||||
static bool host_services_initialized = false;
|
||||
static bool host_services_logged = false;
|
||||
|
||||
/* ============================================================================
|
||||
* Memory Allocation
|
||||
* ============================================================================ */
|
||||
|
||||
static void* kernel_alloc(size_t size, size_t align) {
|
||||
if (size == 0) {
|
||||
return (void*)0;
|
||||
}
|
||||
|
||||
size_t arena_size = sk_vm_arena_size();
|
||||
if (size == arena_size) {
|
||||
if (!sk_vm_arena_is_initialized()) {
|
||||
/* Hera (first call): use the PMM-backed arena singleton */
|
||||
if (sk_vm_arena_alloc() == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return sk_vm_arena_ptr();
|
||||
}
|
||||
/* Baby VM: each needs its own isolated region; returning the singleton
|
||||
* here causes all VMs to share Hera's memory, overwriting her compiled
|
||||
* word bodies when the baby registers its dictionary. */
|
||||
void *p = (align <= sizeof(void*)) ? kmalloc(size)
|
||||
: kmalloc_aligned(size, align);
|
||||
if (p) {
|
||||
uint8_t *b = (uint8_t *)p;
|
||||
size_t i;
|
||||
for (i = 0; i < size; i++) b[i] = 0;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void *ptr;
|
||||
if (align <= sizeof(void*)) {
|
||||
ptr = kmalloc(size);
|
||||
} else {
|
||||
ptr = kmalloc_aligned(size, align);
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void kernel_free(void *ptr) {
|
||||
if (!ptr) {
|
||||
return;
|
||||
}
|
||||
/* VM arena lifetime is managed separately; ignore free requests */
|
||||
if (ptr == sk_vm_arena_ptr()) {
|
||||
return;
|
||||
}
|
||||
kfree(ptr);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Monotonic Time
|
||||
* ============================================================================ */
|
||||
|
||||
static uint64_t kernel_monotonic_ns(void) {
|
||||
#if PARITY_MODE
|
||||
/* Deterministic: 1us per call */
|
||||
parity_fake_ns += 1000;
|
||||
return parity_fake_ns;
|
||||
#else
|
||||
/* Delegate to timer_now_ns() rather than reimplementing TSC->ns
|
||||
* conversion here. This used to check timer_tsc_hz() itself and give
|
||||
* up (return 0) whenever TSC calibration failed -- which it reliably
|
||||
* does under QEMU/TCG (no invariant TSC, CPUID/PM-Timer frequency
|
||||
* derivation both fail: see "Timer: trust=1 (REL), TSC=0 Hz" in the
|
||||
* boot log). timer_now_ns() already has the correct fallback for
|
||||
* exactly this case (RELATIVE mode via the ACPI PM Timer,
|
||||
* timer_now_ns_vm_relative()) that this function never used. Every
|
||||
* caller of vm_monotonic_ns() -- including vm_physics_touch and
|
||||
* word-level background decay -- was silently receiving now_ns=0
|
||||
* forever as a result. Found via VM-FLEET-ATTRACTOR-DESIGN-20260705.md
|
||||
* rev g/h. */
|
||||
return timer_now_ns();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Mutex Operations (No-op in single-threaded kernel)
|
||||
* ============================================================================ */
|
||||
|
||||
static int kernel_mutex_init(void *mutex) {
|
||||
(void)mutex;
|
||||
return 0; /* Success */
|
||||
}
|
||||
|
||||
static int kernel_mutex_lock(void *mutex) {
|
||||
(void)mutex;
|
||||
return 0; /* Success */
|
||||
}
|
||||
|
||||
static int kernel_mutex_unlock(void *mutex) {
|
||||
(void)mutex;
|
||||
return 0; /* Success */
|
||||
}
|
||||
|
||||
static void kernel_mutex_destroy(void *mutex) {
|
||||
(void)mutex;
|
||||
/* Nothing to do */
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Console Output
|
||||
* ============================================================================ */
|
||||
|
||||
static int kernel_puts(const char *str) {
|
||||
if (!str) return -1;
|
||||
console_puts(str);
|
||||
/* Count characters (console_puts doesn't return count) */
|
||||
int count = 0;
|
||||
while (str[count]) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
static int kernel_putc(int c) {
|
||||
console_putc((char)c);
|
||||
return c;
|
||||
}
|
||||
|
||||
static bool kernel_xt_entry_owned(const void *ptr, size_t bytes) {
|
||||
if (!ptr || bytes == 0) {
|
||||
return false;
|
||||
}
|
||||
uintptr_t addr = (uintptr_t)ptr;
|
||||
uintptr_t entry_end = addr + bytes;
|
||||
if (entry_end < addr) {
|
||||
return false; /* Overflow check */
|
||||
}
|
||||
|
||||
/* Check if in VM arena (dictionary entries are allocated here) */
|
||||
if (sk_vm_arena_is_initialized()) {
|
||||
uint8_t *arena_ptr = sk_vm_arena_ptr();
|
||||
size_t arena_size = sk_vm_arena_size();
|
||||
uintptr_t arena_base = (uintptr_t)arena_ptr;
|
||||
uintptr_t arena_end = arena_base + arena_size;
|
||||
if (addr >= arena_base && entry_end <= arena_end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if in kmalloc heap (for other allocations) */
|
||||
uintptr_t heap_base = kmalloc_heap_base_addr();
|
||||
uintptr_t heap_end = kmalloc_heap_end_addr();
|
||||
if (heap_base != 0 && heap_end != 0) {
|
||||
if (addr >= heap_base && entry_end <= heap_end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Static wrappers for HAL functions - avoids GOT references in PE.
|
||||
* With -fPIC, cross-TU function pointer assignments use GOT-relative loads,
|
||||
* but PE files don't have a working GOT. These wrappers provide local
|
||||
* addresses that use RIP-relative LEA instead of GOT loads. */
|
||||
static bool kernel_is_executable_ptr(const void *ptr) {
|
||||
return sk_hal_is_executable_ptr(ptr);
|
||||
}
|
||||
|
||||
static void kernel_panic(const char *message) {
|
||||
sk_hal_panic(message);
|
||||
}
|
||||
|
||||
static void host_services_dump_table(void) {
|
||||
host_services_logged = true;
|
||||
}
|
||||
|
||||
const VMHostServices* sk_host_services(void) {
|
||||
if (!host_services_initialized) {
|
||||
sk_host_init();
|
||||
}
|
||||
return &kernel_services;
|
||||
}
|
||||
|
||||
void sk_host_init(void) {
|
||||
if (host_services_initialized) {
|
||||
host_services_dump_table();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Reset parity time if needed */
|
||||
parity_fake_ns = 0;
|
||||
|
||||
kernel_services.alloc = kernel_alloc;
|
||||
kernel_services.free = kernel_free;
|
||||
kernel_services.monotonic_ns = kernel_monotonic_ns;
|
||||
kernel_services.mutex_init = kernel_mutex_init;
|
||||
kernel_services.mutex_lock = kernel_mutex_lock;
|
||||
kernel_services.mutex_unlock = kernel_mutex_unlock;
|
||||
kernel_services.mutex_destroy = kernel_mutex_destroy;
|
||||
kernel_services.puts = kernel_puts;
|
||||
kernel_services.putc = kernel_putc;
|
||||
kernel_services.is_executable_ptr = kernel_is_executable_ptr;
|
||||
kernel_services.owns_xt_entry = kernel_xt_entry_owned;
|
||||
kernel_services.panic = kernel_panic;
|
||||
kernel_services.parity_mode = PARITY_MODE;
|
||||
kernel_services.verbose = 0;
|
||||
|
||||
host_services_initialized = true;
|
||||
host_services_dump_table();
|
||||
}
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* memory.c - HAL memory wrappers
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include "hal_memory.h"
|
||||
#include "kmalloc.h"
|
||||
|
||||
void *hal_mem_alloc(size_t size)
|
||||
{
|
||||
return kmalloc(size);
|
||||
}
|
||||
|
||||
void *hal_mem_alloc_aligned(size_t size, size_t align)
|
||||
{
|
||||
return kmalloc_aligned(size, align);
|
||||
}
|
||||
|
||||
void hal_mem_free(void *ptr)
|
||||
{
|
||||
kfree(ptr);
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ttf.c — TrueType font parser core (FABRIC-0.md items 4.3.7, 4.3.7a)
|
||||
*
|
||||
* sfnt directory + head/maxp/loca/glyf/cmap(format 4) table parsing, plus
|
||||
* simple- and composite-glyph outline extraction. All multi-byte fields in
|
||||
* a TTF are big-endian; this file reads them by hand (no libc byteswap
|
||||
* dependency) with bounds checks against the buffer length on every
|
||||
* access, since the buffer is untrusted input.
|
||||
*
|
||||
* Composite-glyph transforms: this file applies (dx,dy) TRANSLATION only.
|
||||
* A composite component carrying a non-identity scale/rotation/skew (the
|
||||
* WE_HAVE_A_SCALE/WE_HAVE_AN_X_AND_Y_SCALE/WE_HAVE_A_TWO_BY_TWO flags with
|
||||
* a non-1.0/non-zero F2Dot14 value) is rejected with TTF_ERR_UNSUPPORTED
|
||||
* rather than silently mis-rendered. Reason: applying such a transform
|
||||
* needs a signed fixed-point multiply, and the shared q48_mul()/q48_div()
|
||||
* in src/starkernel/math/q48_16.c are unsigned-only (q48_div saturates via
|
||||
* an unsigned overflow check; q48_mul does a plain unsigned widen-multiply)
|
||||
* — confirmed by reading that file, not assumed. Per this repo's rule
|
||||
* against modifying a shared/tested module to "fix" it without being
|
||||
* asked, that gap is reported (see FABRIC-0.md's 4.3.7a completion note),
|
||||
* not patched here. No glyph in the v1 repertoire (§27.6.4) needs a
|
||||
* non-identity composite transform — checked against
|
||||
* fonts/JetBrainsMono-Regular.ttf before writing this, not assumed.
|
||||
*/
|
||||
|
||||
#include "ttf.h"
|
||||
|
||||
#define TAG(a, b, c, d) \
|
||||
(((uint32_t)(a) << 24) | ((uint32_t)(b) << 16) | ((uint32_t)(c) << 8) | (uint32_t)(d))
|
||||
|
||||
static int fits(uint32_t size, uint32_t off, uint32_t len) {
|
||||
if (off > size) return 0;
|
||||
if (len > size - off) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int in_bounds(const ttf_font_t *f, uint32_t off, uint32_t len) {
|
||||
return fits(f->size, off, len);
|
||||
}
|
||||
|
||||
static uint16_t rd_u16(const uint8_t *d, uint32_t off) {
|
||||
return (uint16_t)(((uint16_t) d[off] << 8) | (uint16_t) d[off + 1]);
|
||||
}
|
||||
|
||||
static int16_t rd_i16(const uint8_t *d, uint32_t off) {
|
||||
return (int16_t) rd_u16(d, off);
|
||||
}
|
||||
|
||||
static uint32_t rd_u32(const uint8_t *d, uint32_t off) {
|
||||
return ((uint32_t) d[off] << 24) | ((uint32_t) d[off + 1] << 16) |
|
||||
((uint32_t) d[off + 2] << 8) | (uint32_t) d[off + 3];
|
||||
}
|
||||
|
||||
/* Locate a table record in the sfnt directory. Returns TTF_OK and fills
|
||||
* tbl_off/tbl_len, or TTF_ERR_TABLE_MISSING. */
|
||||
static int find_table(const uint8_t *data, uint32_t size, uint16_t num_tables,
|
||||
uint32_t tag, uint32_t *tbl_off, uint32_t *tbl_len) {
|
||||
uint32_t i;
|
||||
for (i = 0; i < num_tables; i++) {
|
||||
uint32_t rec = 12 + i * 16;
|
||||
if (!fits(size, rec, 16))
|
||||
return TTF_ERR_OUT_OF_BOUNDS;
|
||||
{
|
||||
uint32_t rec_tag = rd_u32(data, rec);
|
||||
if (rec_tag == tag) {
|
||||
*tbl_off = rd_u32(data, rec + 8);
|
||||
*tbl_len = rd_u32(data, rec + 12);
|
||||
return TTF_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
return TTF_ERR_TABLE_MISSING;
|
||||
}
|
||||
|
||||
int ttf_parse(const uint8_t *data, uint32_t size, ttf_font_t *out) {
|
||||
uint32_t sfnt_version;
|
||||
uint16_t num_tables;
|
||||
uint32_t glyf_len_unused;
|
||||
int rc;
|
||||
|
||||
if (!data || !out || size < 12) return TTF_ERR_BAD_SFNT;
|
||||
|
||||
sfnt_version = rd_u32(data, 0);
|
||||
if (sfnt_version != 0x00010000UL && sfnt_version != TAG('t', 'r', 'u', 'e'))
|
||||
return TTF_ERR_BAD_SFNT; /* not TrueType outlines (e.g. 'OTTO' = CFF) */
|
||||
|
||||
num_tables = rd_u16(data, 4);
|
||||
if (!fits(size, 12, (uint32_t) num_tables * 16))
|
||||
return TTF_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
out->data = data;
|
||||
out->size = size;
|
||||
out->has_cmap = 0;
|
||||
out->cmap_subtable_off = 0;
|
||||
|
||||
rc = find_table(data, size, num_tables, TAG('h', 'e', 'a', 'd'), &out->head_off, &glyf_len_unused);
|
||||
if (rc != TTF_OK) return rc;
|
||||
if (!in_bounds(out, out->head_off, 54)) return TTF_ERR_BAD_TABLE;
|
||||
|
||||
out->units_per_em = rd_u16(data, out->head_off + 18);
|
||||
out->index_to_loc_format = rd_i16(data, out->head_off + 50);
|
||||
if (out->index_to_loc_format != 0 && out->index_to_loc_format != 1)
|
||||
return TTF_ERR_BAD_TABLE;
|
||||
|
||||
rc = find_table(data, size, num_tables, TAG('m', 'a', 'x', 'p'), &out->maxp_off, &glyf_len_unused);
|
||||
if (rc != TTF_OK) return rc;
|
||||
if (!in_bounds(out, out->maxp_off, 6)) return TTF_ERR_BAD_TABLE;
|
||||
out->num_glyphs = rd_u16(data, out->maxp_off + 4);
|
||||
|
||||
rc = find_table(data, size, num_tables, TAG('l', 'o', 'c', 'a'), &out->loca_off, &out->loca_len);
|
||||
if (rc != TTF_OK) return rc;
|
||||
{
|
||||
uint32_t expect_entries = (uint32_t) out->num_glyphs + 1;
|
||||
uint32_t expect_bytes = out->index_to_loc_format == 0
|
||||
? expect_entries * 2
|
||||
: expect_entries * 4;
|
||||
if (out->loca_len < expect_bytes) return TTF_ERR_BAD_TABLE;
|
||||
if (!in_bounds(out, out->loca_off, expect_bytes)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
rc = find_table(data, size, num_tables, TAG('g', 'l', 'y', 'f'), &out->glyf_off, &out->glyf_len);
|
||||
if (rc != TTF_OK) return rc;
|
||||
if (!in_bounds(out, out->glyf_off, out->glyf_len)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
/* hhea (for numberOfHMetrics) + hmtx (advance widths) -- mandatory,
|
||||
* needed for ttf_glyph_advance_width() (4.3.7e's proportional
|
||||
* spacing). numberOfHMetrics is hhea's 18th field, a fixed 36-byte
|
||||
* table per spec. */
|
||||
{
|
||||
uint32_t hhea_off, hhea_len_unused, hmtx_len;
|
||||
rc = find_table(data, size, num_tables, TAG('h', 'h', 'e', 'a'), &hhea_off, &hhea_len_unused);
|
||||
if (rc != TTF_OK) return rc;
|
||||
if (!in_bounds(out, hhea_off, 36)) return TTF_ERR_BAD_TABLE;
|
||||
out->num_h_metrics = rd_u16(data, hhea_off + 34);
|
||||
|
||||
rc = find_table(data, size, num_tables, TAG('h', 'm', 't', 'x'), &out->hmtx_off, &hmtx_len);
|
||||
if (rc != TTF_OK) return rc;
|
||||
if (out->num_h_metrics == 0
|
||||
|| !in_bounds(out, out->hmtx_off, (uint32_t) out->num_h_metrics * 4))
|
||||
return TTF_ERR_BAD_TABLE;
|
||||
}
|
||||
|
||||
/* cmap is optional to overall parse success; codepoint lookup just
|
||||
* fails cleanly (TTF_GLYPH_MISSING) if it's absent or unrecognized. */
|
||||
{
|
||||
uint32_t cmap_off, cmap_len_unused;
|
||||
rc = find_table(data, size, num_tables, TAG('c', 'm', 'a', 'p'), &cmap_off, &cmap_len_unused);
|
||||
if (rc == TTF_OK && in_bounds(out, cmap_off, 4)) {
|
||||
uint16_t cmap_num_tables = rd_u16(data, cmap_off + 2);
|
||||
uint32_t i;
|
||||
if (in_bounds(out, cmap_off + 4, (uint32_t) cmap_num_tables * 8)) {
|
||||
uint32_t best_off = 0;
|
||||
int best_score = -1;
|
||||
for (i = 0; i < cmap_num_tables; i++) {
|
||||
uint32_t rec = cmap_off + 4 + i * 8;
|
||||
uint16_t platform_id = rd_u16(data, rec);
|
||||
uint16_t encoding_id = rd_u16(data, rec + 2);
|
||||
uint32_t sub_off = cmap_off + rd_u32(data, rec + 4);
|
||||
int score = -1;
|
||||
|
||||
if (!in_bounds(out, sub_off, 2)) continue;
|
||||
if (rd_u16(data, sub_off) != 4) continue; /* format 4 only, see header */
|
||||
|
||||
if (platform_id == 3 && encoding_id == 1) score = 3; /* Windows BMP */
|
||||
else if (platform_id == 0) score = 2; /* Unicode */
|
||||
else if (platform_id == 3 && encoding_id == 0) score = 1; /* Windows symbol */
|
||||
|
||||
if (score > best_score) {
|
||||
best_score = score;
|
||||
best_off = sub_off;
|
||||
}
|
||||
}
|
||||
if (best_score >= 0) {
|
||||
out->cmap_subtable_off = best_off;
|
||||
out->has_cmap = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
uint32_t ttf_codepoint_to_glyph(const ttf_font_t *font, uint32_t codepoint) {
|
||||
uint32_t sub;
|
||||
uint16_t seg_count_x2, seg_count;
|
||||
uint32_t end_code_off, start_code_off, id_delta_off, id_range_off;
|
||||
uint32_t i;
|
||||
|
||||
if (!font || !font->has_cmap) return TTF_GLYPH_MISSING;
|
||||
if (codepoint > 0xFFFFUL) return TTF_GLYPH_MISSING; /* format 4 = BMP only */
|
||||
|
||||
sub = font->cmap_subtable_off;
|
||||
if (!in_bounds(font, sub, 14)) return TTF_GLYPH_MISSING;
|
||||
|
||||
seg_count_x2 = rd_u16(font->data, sub + 6);
|
||||
seg_count = (uint16_t) (seg_count_x2 / 2);
|
||||
end_code_off = sub + 14;
|
||||
start_code_off = end_code_off + seg_count_x2 + 2; /* +2 skips reservedPad */
|
||||
id_delta_off = start_code_off + seg_count_x2;
|
||||
id_range_off = id_delta_off + seg_count_x2;
|
||||
|
||||
if (!in_bounds(font, end_code_off, (uint32_t) seg_count_x2 * 4 + 2))
|
||||
return TTF_GLYPH_MISSING;
|
||||
|
||||
for (i = 0; i < seg_count; i++) {
|
||||
uint16_t end_code = rd_u16(font->data, end_code_off + i * 2);
|
||||
if (codepoint > end_code) continue;
|
||||
|
||||
{
|
||||
uint16_t start_code = rd_u16(font->data, start_code_off + i * 2);
|
||||
int16_t id_delta = rd_i16(font->data, id_delta_off + i * 2);
|
||||
uint16_t id_range_offset = rd_u16(font->data, id_range_off + i * 2);
|
||||
|
||||
if (codepoint < start_code) return TTF_GLYPH_MISSING;
|
||||
|
||||
if (id_range_offset == 0) {
|
||||
return (uint32_t) ((uint16_t) (codepoint + (uint32_t) (int32_t) id_delta));
|
||||
} else {
|
||||
uint32_t glyph_addr = id_range_off + i * 2 + id_range_offset +
|
||||
(uint32_t) (codepoint - start_code) * 2;
|
||||
uint16_t glyph_id;
|
||||
if (!in_bounds(font, glyph_addr, 2)) return TTF_GLYPH_MISSING;
|
||||
glyph_id = rd_u16(font->data, glyph_addr);
|
||||
if (glyph_id == 0) return TTF_GLYPH_MISSING;
|
||||
return (uint32_t) ((uint16_t) (glyph_id + (uint32_t) (int32_t) id_delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
return TTF_GLYPH_MISSING;
|
||||
}
|
||||
|
||||
int ttf_glyph_header(const ttf_font_t *font, uint32_t glyph_index, ttf_glyph_header_t *out) {
|
||||
uint32_t off_a, off_b;
|
||||
|
||||
if (!font || !out) return TTF_ERR_BAD_TABLE;
|
||||
if (glyph_index >= font->num_glyphs) return TTF_ERR_BAD_GLYPH_INDEX;
|
||||
|
||||
if (font->index_to_loc_format == 0) {
|
||||
uint32_t e = font->loca_off + glyph_index * 2;
|
||||
if (!in_bounds(font, e, 4)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
off_a = (uint32_t) rd_u16(font->data, e) * 2;
|
||||
off_b = (uint32_t) rd_u16(font->data, e + 2) * 2;
|
||||
} else {
|
||||
uint32_t e = font->loca_off + glyph_index * 4;
|
||||
if (!in_bounds(font, e, 8)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
off_a = rd_u32(font->data, e);
|
||||
off_b = rd_u32(font->data, e + 4);
|
||||
}
|
||||
|
||||
if (off_b < off_a) return TTF_ERR_BAD_TABLE;
|
||||
|
||||
out->glyf_offset = font->glyf_off + off_a;
|
||||
out->glyf_length = off_b - off_a;
|
||||
|
||||
if (out->glyf_length == 0) {
|
||||
/* Empty glyph (e.g. space) — no glyf record at all. */
|
||||
out->num_contours = 0;
|
||||
out->x_min = out->y_min = out->x_max = out->y_max = 0;
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
if (!in_bounds(font, out->glyf_offset, 10)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
out->num_contours = rd_i16(font->data, out->glyf_offset);
|
||||
out->x_min = rd_i16(font->data, out->glyf_offset + 2);
|
||||
out->y_min = rd_i16(font->data, out->glyf_offset + 4);
|
||||
out->x_max = rd_i16(font->data, out->glyf_offset + 6);
|
||||
out->y_max = rd_i16(font->data, out->glyf_offset + 8);
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
uint16_t ttf_glyph_advance_width(const ttf_font_t *font, uint32_t glyph_index) {
|
||||
uint32_t idx, off;
|
||||
|
||||
if (!font || font->num_h_metrics == 0) return 0;
|
||||
idx = glyph_index < font->num_h_metrics ? glyph_index : (uint32_t) font->num_h_metrics - 1;
|
||||
off = font->hmtx_off + idx * 4;
|
||||
if (!in_bounds(font, off, 2)) return 0;
|
||||
return rd_u16(font->data, off);
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* 4.3.7a — glyph outline extraction
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
static uint8_t rd_u8(const uint8_t *d, uint32_t off) {
|
||||
return d[off];
|
||||
}
|
||||
|
||||
static int8_t rd_i8(const uint8_t *d, uint32_t off) {
|
||||
return (int8_t) d[off];
|
||||
}
|
||||
|
||||
/* Left-shift a signed 32-bit font-unit coordinate into Q48.16. Two's-
|
||||
* complement, deliberately not going through q48_16.h's q48_from_u64 (same
|
||||
* operation, but that name implies an unsigned caller, which this isn't —
|
||||
* see this file's header comment on why negative values are fine for
|
||||
* shift/add/sub but not for q48_mul/q48_div). */
|
||||
static q48_16_t q48_from_i32(int32_t v) {
|
||||
return ((uint64_t) (int64_t) v) << 16;
|
||||
}
|
||||
|
||||
#define TTF_FLAG_ON_CURVE 0x01
|
||||
#define TTF_FLAG_X_SHORT 0x02
|
||||
#define TTF_FLAG_Y_SHORT 0x04
|
||||
#define TTF_FLAG_REPEAT 0x08
|
||||
#define TTF_FLAG_X_SAME_OR_POS 0x10
|
||||
#define TTF_FLAG_Y_SAME_OR_POS 0x20
|
||||
|
||||
/* Decode-time cap on points/contours per single simple-glyph component —
|
||||
* a defensive stack-buffer bound, independent of the caller's out->
|
||||
* max_points/max_contours (which are checked separately, on the combined
|
||||
* outline). No glyph in the v1 repertoire (§27.6.4) comes close to this. */
|
||||
#define TTF_MAX_SIMPLE_POINTS 512
|
||||
|
||||
static int decode_simple_glyph(const ttf_font_t *font, const ttf_glyph_header_t *hdr,
|
||||
ttf_outline_t *out) {
|
||||
uint32_t off = hdr->glyf_offset + 10;
|
||||
uint16_t nc = (uint16_t) hdr->num_contours;
|
||||
uint16_t end_pts[TTF_MAX_SIMPLE_POINTS]; /* reused below for endPtsOfContours only, nc <= that bound checked */
|
||||
uint16_t num_points;
|
||||
uint8_t flags[TTF_MAX_SIMPLE_POINTS];
|
||||
int32_t xs[TTF_MAX_SIMPLE_POINTS];
|
||||
int32_t ys[TTF_MAX_SIMPLE_POINTS];
|
||||
uint32_t i;
|
||||
uint32_t point_base;
|
||||
int32_t acc;
|
||||
|
||||
if (nc == 0 || nc > TTF_MAX_SIMPLE_POINTS) return TTF_ERR_TOO_MANY_CONTOURS;
|
||||
if (!in_bounds(font, off, (uint32_t) nc * 2 + 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
|
||||
for (i = 0; i < nc; i++) {
|
||||
end_pts[i] = rd_u16(font->data, off + i * 2);
|
||||
}
|
||||
off += (uint32_t) nc * 2;
|
||||
|
||||
num_points = (uint16_t) (end_pts[nc - 1] + 1);
|
||||
if (num_points == 0 || num_points > TTF_MAX_SIMPLE_POINTS) return TTF_ERR_TOO_MANY_POINTS;
|
||||
|
||||
{
|
||||
uint16_t instruction_length;
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
instruction_length = rd_u16(font->data, off);
|
||||
off += 2;
|
||||
if (!in_bounds(font, off, instruction_length)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
off += instruction_length;
|
||||
}
|
||||
|
||||
/* Flags, with run-length repeat expansion. */
|
||||
i = 0;
|
||||
while (i < num_points) {
|
||||
uint8_t f;
|
||||
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
f = rd_u8(font->data, off);
|
||||
off += 1;
|
||||
flags[i++] = f;
|
||||
if (f & TTF_FLAG_REPEAT) {
|
||||
uint8_t repeat;
|
||||
uint32_t r;
|
||||
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
repeat = rd_u8(font->data, off);
|
||||
off += 1;
|
||||
for (r = 0; r < repeat; r++) {
|
||||
if (i >= num_points) return TTF_ERR_TOO_MANY_POINTS;
|
||||
flags[i++] = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* X coordinates, delta-decoded. */
|
||||
acc = 0;
|
||||
for (i = 0; i < num_points; i++) {
|
||||
uint8_t f = flags[i];
|
||||
int32_t dx;
|
||||
if (f & TTF_FLAG_X_SHORT) {
|
||||
uint8_t v;
|
||||
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
v = rd_u8(font->data, off);
|
||||
off += 1;
|
||||
dx = (f & TTF_FLAG_X_SAME_OR_POS) ? (int32_t) v : -(int32_t) v;
|
||||
} else if (f & TTF_FLAG_X_SAME_OR_POS) {
|
||||
dx = 0;
|
||||
} else {
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
dx = rd_i16(font->data, off);
|
||||
off += 2;
|
||||
}
|
||||
acc += dx;
|
||||
xs[i] = acc;
|
||||
}
|
||||
|
||||
/* Y coordinates, delta-decoded. */
|
||||
acc = 0;
|
||||
for (i = 0; i < num_points; i++) {
|
||||
uint8_t f = flags[i];
|
||||
int32_t dy;
|
||||
if (f & TTF_FLAG_Y_SHORT) {
|
||||
uint8_t v;
|
||||
if (!in_bounds(font, off, 1)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
v = rd_u8(font->data, off);
|
||||
off += 1;
|
||||
dy = (f & TTF_FLAG_Y_SAME_OR_POS) ? (int32_t) v : -(int32_t) v;
|
||||
} else if (f & TTF_FLAG_Y_SAME_OR_POS) {
|
||||
dy = 0;
|
||||
} else {
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
dy = rd_i16(font->data, off);
|
||||
off += 2;
|
||||
}
|
||||
acc += dy;
|
||||
ys[i] = acc;
|
||||
}
|
||||
|
||||
if (out->point_count + num_points > out->max_points) return TTF_ERR_TOO_MANY_POINTS;
|
||||
if (out->contour_count + nc > out->max_contours) return TTF_ERR_TOO_MANY_CONTOURS;
|
||||
|
||||
point_base = out->point_count;
|
||||
for (i = 0; i < num_points; i++) {
|
||||
out->points[point_base + i].x = q48_from_i32(xs[i]);
|
||||
out->points[point_base + i].y = q48_from_i32(ys[i]);
|
||||
out->points[point_base + i].on_curve = (uint8_t) ((flags[i] & TTF_FLAG_ON_CURVE) != 0);
|
||||
}
|
||||
for (i = 0; i < nc; i++) {
|
||||
out->contour_ends[out->contour_count + i] = (uint16_t) (point_base + end_pts[i]);
|
||||
}
|
||||
out->point_count += num_points;
|
||||
out->contour_count += nc;
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
#define TTF_COMP_ARGS_ARE_WORDS 0x0001
|
||||
#define TTF_COMP_ARGS_ARE_XY_VALUES 0x0002
|
||||
#define TTF_COMP_WE_HAVE_A_SCALE 0x0008
|
||||
#define TTF_COMP_MORE_COMPONENTS 0x0020
|
||||
#define TTF_COMP_WE_HAVE_XY_SCALE 0x0040
|
||||
#define TTF_COMP_WE_HAVE_2X2 0x0080
|
||||
|
||||
#define TTF_F2DOT14_ONE 16384 /* 1.0 in F2Dot14 */
|
||||
|
||||
static int decode_glyph_r(const ttf_font_t *font, uint32_t glyph_index,
|
||||
ttf_outline_t *out, int depth);
|
||||
|
||||
static int decode_composite_glyph(const ttf_font_t *font, const ttf_glyph_header_t *hdr,
|
||||
ttf_outline_t *out, int depth) {
|
||||
uint32_t off = hdr->glyf_offset + 10;
|
||||
int more = 1;
|
||||
|
||||
while (more) {
|
||||
uint16_t flags, comp_glyph;
|
||||
int32_t arg1, arg2;
|
||||
uint32_t point_base;
|
||||
int rc;
|
||||
|
||||
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
flags = rd_u16(font->data, off);
|
||||
comp_glyph = rd_u16(font->data, off + 2);
|
||||
off += 4;
|
||||
|
||||
if (!(flags & TTF_COMP_ARGS_ARE_XY_VALUES)) return TTF_ERR_UNSUPPORTED; /* point-matching, not implemented */
|
||||
|
||||
if (flags & TTF_COMP_ARGS_ARE_WORDS) {
|
||||
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
arg1 = rd_i16(font->data, off);
|
||||
arg2 = rd_i16(font->data, off + 2);
|
||||
off += 4;
|
||||
} else {
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
arg1 = rd_i8(font->data, off);
|
||||
arg2 = rd_i8(font->data, off + 1);
|
||||
off += 2;
|
||||
}
|
||||
|
||||
if (flags & TTF_COMP_WE_HAVE_A_SCALE) {
|
||||
int16_t s;
|
||||
if (!in_bounds(font, off, 2)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
s = rd_i16(font->data, off);
|
||||
off += 2;
|
||||
if (s != TTF_F2DOT14_ONE) return TTF_ERR_UNSUPPORTED;
|
||||
} else if (flags & TTF_COMP_WE_HAVE_XY_SCALE) {
|
||||
int16_t sx, sy;
|
||||
if (!in_bounds(font, off, 4)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
sx = rd_i16(font->data, off);
|
||||
sy = rd_i16(font->data, off + 2);
|
||||
off += 4;
|
||||
if (sx != TTF_F2DOT14_ONE || sy != TTF_F2DOT14_ONE) return TTF_ERR_UNSUPPORTED;
|
||||
} else if (flags & TTF_COMP_WE_HAVE_2X2) {
|
||||
int16_t a, b, c, dd;
|
||||
if (!in_bounds(font, off, 8)) return TTF_ERR_OUT_OF_BOUNDS;
|
||||
a = rd_i16(font->data, off);
|
||||
b = rd_i16(font->data, off + 2);
|
||||
c = rd_i16(font->data, off + 4);
|
||||
dd = rd_i16(font->data, off + 6);
|
||||
off += 8;
|
||||
if (a != TTF_F2DOT14_ONE || b != 0 || c != 0 || dd != TTF_F2DOT14_ONE)
|
||||
return TTF_ERR_UNSUPPORTED;
|
||||
}
|
||||
|
||||
point_base = out->point_count;
|
||||
rc = decode_glyph_r(font, comp_glyph, out, depth + 1);
|
||||
if (rc != TTF_OK) return rc;
|
||||
|
||||
{
|
||||
q48_16_t dx = q48_from_i32(arg1);
|
||||
q48_16_t dy = q48_from_i32(arg2);
|
||||
uint32_t i;
|
||||
for (i = point_base; i < out->point_count; i++) {
|
||||
out->points[i].x = q48_add(out->points[i].x, dx);
|
||||
out->points[i].y = q48_add(out->points[i].y, dy);
|
||||
}
|
||||
}
|
||||
|
||||
more = (flags & TTF_COMP_MORE_COMPONENTS) != 0;
|
||||
}
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
static int decode_glyph_r(const ttf_font_t *font, uint32_t glyph_index,
|
||||
ttf_outline_t *out, int depth) {
|
||||
ttf_glyph_header_t hdr;
|
||||
int rc;
|
||||
|
||||
if (depth > TTF_MAX_COMPOSITE_DEPTH) return TTF_ERR_TOO_DEEP;
|
||||
|
||||
rc = ttf_glyph_header(font, glyph_index, &hdr);
|
||||
if (rc != TTF_OK) return rc;
|
||||
|
||||
if (hdr.glyf_length == 0) return TTF_OK; /* empty glyph, e.g. space */
|
||||
|
||||
if (hdr.num_contours >= 0) return decode_simple_glyph(font, &hdr, out);
|
||||
return decode_composite_glyph(font, &hdr, out, depth);
|
||||
}
|
||||
|
||||
int ttf_glyph_outline(const ttf_font_t *font, uint32_t glyph_index, ttf_outline_t *out) {
|
||||
if (!font || !out) return TTF_ERR_BAD_TABLE;
|
||||
out->point_count = 0;
|
||||
out->contour_count = 0;
|
||||
return decode_glyph_r(font, glyph_index, out, 0);
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* 4.3.7c — rasterization: quadratic Bezier flattening + even-odd scanline
|
||||
* fill. No antialiasing (deferred, per this item's own "done when" clause).
|
||||
*
|
||||
* Fill rule: even-odd, not TrueType's native nonzero winding. Simpler to
|
||||
* implement (no edge-direction bookkeeping) and produces an identical
|
||||
* result to nonzero winding for the v1 glyph repertoire (§27.6.4), whose
|
||||
* contours are simple (non-self-intersecting) and properly nested
|
||||
* (outer contour + inner counters, e.g. 'a', 'o') -- the two rules only
|
||||
* diverge on self-intersecting outlines, which no glyph here has. Not
|
||||
* correct in general for an arbitrary font.
|
||||
*
|
||||
* Local signed Q48.16 multiply (q48_smul): the shared q48_mul()/q48_div()
|
||||
* (src/starkernel/math/q48_16.c) are unsigned-only (see 4.3.7a's
|
||||
* completion note) and this file routinely multiplies a negative outline
|
||||
* coordinate by a non-negative scale/Bezier-blend weight -- exactly the
|
||||
* signed use case q48_mul doesn't support. Reported there, not patched
|
||||
* here, per this repo's rule against modifying a shared/tested module
|
||||
* without being asked. All values here (glyph coordinates scaled to at
|
||||
* most a few hundred pixels) stay far inside int64_t range.
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
#define TTF_QUAD_SEGMENTS 8 /* fixed subdivision count per quadratic curve --
|
||||
* same fixed-segment-count approach as
|
||||
* capsules/fabric.4th's CIRCLE/ELLIPSE (36 segs)
|
||||
* and ARC (18 segs); no adaptive tessellation. */
|
||||
#define TTF_RASTER_RAW_MAX_POINTS 256
|
||||
#define TTF_RASTER_RAW_MAX_CONTOURS 16
|
||||
#define TTF_RASTER_FLAT_MAX_POINTS (TTF_RASTER_RAW_MAX_POINTS * TTF_QUAD_SEGMENTS)
|
||||
#define TTF_RASTER_FLAT_MAX_CONTOURS TTF_RASTER_RAW_MAX_CONTOURS
|
||||
|
||||
/* Flattened polygon vertex: pixel space, Q48.16, after scale + origin +
|
||||
* the font-Y-up-to-raster-Y-down flip. */
|
||||
typedef struct {
|
||||
q48_16_t x, y;
|
||||
} ttf_flat_point_t;
|
||||
|
||||
static q48_16_t q48_smul(q48_16_t a, q48_16_t b) {
|
||||
return (q48_16_t) (((int64_t) a * (int64_t) b) >> 16);
|
||||
}
|
||||
|
||||
static q48_16_t q48_mid(q48_16_t a, q48_16_t b) {
|
||||
return (q48_16_t) (((int64_t) (a + b)) >> 1);
|
||||
}
|
||||
|
||||
static void raw_midpoint(const ttf_point_t *a, const ttf_point_t *b, ttf_point_t *out) {
|
||||
out->x = q48_mid(a->x, b->x);
|
||||
out->y = q48_mid(a->y, b->y);
|
||||
out->on_curve = 1;
|
||||
}
|
||||
|
||||
static void to_pixel_space(q48_16_t fx, q48_16_t fy, q48_16_t scale,
|
||||
q48_16_t origin_x, q48_16_t origin_y,
|
||||
q48_16_t *px, q48_16_t *py) {
|
||||
*px = q48_add(origin_x, q48_smul(fx, scale));
|
||||
*py = q48_sub(origin_y, q48_smul(fy, scale));
|
||||
}
|
||||
|
||||
static void flat_push(ttf_flat_point_t *pts, uint32_t *count, uint32_t max,
|
||||
q48_16_t x, q48_16_t y, int *err) {
|
||||
if (*err != TTF_OK) return;
|
||||
if (*count >= max) { *err = TTF_ERR_TOO_MANY_POINTS; return; }
|
||||
pts[*count].x = x;
|
||||
pts[*count].y = y;
|
||||
(*count)++;
|
||||
}
|
||||
|
||||
/* Subdivide one quadratic Bezier (control points already in pixel space)
|
||||
* into TTF_QUAD_SEGMENTS line segments. Emits the interior + final
|
||||
* vertices only -- the caller's `cur` is already the start (p0). */
|
||||
static void flatten_quad(ttf_flat_point_t *pts, uint32_t *count, uint32_t max,
|
||||
q48_16_t p0x, q48_16_t p0y, q48_16_t p1x, q48_16_t p1y,
|
||||
q48_16_t p2x, q48_16_t p2y, int *err) {
|
||||
uint32_t i;
|
||||
for (i = 1; i <= TTF_QUAD_SEGMENTS; i++) {
|
||||
q48_16_t t = (q48_16_t) ((uint64_t) Q48_ONE * i / TTF_QUAD_SEGMENTS);
|
||||
q48_16_t omt = (q48_16_t) (Q48_ONE - t);
|
||||
q48_16_t w0 = q48_smul(omt, omt);
|
||||
q48_16_t w1 = q48_smul(q48_smul((q48_16_t) (2ULL * Q48_ONE), omt), t);
|
||||
q48_16_t w2 = q48_smul(t, t);
|
||||
q48_16_t x = q48_add(q48_add(q48_smul(w0, p0x), q48_smul(w1, p1x)), q48_smul(w2, p2x));
|
||||
q48_16_t y = q48_add(q48_add(q48_smul(w0, p0y), q48_smul(w1, p1y)), q48_smul(w2, p2y));
|
||||
flat_push(pts, count, max, x, y, err);
|
||||
}
|
||||
}
|
||||
|
||||
/* Walk one contour (raw[begin..end] inclusive, font design units) and
|
||||
* append its flattened, closed polygon to the flat point list. Handles
|
||||
* the three TrueType start-point cases (first point on-curve, last point
|
||||
* on-curve, or neither -- synthesized midpoint start) and implied
|
||||
* on-curve points between two consecutive off-curve control points. */
|
||||
static void flatten_contour(const ttf_point_t *raw, uint32_t begin, uint32_t end,
|
||||
q48_16_t scale, q48_16_t origin_x, q48_16_t origin_y,
|
||||
ttf_flat_point_t *flat_pts, uint32_t *flat_count, uint32_t flat_max,
|
||||
int *err) {
|
||||
uint32_t n = end - begin + 1;
|
||||
ttf_point_t start_pt;
|
||||
uint32_t walk_start, walk_count, idx, steps_done;
|
||||
q48_16_t cur_px, cur_py, start_px, start_py;
|
||||
|
||||
if (n == 0 || *err != TTF_OK) return;
|
||||
|
||||
if (raw[begin].on_curve) {
|
||||
start_pt = raw[begin];
|
||||
walk_start = 1;
|
||||
walk_count = n - 1;
|
||||
} else if (raw[end].on_curve) {
|
||||
start_pt = raw[end];
|
||||
walk_start = 0;
|
||||
walk_count = n - 1;
|
||||
} else {
|
||||
raw_midpoint(&raw[end], &raw[begin], &start_pt);
|
||||
walk_start = 0;
|
||||
walk_count = n;
|
||||
}
|
||||
|
||||
to_pixel_space(start_pt.x, start_pt.y, scale, origin_x, origin_y, &start_px, &start_py);
|
||||
cur_px = start_px;
|
||||
cur_py = start_py;
|
||||
flat_push(flat_pts, flat_count, flat_max, start_px, start_py, err);
|
||||
|
||||
idx = walk_start;
|
||||
steps_done = 0;
|
||||
while (steps_done < walk_count && *err == TTF_OK) {
|
||||
const ttf_point_t *p = &raw[begin + idx];
|
||||
|
||||
if (p->on_curve) {
|
||||
q48_16_t px, py;
|
||||
to_pixel_space(p->x, p->y, scale, origin_x, origin_y, &px, &py);
|
||||
flat_push(flat_pts, flat_count, flat_max, px, py, err);
|
||||
cur_px = px;
|
||||
cur_py = py;
|
||||
idx = (idx + 1) % n;
|
||||
steps_done++;
|
||||
} else if (steps_done + 1 < walk_count) {
|
||||
uint32_t idx2 = (idx + 1) % n;
|
||||
const ttf_point_t *p2 = &raw[begin + idx2];
|
||||
ttf_point_t end_pt;
|
||||
int consumed2;
|
||||
q48_16_t c1x, c1y, ex, ey;
|
||||
|
||||
if (p2->on_curve) {
|
||||
end_pt = *p2;
|
||||
consumed2 = 1;
|
||||
} else {
|
||||
raw_midpoint(p, p2, &end_pt);
|
||||
consumed2 = 0;
|
||||
}
|
||||
|
||||
to_pixel_space(p->x, p->y, scale, origin_x, origin_y, &c1x, &c1y);
|
||||
to_pixel_space(end_pt.x, end_pt.y, scale, origin_x, origin_y, &ex, &ey);
|
||||
flatten_quad(flat_pts, flat_count, flat_max, cur_px, cur_py, c1x, c1y, ex, ey, err);
|
||||
cur_px = ex;
|
||||
cur_py = ey;
|
||||
|
||||
if (consumed2) {
|
||||
idx = (idx2 + 1) % n;
|
||||
steps_done += 2;
|
||||
} else {
|
||||
idx = idx2;
|
||||
steps_done += 1;
|
||||
}
|
||||
} else {
|
||||
/* Last point in the walk is a control point -- the curve it
|
||||
* starts closes the contour, ending at start_pt. */
|
||||
q48_16_t c1x, c1y;
|
||||
to_pixel_space(p->x, p->y, scale, origin_x, origin_y, &c1x, &c1y);
|
||||
flatten_quad(flat_pts, flat_count, flat_max, cur_px, cur_py, c1x, c1y,
|
||||
start_px, start_py, err);
|
||||
cur_px = start_px;
|
||||
cur_py = start_py;
|
||||
steps_done += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Close the polygon back to its start when the walk ended on an
|
||||
* on-curve vertex that isn't the start itself -- the common case,
|
||||
* not a rare fallback (a contour that ends mid-curve self-closes in
|
||||
* the branch above instead). */
|
||||
if (cur_px != start_px || cur_py != start_py) {
|
||||
flat_push(flat_pts, flat_count, flat_max, start_px, start_py, err);
|
||||
}
|
||||
}
|
||||
|
||||
int ttf_rasterize_glyph(const ttf_font_t *font, uint32_t glyph_index,
|
||||
q48_16_t scale, q48_16_t origin_x, q48_16_t origin_y,
|
||||
uint8_t fill_value, ttf_bitmap_t *out) {
|
||||
ttf_point_t raw_points[TTF_RASTER_RAW_MAX_POINTS];
|
||||
uint16_t raw_contour_ends[TTF_RASTER_RAW_MAX_CONTOURS];
|
||||
ttf_outline_t raw_outline;
|
||||
ttf_flat_point_t flat_points[TTF_RASTER_FLAT_MAX_POINTS];
|
||||
uint16_t flat_contour_ends[TTF_RASTER_FLAT_MAX_CONTOURS];
|
||||
uint32_t flat_count = 0;
|
||||
uint32_t flat_contour_count = 0;
|
||||
int err = TTF_OK;
|
||||
uint32_t c, begin;
|
||||
int rc;
|
||||
int32_t yrow;
|
||||
|
||||
if (!font || !out || !out->pixels) return TTF_ERR_BAD_TABLE;
|
||||
|
||||
raw_outline.points = raw_points;
|
||||
raw_outline.max_points = TTF_RASTER_RAW_MAX_POINTS;
|
||||
raw_outline.contour_ends = raw_contour_ends;
|
||||
raw_outline.max_contours = TTF_RASTER_RAW_MAX_CONTOURS;
|
||||
|
||||
rc = ttf_glyph_outline(font, glyph_index, &raw_outline);
|
||||
if (rc != TTF_OK) return rc;
|
||||
if (raw_outline.contour_count == 0) return TTF_OK; /* empty glyph, e.g. space */
|
||||
|
||||
begin = 0;
|
||||
for (c = 0; c < raw_outline.contour_count; c++) {
|
||||
uint32_t end = raw_outline.contour_ends[c];
|
||||
|
||||
flatten_contour(raw_points, begin, end, scale, origin_x, origin_y,
|
||||
flat_points, &flat_count, TTF_RASTER_FLAT_MAX_POINTS, &err);
|
||||
if (err != TTF_OK) return err;
|
||||
|
||||
if (flat_contour_count >= TTF_RASTER_FLAT_MAX_CONTOURS) return TTF_ERR_TOO_MANY_CONTOURS;
|
||||
flat_contour_ends[flat_contour_count++] = (uint16_t) (flat_count - 1);
|
||||
|
||||
begin = end + 1;
|
||||
}
|
||||
|
||||
/* Even-odd scanline fill, sampled at the pixel-center Y (row + 0.5)
|
||||
* so a vertex landing exactly on an integer scanline doesn't get
|
||||
* double-counted -- standard convention. */
|
||||
for (yrow = 0; yrow < (int32_t) out->height; yrow++) {
|
||||
q48_16_t yc = q48_add((q48_16_t) ((uint64_t) yrow << 16), (q48_16_t) (Q48_ONE / 2));
|
||||
q48_16_t xs[64];
|
||||
uint32_t xn = 0;
|
||||
uint32_t cbegin = 0;
|
||||
|
||||
for (c = 0; c < flat_contour_count; c++) {
|
||||
uint32_t cend = flat_contour_ends[c];
|
||||
uint32_t i;
|
||||
for (i = cbegin; i <= cend; i++) {
|
||||
uint32_t j = (i == cend) ? cbegin : i + 1;
|
||||
int64_t y0q = (int64_t) flat_points[i].y;
|
||||
int64_t y1q = (int64_t) flat_points[j].y;
|
||||
int below0 = y0q <= (int64_t) yc;
|
||||
int below1 = y1q <= (int64_t) yc;
|
||||
if (below0 == below1) continue; /* edge doesn't cross this scanline */
|
||||
{
|
||||
int64_t x0 = (int64_t) flat_points[i].x;
|
||||
int64_t x1 = (int64_t) flat_points[j].x;
|
||||
int64_t dy = y1q - y0q;
|
||||
int64_t dx = x1 - x0;
|
||||
int64_t t_num = (int64_t) yc - y0q;
|
||||
int64_t x = x0 + (dx * t_num) / dy;
|
||||
if (xn < 64) xs[xn++] = (q48_16_t) x;
|
||||
}
|
||||
}
|
||||
cbegin = cend + 1;
|
||||
}
|
||||
|
||||
/* insertion sort -- xn is small for a glyph outline */
|
||||
{
|
||||
uint32_t a;
|
||||
for (a = 1; a < xn; a++) {
|
||||
q48_16_t key = xs[a];
|
||||
int64_t keyv = (int64_t) key;
|
||||
int32_t b = (int32_t) a - 1;
|
||||
while (b >= 0 && (int64_t) xs[b] > keyv) {
|
||||
xs[b + 1] = xs[b];
|
||||
b--;
|
||||
}
|
||||
xs[b + 1] = key;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t a;
|
||||
for (a = 0; a + 1 < xn; a += 2) {
|
||||
int32_t xa = (int32_t) (((int64_t) xs[a]) >> 16);
|
||||
int32_t xb = (int32_t) (((int64_t) xs[a + 1]) >> 16);
|
||||
int32_t x;
|
||||
if (xa < 0) xa = 0;
|
||||
if (xb > (int32_t) out->width) xb = (int32_t) out->width;
|
||||
for (x = xa; x < xb; x++) {
|
||||
out->pixels[(uint32_t) yrow * out->width + (uint32_t) x] = fill_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* 4.3.7d — glyph raster cache: fixed caller-owned slots, no allocation.
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
void ttf_raster_cache_init(ttf_raster_cache_t *cache, ttf_raster_cache_slot_t *slots,
|
||||
uint32_t slot_count) {
|
||||
uint32_t i;
|
||||
cache->slots = slots;
|
||||
cache->slot_count = slot_count;
|
||||
cache->evict_next = 0;
|
||||
for (i = 0; i < slot_count; i++) {
|
||||
slots[i].valid = 0;
|
||||
slots[i].hits = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ttf_raster_cache_get(ttf_raster_cache_t *cache, const ttf_font_t *font,
|
||||
uint32_t codepoint, uint32_t size_px,
|
||||
ttf_bitmap_t *out, int *was_hit) {
|
||||
uint32_t i, slot_idx;
|
||||
ttf_raster_cache_slot_t *slot;
|
||||
int rc;
|
||||
|
||||
if (!cache || !font || !out) return TTF_ERR_BAD_TABLE;
|
||||
if (size_px > TTF_CACHE_MAX_SIZE_PX) return TTF_ERR_UNSUPPORTED;
|
||||
|
||||
for (i = 0; i < cache->slot_count; i++) {
|
||||
slot = &cache->slots[i];
|
||||
if (slot->valid && slot->font == font && slot->codepoint == codepoint
|
||||
&& slot->size_px == size_px) {
|
||||
slot->hits++;
|
||||
out->pixels = slot->pixels;
|
||||
out->width = slot->width;
|
||||
out->height = slot->height;
|
||||
if (was_hit) *was_hit = 1;
|
||||
return TTF_OK;
|
||||
}
|
||||
}
|
||||
|
||||
/* Miss: use a free slot if one exists, else evict round-robin. */
|
||||
slot_idx = cache->slot_count;
|
||||
for (i = 0; i < cache->slot_count; i++) {
|
||||
if (!cache->slots[i].valid) { slot_idx = i; break; }
|
||||
}
|
||||
if (slot_idx == cache->slot_count) {
|
||||
slot_idx = cache->evict_next;
|
||||
cache->evict_next = (cache->evict_next + 1) % cache->slot_count;
|
||||
}
|
||||
slot = &cache->slots[slot_idx];
|
||||
|
||||
{
|
||||
uint32_t j;
|
||||
ttf_bitmap_t bmp;
|
||||
q48_16_t scale;
|
||||
uint32_t gid;
|
||||
|
||||
for (j = 0; j < TTF_CACHE_BITMAP_BYTES; j++) slot->pixels[j] = 0;
|
||||
|
||||
gid = ttf_codepoint_to_glyph(font, codepoint);
|
||||
scale = q48_div(q48_from_u64(size_px), q48_from_u64(font->units_per_em));
|
||||
|
||||
bmp.pixels = slot->pixels;
|
||||
bmp.width = TTF_CACHE_BITMAP_DIM;
|
||||
bmp.height = TTF_CACHE_BITMAP_DIM;
|
||||
rc = ttf_rasterize_glyph(font, gid, scale,
|
||||
q48_from_u64(TTF_CACHE_MARGIN),
|
||||
q48_from_u64(size_px + TTF_CACHE_MARGIN),
|
||||
255, &bmp);
|
||||
if (rc != TTF_OK) {
|
||||
slot->valid = 0;
|
||||
return rc;
|
||||
}
|
||||
|
||||
slot->valid = 1;
|
||||
slot->font = font;
|
||||
slot->codepoint = codepoint;
|
||||
slot->size_px = size_px;
|
||||
slot->width = TTF_CACHE_BITMAP_DIM;
|
||||
slot->height = TTF_CACHE_BITMAP_DIM;
|
||||
slot->hits = 0;
|
||||
}
|
||||
|
||||
out->pixels = slot->pixels;
|
||||
out->width = slot->width;
|
||||
out->height = slot->height;
|
||||
if (was_hit) *was_hit = 0;
|
||||
return TTF_OK;
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* 4.3.7b — font data ingestion (capsule-backed, zero-copy)
|
||||
* ===========================================================================
|
||||
*/
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
int ttf_load_from_capsule(
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs,
|
||||
const CapsuleNameEntry *names,
|
||||
const uint8_t *arena,
|
||||
const char *capsule_name,
|
||||
ttf_font_t *out) {
|
||||
const CapsuleDesc *cap;
|
||||
const uint8_t *payload;
|
||||
CapsuleValidateResult vr;
|
||||
|
||||
if (!dir || !descs || !names || !arena || !capsule_name || !out)
|
||||
return TTF_ERR_BAD_TABLE;
|
||||
|
||||
cap = capsule_find_by_name(dir, descs, names, capsule_name);
|
||||
if (!cap) return TTF_ERR_TABLE_MISSING;
|
||||
|
||||
vr = capsule_validate(cap, arena, dir->arena_size, 1);
|
||||
if (vr != CAPSULE_VALID) return TTF_ERR_BAD_TABLE;
|
||||
|
||||
payload = capsule_get_payload(cap, arena);
|
||||
if (!payload) return TTF_ERR_BAD_TABLE;
|
||||
|
||||
return ttf_parse(payload, (uint32_t) cap->length, out);
|
||||
}
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user