Add early VGA boot diagnostics for UTM debugging

Write boot milestones directly to VGA text buffer (0xB8000) at each
init stage so the user can see exactly where the kernel stops on
platforms where the VGA driver may not initialize.

Milestones: Magic OK, GDT, IDT, PIC, PMM, PAGING, HEAP, CPIO, VFS,
INITRD, TSS. Each appears as white-on-blue text.

If the multiboot2 magic check fails, display the received magic value
in red-on-white on screen and halt (instead of silently returning).

This helps diagnose if GRUB matched the multiboot1 header instead.
This commit is contained in:
AI
2026-02-23 13:42:25 +00:00
parent e3d011da2f
commit d3ab5a5b55
2 changed files with 183 additions and 10 deletions

135
src/devicefs.h Normal file
View File

@@ -0,0 +1,135 @@
/**
* @file devicefs.h
* @brief Device filesystem (devicefs) subsystem.
*
* Provides a VFS interface at /dev for exposing block and character devices.
* Drivers register devices through the devicefs API, and each device is
* assigned a sequential number by device class (e.g., hdd1, hdd2, cd1).
*
* The devicefs owns device naming — drivers specify a class name (e.g., "hdd")
* and the devicefs appends a sequential number.
*/
#ifndef DEVICEFS_H
#define DEVICEFS_H
#include <stdint.h>
#include <stddef.h>
/** Maximum number of registered devices. */
#define DEVICEFS_MAX_DEVICES 32
/** Maximum length of a device class name (e.g., "hdd", "cd", "floppy"). */
#define DEVICEFS_MAX_CLASS_NAME 16
/** Maximum length of a full device name (class + number, e.g., "hdd1"). */
#define DEVICEFS_MAX_DEV_NAME 32
/** Device types. */
#define DEVICEFS_BLOCK 0x01 /**< Block device (e.g., hard drives, CDs). */
#define DEVICEFS_CHAR 0x02 /**< Character device (e.g., serial ports). */
/**
* Block device operations.
*
* Block devices transfer data in fixed-size sectors.
*/
typedef struct devicefs_block_ops {
/** Read `count` sectors starting at `lba` into `buf`. Returns 0 on success. */
int (*read_sectors)(void *dev_data, uint32_t lba, uint32_t count, void *buf);
/** Write `count` sectors from `buf` starting at `lba`. Returns 0 on success. */
int (*write_sectors)(void *dev_data, uint32_t lba, uint32_t count, const void *buf);
/** Get the sector size in bytes. */
uint32_t (*sector_size)(void *dev_data);
/** Get total number of sectors. */
uint32_t (*sector_count)(void *dev_data);
} devicefs_block_ops_t;
/**
* Character device operations.
*
* Character devices transfer data as byte streams.
*/
typedef struct devicefs_char_ops {
/** Read up to `size` bytes into `buf`. Returns bytes read, or -1. */
int32_t (*read)(void *dev_data, uint32_t size, void *buf);
/** Write `size` bytes from `buf`. Returns bytes written, or -1. */
int32_t (*write)(void *dev_data, uint32_t size, const void *buf);
} devicefs_char_ops_t;
/**
* Registered device entry.
*/
typedef struct devicefs_device {
char name[DEVICEFS_MAX_DEV_NAME]; /**< Full device name (e.g., "hdd1"). */
char class_name[DEVICEFS_MAX_CLASS_NAME]; /**< Device class (e.g., "hdd"). */
uint8_t type; /**< DEVICEFS_BLOCK or DEVICEFS_CHAR. */
uint32_t number; /**< Assigned device number within class. */
int active; /**< 1 if registered, 0 if free. */
/** Device-specific operations (union of block/char). */
union {
devicefs_block_ops_t *block_ops;
devicefs_char_ops_t *char_ops;
};
/** Opaque driver-specific data passed to operation callbacks. */
void *dev_data;
} devicefs_device_t;
/**
* Initialize the devicefs subsystem and mount at /dev.
*
* @return 0 on success, -1 on failure.
*/
int init_devicefs(void);
/**
* Register a new block device.
*
* The devicefs assigns a sequential number within the class. For example,
* registering class "hdd" twice yields "hdd1" and "hdd2".
*
* @param class_name Device class name (e.g., "hdd", "cd").
* @param ops Block device operations.
* @param dev_data Opaque data passed to operation callbacks.
* @return Pointer to the device entry, or NULL on failure.
*/
devicefs_device_t *devicefs_register_block(const char *class_name,
devicefs_block_ops_t *ops,
void *dev_data);
/**
* Register a new character device.
*
* @param class_name Device class name (e.g., "tty", "serial").
* @param ops Character device operations.
* @param dev_data Opaque data passed to operation callbacks.
* @return Pointer to the device entry, or NULL on failure.
*/
devicefs_device_t *devicefs_register_char(const char *class_name,
devicefs_char_ops_t *ops,
void *dev_data);
/**
* Find a device by its full name (e.g., "hdd1").
*
* @param name Device name.
* @return Pointer to the device entry, or NULL if not found.
*/
devicefs_device_t *devicefs_find(const char *name);
/**
* Get the next device number for a given class.
* This is called internally but may be useful for drivers.
*
* @param class_name Device class name.
* @return Next sequential number (starting from 1).
*/
uint32_t devicefs_next_number(const char *class_name);
#endif /* DEVICEFS_H */

