diff --git a/README.md b/README.md index 977a249..9e25ead 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Once a task is completed, it should be checked off. - [x] Create a paging subsystem. It should allow drivers to allocate and deallocate pages at will. - [x] Create a memory allocator. This should provide the kernel with `malloc` and `free`. Internally, it should use the paging subsystem to ensure that the address it returns have actual RAM paged to them. - [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not. -- [ ] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress. +- [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress. - [ ] Create subsystem for loading new processes in Ring 3. - [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format. - [ ] Write a VFS subsystem. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c9e436f..908fac1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(kernel kmalloc.c string.c driver.c + vga.c interrupts.S kernel.c ) diff --git a/src/kernel.c b/src/kernel.c index 477397e..41110a2 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -9,6 +9,7 @@ #include "paging.h" #include "kmalloc.h" #include "driver.h" +#include "vga.h" void offset_print(const char *str) { @@ -71,6 +72,10 @@ void kernel_main(uint32_t magic, uint32_t addr) { init_drivers(); offset_print("Drivers initialized\n"); + /* Show memory statistics and boot progress on VGA */ + vga_show_mem_stats(); + vga_puts("Boot complete.\n\n"); + /* Test kmalloc/kfree */ uint32_t *test_alloc = (uint32_t *)kmalloc(64); if (test_alloc) { diff --git a/src/vga.c b/src/vga.c new file mode 100644 index 0000000..81c7b8f --- /dev/null +++ b/src/vga.c @@ -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); diff --git a/src/vga.h b/src/vga.h new file mode 100644 index 0000000..83510f0 --- /dev/null +++ b/src/vga.h @@ -0,0 +1,97 @@ +/** + * @file vga.h + * @brief VGA text-mode driver interface. + * + * Provides functions to write text to the VGA text-mode framebuffer + * (typically at 0xB8000). Supports an 80x25 character display with + * 16 foreground and 16 background colors. + */ + +#ifndef VGA_H +#define VGA_H + +#include +#include + +/** VGA color constants. */ +typedef enum { + VGA_BLACK = 0, + VGA_BLUE = 1, + VGA_GREEN = 2, + VGA_CYAN = 3, + VGA_RED = 4, + VGA_MAGENTA = 5, + VGA_BROWN = 6, + VGA_LIGHT_GREY = 7, + VGA_DARK_GREY = 8, + VGA_LIGHT_BLUE = 9, + VGA_LIGHT_GREEN = 10, + VGA_LIGHT_CYAN = 11, + VGA_LIGHT_RED = 12, + VGA_LIGHT_MAGENTA = 13, + VGA_YELLOW = 14, + VGA_WHITE = 15, +} vga_color_t; + +/** VGA screen dimensions. */ +#define VGA_WIDTH 80 +#define VGA_HEIGHT 25 + +/** + * Initialize the VGA driver. + * + * Clears the screen and sets the default colors. + * + * @return 0 on success. + */ +int vga_init(void); + +/** + * Clear the VGA screen. + */ +void vga_clear(void); + +/** + * Set the foreground and background color for subsequent writes. + * + * @param fg Foreground color. + * @param bg Background color. + */ +void vga_set_color(vga_color_t fg, vga_color_t bg); + +/** + * Write a single character at the current cursor position. + * + * @param c Character to write. + */ +void vga_putchar(char c); + +/** + * Write a null-terminated string at the current cursor position. + * + * @param str String to write. + */ +void vga_puts(const char *str); + +/** + * Write a 32-bit value in hexadecimal to the VGA display. + * + * @param val Value to display. + */ +void vga_put_hex(uint32_t val); + +/** + * Write a 32-bit value in decimal to the VGA display. + * + * @param val Value to display. + */ +void vga_put_dec(uint32_t val); + +/** + * Display boot memory statistics on VGA. + * + * Shows detected memory, kernel size, and free pages. + */ +void vga_show_mem_stats(void); + +#endif /* VGA_H */