Files
claude-os/src/vga.c
AI e3d011da2f Fix UTM display: remove required framebuffer tag, add serial output, ls app
Display fixes for UTM/Mac black screen issue:
- Remove MULTIBOOT_HEADER_TAG_FRAMEBUFFER from multiboot2 header (was required,
  requesting text mode depth=0 which confused GRUB on some platforms)
- Add COM1 serial port (0x3F8) output alongside debugcon for UTM serial capture
- Change VGA background from black to dark blue for diagnostics
- Add early canary write to 0xB8000 ('COS' in magenta) before subsystem init
- print_hex now outputs to both debugcon and COM1

New ls command and SYS_READDIR syscall:
- SYS_READDIR (10): reads directory entries via VFS
- VFS root listing: vfs_readdir handles '/' by iterating mount table
- apps/ls: lists CWD contents, appends '/' for directories
- apps/libc/syscalls.h: readdir() wrapper
2026-02-23 13:36:34 +00:00

466 lines
13 KiB
C

/**
* @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 <string.h>
/* 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_BLUE);
vga_puts("=== ClaudeOS Memory Statistics ===\n");
vga_set_color(VGA_WHITE, VGA_BLUE);
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_BLUE);
vga_puts("==================================\n");
vga_set_color(VGA_LIGHT_GREY, VGA_BLUE);
}
/* ================================================================
* 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");
}
/* Use dark blue background so user can distinguish "rendering works
* but text invisible" from "framebuffer not working at all". */
vga_set_color(VGA_LIGHT_GREY, VGA_BLUE);
vga_clear();
vga_set_color(VGA_LIGHT_GREEN, VGA_BLUE);
vga_puts("ClaudeOS v0.1 booting...\n\n");
vga_set_color(VGA_LIGHT_GREY, VGA_BLUE);
return 0;
}
/** VGA driver descriptor. */
static const driver_t vga_driver = {
.name = "vga",
.probe = vga_probe,
.init = vga_init,
};
REGISTER_DRIVER(vga_driver);