From d8fad1765e9fd456dd198275d0470f0e6adde7db Mon Sep 17 00:00:00 2001 From: Emin Arslan Date: Tue, 5 May 2026 22:18:56 +0300 Subject: [PATCH] feat: add multiboot2 framebuffer and Forth pixel primitives Co-authored-by: aider (openrouter/moonshotai/kimi-k2.6) --- Makefile | 2 +- font8x8.c | 100 +++++++++++++++++++++++++++ forth.h | 8 +++ kernel_entry.S | 19 ++++++ main.c | 8 +++ platform_kernel.c | 171 ++++++++++++++++++++++++++++++++++++++++------ platform_linux.c | 7 ++ 7 files changed, 294 insertions(+), 21 deletions(-) create mode 100644 font8x8.c diff --git a/Makefile b/Makefile index ae4482c..d03f74c 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/font8x8.c b/font8x8.c new file mode 100644 index 0000000..0e2a16d --- /dev/null +++ b/font8x8.c @@ -0,0 +1,100 @@ +#include + +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 */ +}; diff --git a/forth.h b/forth.h index 0c41551..36d5707 100644 --- a/forth.h +++ b/forth.h @@ -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); diff --git a/kernel_entry.S b/kernel_entry.S index 2e37f47..bc12232 100644 --- a/kernel_entry.S +++ b/kernel_entry.S @@ -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: diff --git a/main.c b/main.c index 719344f..3f3a18d 100644 --- a/main.c +++ b/main.c @@ -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); diff --git a/platform_kernel.c b/platform_kernel.c index b669a04..c8a0cad 100644 --- a/platform_kernel.c +++ b/platform_kernel.c @@ -2,27 +2,102 @@ #include #include -// 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) { - if (c == '\n') { - vga_col = 0; - vga_row++; - } 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++; +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]; } } - if (vga_row >= 25) { - vga_row = 0; - vga_col = 0; + 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') { + fb_col = 0; + fb_row++; + } else if (c == '\r') { + fb_col = 0; + } else if (c == '\b') { + if (fb_col > 0) fb_col--; + } else { + 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); + } + } + } + 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); +} diff --git a/platform_linux.c b/platform_linux.c index 770bf10..e048f92 100644 --- a/platform_linux.c +++ b/platform_linux.c @@ -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);