feat: add multiboot2 framebuffer and Forth pixel primitives

Co-authored-by: aider (openrouter/moonshotai/kimi-k2.6) <aider@aider.chat>
This commit is contained in:
2026-05-05 22:18:56 +03:00
parent d73ace71fb
commit d8fad1765e
7 changed files with 294 additions and 21 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ CFLAGS = -Wall -Wextra -g -std=c11
COMMON_SRCS = forth_core.c forth_dict.c forth_words.c forth_interp.c main.c
LINUX_SRCS = $(COMMON_SRCS) platform_linux.c
KERNEL_SRCS = $(COMMON_SRCS) platform_kernel.c kernel_entry.S
KERNEL_SRCS = $(COMMON_SRCS) platform_kernel.c kernel_entry.S font8x8.c
KERNEL_LD = kernel.ld
TARGET = forth
+100
View File
@@ -0,0 +1,100 @@
#include <stdint.h>
const uint8_t font8x8[96][8] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 32 ' ' */
{0x18,0x18,0x18,0x18,0x18,0x00,0x18,0x00}, /* 33 '!' */
{0x66,0x66,0x66,0x00,0x00,0x00,0x00,0x00}, /* 34 '"' */
{0x66,0x66,0xFF,0x66,0xFF,0x66,0x66,0x00}, /* 35 '#' */
{0x18,0x3E,0x60,0x3C,0x06,0x7C,0x18,0x00}, /* 36 '$' */
{0x62,0x66,0x0C,0x18,0x30,0x66,0x46,0x00}, /* 37 '%' */
{0x3C,0x66,0x3C,0x38,0x67,0x66,0x3F,0x00}, /* 38 '&' */
{0x06,0x0C,0x18,0x00,0x00,0x00,0x00,0x00}, /* 39 ''' */
{0x0C,0x18,0x30,0x30,0x30,0x18,0x0C,0x00}, /* 40 '(' */
{0x30,0x18,0x0C,0x0C,0x0C,0x18,0x30,0x00}, /* 41 ')' */
{0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00}, /* 42 '*' */
{0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00}, /* 43 '+' */
{0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30}, /* 44 ',' */
{0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00}, /* 45 '-' */
{0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00}, /* 46 '.' */
{0x00,0x03,0x06,0x0C,0x18,0x30,0x60,0x00}, /* 47 '/' */
{0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00}, /* 48 '0' */
{0x18,0x18,0x38,0x18,0x18,0x18,0x7E,0x00}, /* 49 '1' */
{0x3C,0x66,0x06,0x0C,0x30,0x60,0x7E,0x00}, /* 50 '2' */
{0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00}, /* 51 '3' */
{0x06,0x0E,0x1E,0x66,0x7F,0x06,0x06,0x00}, /* 52 '4' */
{0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00}, /* 53 '5' */
{0x3C,0x60,0x60,0x7C,0x66,0x66,0x3C,0x00}, /* 54 '6' */
{0x7E,0x06,0x0C,0x18,0x30,0x30,0x30,0x00}, /* 55 '7' */
{0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00}, /* 56 '8' */
{0x3C,0x66,0x66,0x3E,0x06,0x06,0x3C,0x00}, /* 57 '9' */
{0x00,0x00,0x18,0x00,0x00,0x18,0x00,0x00}, /* 58 ':' */
{0x00,0x00,0x18,0x00,0x00,0x18,0x18,0x30}, /* 59 ';' */
{0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x00}, /* 60 '<' */
{0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00}, /* 61 '=' */
{0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x00}, /* 62 '>' */
{0x3C,0x66,0x06,0x0C,0x18,0x00,0x18,0x00}, /* 63 '?' */
{0x3C,0x66,0x6E,0x6E,0x60,0x62,0x3C,0x00}, /* 64 '@' */
{0x18,0x3C,0x66,0x7E,0x66,0x66,0x66,0x00}, /* 65 'A' */
{0x7C,0x66,0x66,0x7C,0x66,0x66,0x7C,0x00}, /* 66 'B' */
{0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00}, /* 67 'C' */
{0x78,0x6C,0x66,0x66,0x66,0x6C,0x78,0x00}, /* 68 'D' */
{0x7E,0x60,0x60,0x78,0x60,0x60,0x7E,0x00}, /* 69 'E' */
{0x7E,0x60,0x60,0x78,0x60,0x60,0x60,0x00}, /* 70 'F' */
{0x3C,0x66,0x60,0x6E,0x66,0x66,0x3C,0x00}, /* 71 'G' */
{0x66,0x66,0x66,0x7E,0x66,0x66,0x66,0x00}, /* 72 'H' */
{0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00}, /* 73 'I' */
{0x1E,0x0C,0x0C,0x0C,0x0C,0x6C,0x38,0x00}, /* 74 'J' */
{0x66,0x6C,0x78,0x70,0x78,0x6C,0x66,0x00}, /* 75 'K' */
{0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x00}, /* 76 'L' */
{0x63,0x77,0x7F,0x6B,0x63,0x63,0x63,0x00}, /* 77 'M' */
{0x66,0x76,0x7E,0x7E,0x6E,0x66,0x66,0x00}, /* 78 'N' */
{0x3C,0x66,0x66,0x66,0x66,0x66,0x3C,0x00}, /* 79 'O' */
{0x7C,0x66,0x66,0x7C,0x60,0x60,0x60,0x00}, /* 80 'P' */
{0x3C,0x66,0x66,0x66,0x66,0x3C,0x0E,0x00}, /* 81 'Q' */
{0x7C,0x66,0x66,0x7C,0x78,0x6C,0x66,0x00}, /* 82 'R' */
{0x3C,0x66,0x60,0x3C,0x06,0x66,0x3C,0x00}, /* 83 'S' */
{0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00}, /* 84 'T' */
{0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x00}, /* 85 'U' */
{0x66,0x66,0x66,0x66,0x66,0x3C,0x18,0x00}, /* 86 'V' */
{0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00}, /* 87 'W' */
{0x66,0x66,0x3C,0x18,0x3C,0x66,0x66,0x00}, /* 88 'X' */
{0x66,0x66,0x66,0x3C,0x18,0x18,0x18,0x00}, /* 89 'Y' */
{0x7E,0x06,0x0C,0x18,0x30,0x60,0x7E,0x00}, /* 90 'Z' */
{0x3C,0x30,0x30,0x30,0x30,0x30,0x3C,0x00}, /* 91 '[' */
{0x00,0x60,0x30,0x18,0x0C,0x06,0x03,0x00}, /* 92 '\' */
{0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00}, /* 93 ']' */
{0x18,0x3C,0x66,0x00,0x00,0x00,0x00,0x00}, /* 94 '^' */
{0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x00}, /* 95 '_' */
{0x18,0x18,0x0C,0x00,0x00,0x00,0x00,0x00}, /* 96 '`' */
{0x00,0x00,0x3C,0x06,0x3E,0x66,0x3E,0x00}, /* 97 'a' */
{0x00,0x60,0x60,0x7C,0x66,0x66,0x7C,0x00}, /* 98 'b' */
{0x00,0x00,0x3C,0x60,0x60,0x60,0x3C,0x00}, /* 99 'c' */
{0x00,0x06,0x06,0x3E,0x66,0x66,0x3E,0x00}, /* 100 'd' */
{0x00,0x00,0x3C,0x66,0x7E,0x60,0x3C,0x00}, /* 101 'e' */
{0x00,0x0E,0x18,0x3E,0x18,0x18,0x18,0x00}, /* 102 'f' */
{0x00,0x00,0x3E,0x66,0x66,0x3E,0x06,0x7C}, /* 103 'g' */
{0x00,0x60,0x60,0x7C,0x66,0x66,0x66,0x00}, /* 104 'h' */
{0x00,0x18,0x00,0x38,0x18,0x18,0x3C,0x00}, /* 105 'i' */
{0x00,0x06,0x00,0x06,0x06,0x06,0x06,0x3C}, /* 106 'j' */
{0x00,0x60,0x60,0x6C,0x78,0x6C,0x66,0x00}, /* 107 'k' */
{0x00,0x38,0x18,0x18,0x18,0x18,0x3C,0x00}, /* 108 'l' */
{0x00,0x00,0x66,0x7F,0x6B,0x63,0x63,0x00}, /* 109 'm' */
{0x00,0x00,0x7C,0x66,0x66,0x66,0x66,0x00}, /* 110 'n' */
{0x00,0x00,0x3C,0x66,0x66,0x66,0x3C,0x00}, /* 111 'o' */
{0x00,0x00,0x7C,0x66,0x66,0x7C,0x60,0x60}, /* 112 'p' */
{0x00,0x00,0x3E,0x66,0x66,0x3E,0x06,0x06}, /* 113 'q' */
{0x00,0x00,0x7C,0x66,0x60,0x60,0x60,0x00}, /* 114 'r' */
{0x00,0x00,0x3E,0x60,0x3C,0x06,0x7C,0x00}, /* 115 's' */
{0x00,0x18,0x18,0x7E,0x18,0x18,0x0C,0x00}, /* 116 't' */
{0x00,0x00,0x66,0x66,0x66,0x66,0x3E,0x00}, /* 117 'u' */
{0x00,0x00,0x66,0x66,0x66,0x3C,0x18,0x00}, /* 118 'v' */
{0x00,0x00,0x63,0x6B,0x7F,0x3E,0x36,0x00}, /* 119 'w' */
{0x00,0x00,0x66,0x3C,0x18,0x3C,0x66,0x00}, /* 120 'x' */
{0x00,0x00,0x66,0x66,0x66,0x3E,0x0C,0x78}, /* 121 'y' */
{0x00,0x00,0x7E,0x0C,0x18,0x30,0x7E,0x00}, /* 122 'z' */
{0x0C,0x18,0x18,0x30,0x18,0x18,0x0C,0x00}, /* 123 '{' */
{0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00}, /* 124 '|' */
{0x30,0x18,0x18,0x0C,0x18,0x18,0x30,0x00}, /* 125 '}' */
{0x00,0x00,0x66,0x3C,0x00,0x00,0x00,0x00}, /* 126 '~' */
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 127 DEL */
};
+8
View File
@@ -204,6 +204,14 @@ void do_dot_quote(Word* w);
void do_dot_quote_inner(Word* w);
void do_words(Word* w);
// Framebuffer
void do_fb_addr(Word* w);
void do_fb_width(Word* w);
void do_fb_height(Word* w);
void do_fb_pitch(Word* w);
void do_fb_bpp(Word* w);
void do_fb_plot(Word* w);
// Memory
void do_fetch(Word* w);
void do_store(Word* w);
+19
View File
@@ -8,6 +8,19 @@ multiboot_header:
.long multiboot_header_end - multiboot_header
.long -(0xe85250d6 + 0 + (multiboot_header_end - multiboot_header))
/* Framebuffer tag: type=5, prefer RGB 32 bpp */
.align 8
.word 5
.word 0
.long 32
.quad 0
.long 0
.long 0
.long 32
.byte 1
.byte 0
.word 0
.align 8
.word 0
.word 0
@@ -28,12 +41,17 @@ stack_bottom:
.space 16384
stack_top:
.align 8
mboot_info_ptr:
.space 8
.section .text
.code32
.global _start
.type _start, @function
_start:
movl $stack_top, %esp
movl %ebx, mboot_info_ptr
cld
movl $__bss_start, %edi
@@ -96,6 +114,7 @@ long_mode_start:
/* Ensure 16-byte alignment for the System V AMD64 ABI. */
andq $-16, %rsp
movq mboot_info_ptr, %rdi
call kernel_main
cli
1:
+8
View File
@@ -65,6 +65,14 @@ void forth_run(void) {
add_primitive(".\"", do_dot_quote, F_IMMEDIATE); // immediate
add_primitive("words", do_words, 0);
// Framebuffer
add_primitive("fb-addr", do_fb_addr, 0);
add_primitive("fb-width", do_fb_width, 0);
add_primitive("fb-height", do_fb_height, 0);
add_primitive("fb-pitch", do_fb_pitch, 0);
add_primitive("fb-bpp", do_fb_bpp, 0);
add_primitive("fb-plot", do_fb_plot, 0);
// Memory
add_primitive("@", do_fetch, 0);
add_primitive("!", do_store, 0);
+149 -18
View File
@@ -2,27 +2,102 @@
#include <stdint.h>
#include <stdarg.h>
// VGA text-mode buffer
static uint16_t* vga_buffer = (uint16_t*)0xB8000;
static int vga_row = 0;
static int vga_col = 0;
extern const uint8_t font8x8[96][8];
static void vga_putchar(char c) {
struct mboot_tag {
uint32_t type;
uint32_t size;
};
struct mboot_tag_framebuffer {
uint32_t type;
uint32_t size;
uint64_t framebuffer_addr;
uint32_t framebuffer_pitch;
uint32_t framebuffer_width;
uint32_t framebuffer_height;
uint8_t framebuffer_bpp;
uint8_t framebuffer_type;
uint16_t reserved;
};
static uint64_t fb_addr = 0;
static uint32_t fb_pitch = 0;
static uint32_t fb_width = 0;
static uint32_t fb_height = 0;
static uint8_t fb_bpp = 0;
static int fb_cols = 0;
static int fb_rows = 0;
static int fb_col = 0;
static int fb_row = 0;
static void fb_draw_pixel(int x, int y, uint32_t color) {
if (x < 0 || y < 0 || (uint32_t)x >= fb_width || (uint32_t)y >= fb_height) return;
if (!fb_addr) return;
if (fb_bpp == 32) {
uint32_t* p = (uint32_t*)(fb_addr + y * fb_pitch + x * 4);
*p = color;
} else if (fb_bpp == 24) {
uint8_t* p = (uint8_t*)(fb_addr + y * fb_pitch + x * 3);
p[0] = color & 0xFF;
p[1] = (color >> 8) & 0xFF;
p[2] = (color >> 16) & 0xFF;
}
}
static void fb_scroll(void) {
int line_height = 8;
uint32_t bytes_per_pixel = (fb_bpp + 7) / 8;
uint32_t copy_bytes = fb_width * bytes_per_pixel;
for (uint32_t y = 0; y < fb_height - line_height; y++) {
uint8_t* dst = (uint8_t*)(fb_addr + y * fb_pitch);
uint8_t* src = (uint8_t*)(fb_addr + (y + line_height) * fb_pitch);
for (uint32_t i = 0; i < copy_bytes; i++) {
dst[i] = src[i];
}
}
for (uint32_t y = fb_height - line_height; y < fb_height; y++) {
uint8_t* row = (uint8_t*)(fb_addr + y * fb_pitch);
for (uint32_t i = 0; i < copy_bytes; i++) {
row[i] = 0;
}
}
}
static void fb_draw_char(char c) {
if (!fb_addr || fb_cols == 0) return;
if (c == '\n') {
vga_col = 0;
vga_row++;
fb_col = 0;
fb_row++;
} else if (c == '\r') {
fb_col = 0;
} else if (c == '\b') {
if (fb_col > 0) fb_col--;
} else {
int idx = vga_row * 80 + vga_col;
vga_buffer[idx] = (uint16_t)((0x0F << 8) | (unsigned char)c);
vga_col++;
if (vga_col >= 80) {
vga_col = 0;
vga_row++;
unsigned char ch = (unsigned char)c;
if (ch < 32 || ch > 126) ch = '?';
const uint8_t* glyph = font8x8[ch - 32];
int start_x = fb_col * 8;
int start_y = fb_row * 8;
for (int dy = 0; dy < 8; dy++) {
uint8_t row = glyph[dy];
for (int dx = 0; dx < 8; dx++) {
if (row & (0x80 >> dx)) {
fb_draw_pixel(start_x + dx, start_y + dy, 0xFFFFFF);
} else {
fb_draw_pixel(start_x + dx, start_y + dy, 0x000000);
}
}
if (vga_row >= 25) {
vga_row = 0;
vga_col = 0;
}
fb_col++;
}
if (fb_col >= fb_cols) {
fb_col = 0;
fb_row++;
}
if (fb_row >= fb_rows) {
fb_scroll();
fb_row = fb_rows - 1;
}
}
@@ -149,7 +224,7 @@ void keyboard_handler(void) {
}
void forth_putchar(char c) {
vga_putchar(c);
fb_draw_char(c);
while ((inb(0x3F8 + 5) & 0x20) == 0) { }
outb(0x3F8, (uint8_t)c);
}
@@ -239,9 +314,32 @@ void forth_printf(const char* fmt, ...) {
va_end(ap);
}
void kernel_main(void) {
void kernel_main(uint64_t mboot_ptr) {
extern void isr_keyboard(void);
struct mboot_tag* tag = (struct mboot_tag*)(mboot_ptr + 8);
while (tag->type != 0) {
if (tag->type == 5) {
struct mboot_tag_framebuffer* fb = (struct mboot_tag_framebuffer*)tag;
fb_addr = fb->framebuffer_addr;
fb_pitch = fb->framebuffer_pitch;
fb_width = fb->framebuffer_width;
fb_height = fb->framebuffer_height;
fb_bpp = fb->framebuffer_bpp;
}
tag = (struct mboot_tag*)((uint8_t*)tag + ((tag->size + 7) & ~7));
}
if (fb_addr && fb_bpp >= 24) {
fb_cols = fb_width / 8;
fb_rows = fb_height / 8;
for (uint32_t y = 0; y < fb_height; y++) {
for (uint32_t x = 0; x < fb_width; x++) {
fb_draw_pixel(x, y, 0);
}
}
}
serial_init();
idt_set_gate(0x21, (uint64_t)isr_keyboard, 0x08, 0x8E);
@@ -260,3 +358,36 @@ void kernel_main(void) {
forth_run();
forth_panic();
}
void do_fb_addr(Word* w) {
(void)w;
data_push((int64_t)fb_addr);
}
void do_fb_width(Word* w) {
(void)w;
data_push((int64_t)fb_width);
}
void do_fb_height(Word* w) {
(void)w;
data_push((int64_t)fb_height);
}
void do_fb_pitch(Word* w) {
(void)w;
data_push((int64_t)fb_pitch);
}
void do_fb_bpp(Word* w) {
(void)w;
data_push((int64_t)fb_bpp);
}
void do_fb_plot(Word* w) {
(void)w;
int64_t color = data_pop();
int64_t y = data_pop();
int64_t x = data_pop();
fb_draw_pixel((int)x, (int)y, (uint32_t)color);
}
+7
View File
@@ -107,6 +107,13 @@ void forth_printf(const char* fmt, ...) {
va_end(ap);
}
void do_fb_addr(Word* w) { (void)w; data_push(0); }
void do_fb_width(Word* w) { (void)w; data_push(0); }
void do_fb_height(Word* w) { (void)w; data_push(0); }
void do_fb_pitch(Word* w) { (void)w; data_push(0); }
void do_fb_bpp(Word* w) { (void)w; data_push(0); }
void do_fb_plot(Word* w) { (void)w; data_pop(); data_pop(); data_pop(); }
void _start(void) {
forth_run();
syscall1(60, 0);