/** * @file vga.c * @brief Display driver supporting both VGA text mode and graphical framebuffer. * * Supports two modes depending on what GRUB provides: * - EGA text mode: writes character+attribute pairs to the text buffer * - Graphical (RGB) framebuffer: renders an embedded 8x16 bitmap font * to the pixel framebuffer provided by GRUB * * The mode is detected at init time from the global fb_info structure, * which kernel_main populates from the multiboot2 framebuffer tag. * * This driver registers itself via the REGISTER_DRIVER macro and is * automatically discovered during boot. */ #include "vga.h" #include "driver.h" #include "port_io.h" #include "pmm.h" #include "framebuffer.h" #include "font8x16.h" #include /* Debug helpers defined in kernel.c */ extern void offset_print(const char *str); extern void print_hex(uint32_t val); /* ================================================================ * Common state * ================================================================ */ /** Current text cursor position. */ static uint32_t cursor_row = 0; static uint32_t cursor_col = 0; /** Columns and rows of the text grid. */ static uint32_t text_cols = 80; static uint32_t text_rows = 25; /** Current color attribute (foreground | background << 4). */ static uint8_t text_attr = 0x07; /** Display mode: 0 = text, 1 = pixel. */ static int display_mode = 0; /* ================================================================ * Text mode (EGA) internals * ================================================================ */ /** VGA text-mode framebuffer default base. */ #define VGA_TEXT_BUFFER 0xB8000 static uint16_t *text_buffer = (uint16_t *)VGA_TEXT_BUFFER; static inline uint16_t vga_entry(char c, uint8_t attr) { return (uint16_t)c | ((uint16_t)attr << 8); } static void text_update_cursor(void) { uint16_t pos = (uint16_t)(cursor_row * text_cols + cursor_col); outb(0x3D4, 0x0F); outb(0x3D5, (uint8_t)(pos & 0xFF)); outb(0x3D4, 0x0E); outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF)); } static void text_scroll(void) { for (uint32_t i = 0; i < (text_rows - 1) * text_cols; i++) { text_buffer[i] = text_buffer[i + text_cols]; } uint16_t blank = vga_entry(' ', text_attr); for (uint32_t i = (text_rows - 1) * text_cols; i < text_rows * text_cols; i++) { text_buffer[i] = blank; } cursor_row = text_rows - 1; } static void text_clear(void) { uint16_t blank = vga_entry(' ', text_attr); for (uint32_t i = 0; i < text_cols * text_rows; i++) { text_buffer[i] = blank; } cursor_row = 0; cursor_col = 0; text_update_cursor(); } static void text_putchar(char c) { if (c == '\n') { cursor_col = 0; cursor_row++; } else if (c == '\r') { cursor_col = 0; } else if (c == '\t') { cursor_col = (cursor_col + 8) & ~7u; if (cursor_col >= text_cols) { cursor_col = 0; cursor_row++; } } else if (c == '\b') { if (cursor_col > 0) { cursor_col--; text_buffer[cursor_row * text_cols + cursor_col] = vga_entry(' ', text_attr); } } else { text_buffer[cursor_row * text_cols + cursor_col] = vga_entry(c, text_attr); cursor_col++; if (cursor_col >= text_cols) { cursor_col = 0; cursor_row++; } } if (cursor_row >= text_rows) { text_scroll(); } text_update_cursor(); } /* ================================================================ * Pixel mode (graphical framebuffer) internals * ================================================================ */ /** Pointer to the pixel framebuffer. */ static uint8_t *pixel_fb = (uint8_t *)0; /** Framebuffer parameters. */ static uint32_t fb_pitch = 0; static uint32_t fb_width = 0; static uint32_t fb_height = 0; static uint32_t fb_bpp = 0; /** RGB field info. */ static uint8_t fb_red_pos = 16, fb_red_size = 8; static uint8_t fb_green_pos = 8, fb_green_size = 8; static uint8_t fb_blue_pos = 0, fb_blue_size = 8; /** * Pack an RGB color into the framebuffer's native pixel format. */ static inline uint32_t pack_color(uint8_t r, uint8_t g, uint8_t b) { (void)fb_red_size; (void)fb_green_size; (void)fb_blue_size; return ((uint32_t)r << fb_red_pos) | ((uint32_t)g << fb_green_pos) | ((uint32_t)b << fb_blue_pos); } /** * Set a single pixel in the framebuffer. */ static inline void pixel_set(uint32_t x, uint32_t y, uint32_t color) { if (x >= fb_width || y >= fb_height) return; uint32_t offset = y * fb_pitch + x * (fb_bpp / 8); uint32_t bytes = fb_bpp / 8; if (bytes == 4) { *(volatile uint32_t *)(pixel_fb + offset) = color; } else if (bytes == 3) { pixel_fb[offset] = (uint8_t)(color & 0xFF); pixel_fb[offset + 1] = (uint8_t)((color >> 8) & 0xFF); pixel_fb[offset + 2] = (uint8_t)((color >> 16) & 0xFF); } else if (bytes == 2) { *(volatile uint16_t *)(pixel_fb + offset) = (uint16_t)color; } } /** * VGA color index to 24-bit RGB mapping. */ static const uint32_t vga_palette[16] = { 0x000000, /* 0 black */ 0x0000AA, /* 1 blue */ 0x00AA00, /* 2 green */ 0x00AAAA, /* 3 cyan */ 0xAA0000, /* 4 red */ 0xAA00AA, /* 5 magenta */ 0xAA5500, /* 6 brown */ 0xAAAAAA, /* 7 light grey */ 0x555555, /* 8 dark grey */ 0x5555FF, /* 9 light blue */ 0x55FF55, /* 10 light green */ 0x55FFFF, /* 11 light cyan */ 0xFF5555, /* 12 light red */ 0xFF55FF, /* 13 light magenta */ 0xFFFF55, /* 14 yellow */ 0xFFFFFF, /* 15 white */ }; /** * Get packed foreground/background colors from text_attr. */ static void attr_to_colors(uint32_t *fg_out, uint32_t *bg_out) { uint8_t fg_idx = text_attr & 0x0F; uint8_t bg_idx = (text_attr >> 4) & 0x0F; uint32_t fg_rgb = vga_palette[fg_idx]; uint32_t bg_rgb = vga_palette[bg_idx]; *fg_out = pack_color((fg_rgb >> 16) & 0xFF, (fg_rgb >> 8) & 0xFF, fg_rgb & 0xFF); *bg_out = pack_color((bg_rgb >> 16) & 0xFF, (bg_rgb >> 8) & 0xFF, bg_rgb & 0xFF); } /** * Render a single glyph at character grid position (col, row). */ static void pixel_render_char(uint32_t col, uint32_t row, char c) { uint32_t fg, bg; attr_to_colors(&fg, &bg); const uint8_t *glyph; if (c >= FONT_FIRST && c <= FONT_LAST) { glyph = font8x16_data[c - FONT_FIRST]; } else { glyph = 0; /* NULL = solid block for unknown chars */ } uint32_t px = col * FONT_WIDTH; uint32_t py = row * FONT_HEIGHT; for (uint32_t y = 0; y < FONT_HEIGHT; y++) { uint8_t bits = glyph ? glyph[y] : 0xFF; for (uint32_t x = 0; x < FONT_WIDTH; x++) { uint32_t color = (bits & (0x80 >> x)) ? fg : bg; pixel_set(px + x, py + y, color); } } } /** * Scroll the pixel framebuffer up by one text row (FONT_HEIGHT pixels). */ static void pixel_scroll(void) { uint32_t row_bytes = FONT_HEIGHT * fb_pitch; uint32_t total_text_bytes = text_rows * row_bytes; /* Move all rows up by one */ memcpy(pixel_fb, pixel_fb + row_bytes, total_text_bytes - row_bytes); /* Clear the last text row */ uint32_t dummy, bg; attr_to_colors(&dummy, &bg); uint32_t last_row_y = (text_rows - 1) * FONT_HEIGHT; for (uint32_t y = last_row_y; y < last_row_y + FONT_HEIGHT; y++) { for (uint32_t x = 0; x < text_cols * FONT_WIDTH; x++) { pixel_set(x, y, bg); } } cursor_row = text_rows - 1; } static void pixel_clear(void) { uint32_t dummy, bg; attr_to_colors(&dummy, &bg); for (uint32_t y = 0; y < fb_height; y++) { for (uint32_t x = 0; x < fb_width; x++) { pixel_set(x, y, bg); } } cursor_row = 0; cursor_col = 0; } static void pixel_putchar(char c) { if (c == '\n') { cursor_col = 0; cursor_row++; } else if (c == '\r') { cursor_col = 0; } else if (c == '\t') { cursor_col = (cursor_col + 8) & ~7u; if (cursor_col >= text_cols) { cursor_col = 0; cursor_row++; } } else if (c == '\b') { if (cursor_col > 0) { cursor_col--; pixel_render_char(cursor_col, cursor_row, ' '); } } else { pixel_render_char(cursor_col, cursor_row, c); cursor_col++; if (cursor_col >= text_cols) { cursor_col = 0; cursor_row++; } } if (cursor_row >= text_rows) { pixel_scroll(); } } /* ================================================================ * Public interface * ================================================================ */ void vga_clear(void) { if (display_mode == 0) text_clear(); else pixel_clear(); } void vga_set_color(vga_color_t fg, vga_color_t bg) { text_attr = (uint8_t)fg | ((uint8_t)bg << 4); } void vga_putchar(char c) { if (display_mode == 0) text_putchar(c); else pixel_putchar(c); } void vga_puts(const char *str) { while (*str) { vga_putchar(*str); str++; } } void vga_put_hex(uint32_t val) { const char *hex = "0123456789ABCDEF"; vga_putchar('0'); vga_putchar('x'); for (int i = 28; i >= 0; i -= 4) { vga_putchar(hex[(val >> i) & 0xF]); } } void vga_put_dec(uint32_t val) { if (val == 0) { vga_putchar('0'); return; } char buf[12]; int pos = 0; while (val > 0) { buf[pos++] = '0' + (val % 10); val /= 10; } while (pos > 0) { vga_putchar(buf[--pos]); } } void vga_show_mem_stats(void) { uint32_t mem_kb = pmm_get_memory_size() + 1024; vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK); vga_puts("=== ClaudeOS Memory Statistics ===\n"); vga_set_color(VGA_WHITE, VGA_BLACK); vga_puts(" Total RAM: "); vga_put_dec(mem_kb); vga_puts(" KiB ("); vga_put_dec(mem_kb / 1024); vga_puts(" MiB)\n"); vga_puts(" Kernel start: "); extern uint32_t _kernel_start; vga_put_hex((uint32_t)&_kernel_start); vga_puts("\n"); vga_puts(" Kernel end: "); extern uint32_t _kernel_end; vga_put_hex((uint32_t)&_kernel_end); vga_puts("\n"); uint32_t kernel_size = (uint32_t)&_kernel_end - (uint32_t)&_kernel_start; vga_puts(" Kernel size: "); vga_put_dec(kernel_size / 1024); vga_puts(" KiB\n"); vga_set_color(VGA_LIGHT_CYAN, VGA_BLACK); vga_puts("==================================\n"); vga_set_color(VGA_LIGHT_GREY, VGA_BLACK); } /* ================================================================ * Driver registration * ================================================================ */ static driver_probe_result_t vga_probe(void) { return DRIVER_PROBE_OK; } int vga_init(void) { text_attr = (uint8_t)VGA_LIGHT_GREY | ((uint8_t)VGA_BLACK << 4); if (fb_info.type == FB_TYPE_EGA_TEXT || fb_info.addr == 0) { /* Text mode (or no framebuffer tag — assume legacy text mode) */ display_mode = 0; text_cols = 80; text_rows = 25; if (fb_info.addr != 0) { text_buffer = (uint16_t *)(uint32_t)fb_info.addr; text_cols = fb_info.width; text_rows = fb_info.height; } offset_print(" VGA: text mode "); print_hex(text_cols); offset_print(" VGA: x "); print_hex(text_rows); } else if (fb_info.type == FB_TYPE_RGB) { /* Graphical framebuffer — render with bitmap font */ display_mode = 1; pixel_fb = (uint8_t *)(uint32_t)fb_info.addr; fb_pitch = fb_info.pitch; fb_width = fb_info.width; fb_height = fb_info.height; fb_bpp = fb_info.bpp; fb_red_pos = fb_info.red_pos; fb_red_size = fb_info.red_size; fb_green_pos = fb_info.green_pos; fb_green_size = fb_info.green_size; fb_blue_pos = fb_info.blue_pos; fb_blue_size = fb_info.blue_size; /* Calculate text grid from pixel dimensions */ text_cols = fb_width / FONT_WIDTH; text_rows = fb_height / FONT_HEIGHT; if (text_cols == 0) text_cols = 1; if (text_rows == 0) text_rows = 1; offset_print(" VGA: pixel mode "); print_hex(fb_width); offset_print(" VGA: x "); print_hex(fb_height); offset_print(" VGA: bpp="); print_hex(fb_bpp); offset_print(" VGA: text grid "); print_hex(text_cols); offset_print(" VGA: x "); print_hex(text_rows); offset_print(" VGA: addr="); print_hex((uint32_t)pixel_fb); } else { /* Indexed or unknown — fall back to text mode */ display_mode = 0; text_cols = 80; text_rows = 25; offset_print(" VGA: unknown fb type, assuming text mode\n"); } vga_clear(); vga_set_color(VGA_LIGHT_GREEN, VGA_BLACK); vga_puts("ClaudeOS v0.1 booting...\n\n"); vga_set_color(VGA_LIGHT_GREY, VGA_BLACK); return 0; } /** VGA driver descriptor. */ static const driver_t vga_driver = { .name = "vga", .probe = vga_probe, .init = vga_init, }; REGISTER_DRIVER(vga_driver);