View File

@@ -69,36 +69,65 @@ void kernel_main(uint32_t magic, uint32_t addr) {
/* Initialize serial port first so all debug output goes to COM1 too */
serial_init();
/* Early canary: write directly to VGA text buffer at 0xB8000.
* If the display is in text mode, this will show a bright magenta 'C'
* in the top-left corner before any subsystem is initialized. */
{
volatile uint16_t *vga = (volatile uint16_t *)0xB8000;
vga[0] = (uint16_t)'C' | (0x5F << 8); /* magenta on magenta = visible block */
vga[1] = (uint16_t)'O' | (0x5F << 8);
vga[2] = (uint16_t)'S' | (0x5F << 8);
}
/* Early VGA: write directly to the text buffer at 0xB8000 for boot
* progress that is visible even without the VGA driver initialized.
* Attribute 0x1F = white on blue. */
volatile uint16_t *early_vga = (volatile uint16_t *)0xB8000;
int early_pos = 0;
/* Helper macro: write a short string to early VGA */
#define EARLY_PRINT(s) do { \
const char *_p = (s); \
while (*_p) { \
early_vga[early_pos++] = (uint16_t)(unsigned char)*_p | (0x1F << 8); \
_p++; \
} \
} while(0)
/* Clear screen to blue */
for (int i = 0; i < 80 * 25; i++)
early_vga[i] = (uint16_t)' ' | (0x1F << 8);
EARLY_PRINT("ClaudeOS early boot");
early_pos = 80; /* Move to line 2 */
if (magic != MULTIBOOT2_BOOTLOADER_MAGIC) {
EARLY_PRINT("ERROR: Bad magic=0x");
/* Print magic in hex */
const char *hex = "0123456789ABCDEF";
for (int i = 28; i >= 0; i -= 4)
early_vga[early_pos++] = (uint16_t)(unsigned char)hex[(magic >> i) & 0xF] | (0x4F << 8);
early_pos = 80 * 3;
EARLY_PRINT("Expected 0x36D76289 (Multiboot2)");
early_pos = 80 * 4;
EARLY_PRINT("Got MB1 magic? Checking grub.cfg...");
offset_print("Invalid magic number: ");
print_hex(magic);
return;
/* Hang with interrupts disabled so user can see the message */
for (;;) __asm__ volatile("hlt");
}
EARLY_PRINT("Magic OK ");
offset_print("Booting...\n");
init_gdt();
EARLY_PRINT("GDT ");
offset_print("GDT initialized\n");
init_idt();
EARLY_PRINT("IDT ");
offset_print("IDT initialized\n");
init_pic();
/* Unmask timer IRQ (IRQ0) explicitly */
pic_clear_mask(0);
EARLY_PRINT("PIC ");
offset_print("PIC initialized\n");
init_pmm(addr);
EARLY_PRINT("PMM ");
offset_print("PMM initialized\n");
/* Scan Multiboot2 tags for the initrd module and framebuffer info */
@@ -152,6 +181,7 @@ void kernel_main(uint32_t magic, uint32_t addr) {
}
init_paging();
EARLY_PRINT("PAGING ");
offset_print("Paging initialized\n");
/* If GRUB provided a graphical framebuffer, identity-map it so
@@ -182,21 +212,25 @@ void kernel_main(uint32_t magic, uint32_t addr) {
}
init_kmalloc();
EARLY_PRINT("HEAP ");
offset_print("Memory allocator initialized\n");
/* Initialize CPIO ramdisk if module was loaded */
if (initrd_start != 0) {
cpio_init((const void *)initrd_start, initrd_end - initrd_start);
EARLY_PRINT("CPIO ");
offset_print("CPIO ramdisk initialized\n");
} else {
offset_print("No initrd module found\n");
}
init_vfs();
EARLY_PRINT("VFS ");
offset_print("VFS initialized\n");
if (initrd_start != 0) {
init_initrd_fs();
EARLY_PRINT("INITRD ");
offset_print("Initrd filesystem mounted\n");
/* Test VFS: read a file from the initrd */
@@ -214,6 +248,7 @@ void kernel_main(uint32_t magic, uint32_t addr) {
}
init_tss();
EARLY_PRINT("TSS ");
offset_print("TSS initialized\n");
init_syscalls();
@@ -228,6 +263,9 @@ void kernel_main(uint32_t magic, uint32_t addr) {
init_drivers();
offset_print("Drivers initialized\n");
/* At this point the VGA driver has been initialized and taken over
* the display. The early VGA text is no longer visible. */
/* Show memory statistics and boot progress on VGA */
vga_show_mem_stats();
vga_puts("Boot complete.\n\n");