Implement VGA text-mode driver with memory statistics display (AI)
- Created VGA driver that writes to the 0xB8000 text-mode framebuffer.
Supports 80x25 display with 16 foreground/background colors, scrolling,
hardware cursor updates, and special characters (\n, \r, \t, \b).
- Provides vga_puts, vga_putchar, vga_put_hex, vga_put_dec, vga_set_color.
- Displays boot banner ("ClaudeOS v0.1 booting...") on screen clear.
- vga_show_mem_stats() prints total RAM, kernel start/end addresses, and
kernel size on the VGA display during boot.
- Registered as the first driver using REGISTER_DRIVER, proving the
driver framework works end-to-end (probe -> init lifecycle).
Tested: driver loads successfully, debug port confirms vga probe/init.
This commit is contained in:
230
src/vga.c
Normal file
230
src/vga.c
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* @file vga.c
|
||||
* @brief VGA text-mode driver implementation.
|
||||
*
|
||||
* Drives the standard VGA text-mode framebuffer at 0xB8000. The buffer
|
||||
* is an array of 80×25 16-bit values, where the low byte is the ASCII
|
||||
* character and the high byte encodes foreground and background color.
|
||||
*
|
||||
* 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"
|
||||
|
||||
/** Base address of the VGA text-mode framebuffer. */
|
||||
#define VGA_BUFFER 0xB8000
|
||||
|
||||
/** Pointer to the VGA framebuffer, treated as an array of uint16_t. */
|
||||
static uint16_t *vga_buffer = (uint16_t *)VGA_BUFFER;
|
||||
|
||||
/** Current cursor row (0-based). */
|
||||
static uint8_t cursor_row = 0;
|
||||
|
||||
/** Current cursor column (0-based). */
|
||||
static uint8_t cursor_col = 0;
|
||||
|
||||
/** Current text attribute byte (foreground | background << 4). */
|
||||
static uint8_t text_attr = 0;
|
||||
|
||||
/**
|
||||
* Create a VGA entry (character + attribute) for the framebuffer.
|
||||
*
|
||||
* @param c ASCII character.
|
||||
* @param attr Color attribute byte.
|
||||
* @return 16-bit VGA entry.
|
||||
*/
|
||||
static inline uint16_t vga_entry(char c, uint8_t attr) {
|
||||
return (uint16_t)c | ((uint16_t)attr << 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a color attribute byte from foreground and background colors.
|
||||
*
|
||||
* @param fg Foreground color (0–15).
|
||||
* @param bg Background color (0–15).
|
||||
* @return Attribute byte.
|
||||
*/
|
||||
static inline uint8_t vga_color(vga_color_t fg, vga_color_t bg) {
|
||||
return (uint8_t)fg | ((uint8_t)bg << 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the hardware cursor position via VGA I/O ports.
|
||||
*/
|
||||
static void update_cursor(void) {
|
||||
uint16_t pos = cursor_row * VGA_WIDTH + cursor_col;
|
||||
|
||||
outb(0x3D4, 0x0F);
|
||||
outb(0x3D5, (uint8_t)(pos & 0xFF));
|
||||
outb(0x3D4, 0x0E);
|
||||
outb(0x3D5, (uint8_t)((pos >> 8) & 0xFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the screen up by one line.
|
||||
*
|
||||
* The top line is discarded, all other lines move up, and the bottom
|
||||
* line is filled with spaces.
|
||||
*/
|
||||
static void scroll(void) {
|
||||
/* Move all lines up by one */
|
||||
for (int i = 0; i < (VGA_HEIGHT - 1) * VGA_WIDTH; i++) {
|
||||
vga_buffer[i] = vga_buffer[i + VGA_WIDTH];
|
||||
}
|
||||
|
||||
/* Clear the last line */
|
||||
uint16_t blank = vga_entry(' ', text_attr);
|
||||
for (int i = (VGA_HEIGHT - 1) * VGA_WIDTH; i < VGA_HEIGHT * VGA_WIDTH; i++) {
|
||||
vga_buffer[i] = blank;
|
||||
}
|
||||
|
||||
cursor_row = VGA_HEIGHT - 1;
|
||||
}
|
||||
|
||||
void vga_clear(void) {
|
||||
uint16_t blank = vga_entry(' ', text_attr);
|
||||
for (int i = 0; i < VGA_WIDTH * VGA_HEIGHT; i++) {
|
||||
vga_buffer[i] = blank;
|
||||
}
|
||||
cursor_row = 0;
|
||||
cursor_col = 0;
|
||||
update_cursor();
|
||||
}
|
||||
|
||||
void vga_set_color(vga_color_t fg, vga_color_t bg) {
|
||||
text_attr = vga_color(fg, bg);
|
||||
}
|
||||
|
||||
void vga_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) & ~7;
|
||||
if (cursor_col >= VGA_WIDTH) {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
}
|
||||
} else if (c == '\b') {
|
||||
if (cursor_col > 0) {
|
||||
cursor_col--;
|
||||
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(' ', text_attr);
|
||||
}
|
||||
} else {
|
||||
vga_buffer[cursor_row * VGA_WIDTH + cursor_col] = vga_entry(c, text_attr);
|
||||
cursor_col++;
|
||||
if (cursor_col >= VGA_WIDTH) {
|
||||
cursor_col = 0;
|
||||
cursor_row++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cursor_row >= VGA_HEIGHT) {
|
||||
scroll();
|
||||
}
|
||||
|
||||
update_cursor();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
/* Print in reverse */
|
||||
while (pos > 0) {
|
||||
vga_putchar(buf[--pos]);
|
||||
}
|
||||
}
|
||||
|
||||
void vga_show_mem_stats(void) {
|
||||
uint32_t mem_kb = pmm_get_memory_size() + 1024; /* total including lower */
|
||||
|
||||
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 --- */
|
||||
|
||||
/**
|
||||
* VGA probe: always succeeds since VGA text mode is always available
|
||||
* on the target platform (i386).
|
||||
*/
|
||||
static driver_probe_result_t vga_probe(void) {
|
||||
return DRIVER_PROBE_OK;
|
||||
}
|
||||
|
||||
int vga_init(void) {
|
||||
text_attr = vga_color(VGA_LIGHT_GREY, VGA_BLACK);
|
||||
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);
|
||||
Reference in New Issue
Block a user