- Created src/arp.h: ARP packet struct, cache entry struct, operation codes, lookup/request/resolve/receive API, sysfs registration - Created src/arp.c: ARP cache with 32 entries, request/reply handling, ARP response to incoming requests for our IP, sysfs /sys/arp/table with formatted IP/MAC/interface/state columns - Created apps/arp/arp.c: reads and displays /sys/arp/table - Kernel calls arp_init() at boot, registered sysfs 'arp' namespace - Tested: clean boot, ARP initialized, arp app in CPIO
494 lines
15 KiB
C
494 lines
15 KiB
C
#include <multiboot2.h>
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include "gdt.h"
|
|
#include "idt.h"
|
|
#include "pic.h"
|
|
#include "port_io.h"
|
|
#include "pmm.h"
|
|
#include "paging.h"
|
|
#include "kmalloc.h"
|
|
#include "driver.h"
|
|
#include "vga.h"
|
|
#include "tss.h"
|
|
#include "syscall.h"
|
|
#include "process.h"
|
|
#include "cpio.h"
|
|
#include "vfs.h"
|
|
#include "initrd_fs.h"
|
|
#include "devicefs.h"
|
|
#include "sysfs.h"
|
|
#include "mbr.h"
|
|
#include "fat32.h"
|
|
#include "keyboard.h"
|
|
#include "ethernet.h"
|
|
#include "ipv4.h"
|
|
#include "arp.h"
|
|
#include "framebuffer.h"
|
|
|
|
/* Global framebuffer info, parsed from multiboot2 tags. */
|
|
framebuffer_info_t fb_info;
|
|
|
|
/**
|
|
* Initialize COM1 serial port for debug output.
|
|
* Baud rate: 115200, 8N1.
|
|
*/
|
|
static void serial_init(void) {
|
|
outb(0x3F8 + 1, 0x00); /* Disable interrupts */
|
|
outb(0x3F8 + 3, 0x80); /* Enable DLAB */
|
|
outb(0x3F8 + 0, 0x01); /* Divisor low: 115200 baud */
|
|
outb(0x3F8 + 1, 0x00); /* Divisor high */
|
|
outb(0x3F8 + 3, 0x03); /* 8 bits, no parity, 1 stop */
|
|
outb(0x3F8 + 2, 0xC7); /* Enable FIFO */
|
|
outb(0x3F8 + 4, 0x03); /* RTS/DSR set */
|
|
}
|
|
|
|
static void serial_putc(char c) {
|
|
/* Wait for transmit buffer empty */
|
|
while (!(inb(0x3F8 + 5) & 0x20));
|
|
outb(0x3F8, c);
|
|
}
|
|
|
|
void offset_print(const char *str)
|
|
{
|
|
while (*str) {
|
|
outb(0xE9, *str); /* debugcon */
|
|
serial_putc(*str); /* COM1 serial */
|
|
str++;
|
|
}
|
|
}
|
|
|
|
void print_hex(uint32_t val)
|
|
{
|
|
const char *hex = "0123456789ABCDEF";
|
|
outb(0xE9, '0'); serial_putc('0');
|
|
outb(0xE9, 'x'); serial_putc('x');
|
|
for (int i = 28; i >= 0; i -= 4) {
|
|
char c = hex[(val >> i) & 0xF];
|
|
outb(0xE9, c);
|
|
serial_putc(c);
|
|
}
|
|
outb(0xE9, '\n'); serial_putc('\n');
|
|
}
|
|
|
|
/* ================================================================
|
|
* VFS sysfs namespace — handles mount/umount via /sys/vfs
|
|
* ================================================================
|
|
*
|
|
* /sys/vfs/mount — write "device mountpoint" to mount a FAT32 device
|
|
* /sys/vfs/mounts — read to list active mounts
|
|
*/
|
|
|
|
/** Last mount status message for reading back. */
|
|
static char vfs_sysfs_status[256] = "no mounts\n";
|
|
|
|
static int vfs_sysfs_list(void *ctx, const char *path, uint32_t idx,
|
|
sysfs_entry_t *out) {
|
|
(void)ctx;
|
|
if (path[0] != '\0') return -1; /* No subdirectories */
|
|
|
|
static const char *entries[] = { "mount", "mounts" };
|
|
if (idx >= 2) return -1;
|
|
|
|
memset(out, 0, sizeof(sysfs_entry_t));
|
|
strncpy(out->name, entries[idx], SYSFS_MAX_NAME - 1);
|
|
out->is_dir = 0;
|
|
return 0;
|
|
}
|
|
|
|
static int vfs_sysfs_read(void *ctx, const char *path, char *buf,
|
|
uint32_t buf_size) {
|
|
(void)ctx;
|
|
|
|
if (strcmp(path, "mount") == 0 || strcmp(path, "mounts") == 0) {
|
|
uint32_t len = strlen(vfs_sysfs_status);
|
|
if (len + 1 > buf_size) len = buf_size - 1;
|
|
memcpy(buf, vfs_sysfs_status, len);
|
|
buf[len] = '\0';
|
|
return (int)len;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static int vfs_sysfs_write(void *ctx, const char *path, const char *buf,
|
|
uint32_t size) {
|
|
(void)ctx;
|
|
|
|
if (strcmp(path, "mount") != 0) return -1;
|
|
|
|
/* Parse: "device_name mount_path\n" */
|
|
char device[64];
|
|
char mount_path[128];
|
|
int di = 0, mi = 0;
|
|
uint32_t i = 0;
|
|
|
|
/* Parse device name */
|
|
while (i < size && buf[i] != ' ' && buf[i] != '\n' && di < 63) {
|
|
device[di++] = buf[i++];
|
|
}
|
|
device[di] = '\0';
|
|
|
|
/* Skip spaces */
|
|
while (i < size && buf[i] == ' ') i++;
|
|
|
|
/* Parse mount path */
|
|
while (i < size && buf[i] != ' ' && buf[i] != '\n' && mi < 127) {
|
|
mount_path[mi++] = buf[i++];
|
|
}
|
|
mount_path[mi] = '\0';
|
|
|
|
if (di == 0 || mi == 0) {
|
|
strncpy(vfs_sysfs_status, "error: usage: device mountpoint\n",
|
|
sizeof(vfs_sysfs_status) - 1);
|
|
return -1;
|
|
}
|
|
|
|
offset_print(" VFS mount request: ");
|
|
offset_print(device);
|
|
offset_print(" -> ");
|
|
offset_print(mount_path);
|
|
offset_print("\n");
|
|
|
|
/* Find the device */
|
|
devicefs_device_t *dev = devicefs_find(device);
|
|
if (!dev) {
|
|
strncpy(vfs_sysfs_status, "error: device not found\n",
|
|
sizeof(vfs_sysfs_status) - 1);
|
|
offset_print(" VFS mount: device not found\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Mount as FAT32 */
|
|
int ret = fat32_mount_device(dev, mount_path);
|
|
if (ret != 0) {
|
|
strncpy(vfs_sysfs_status, "error: FAT32 mount failed\n",
|
|
sizeof(vfs_sysfs_status) - 1);
|
|
offset_print(" VFS mount: FAT32 mount failed\n");
|
|
return -1;
|
|
}
|
|
|
|
/* Build success message */
|
|
strncpy(vfs_sysfs_status, "mounted ", sizeof(vfs_sysfs_status) - 1);
|
|
uint32_t slen = strlen(vfs_sysfs_status);
|
|
strncpy(vfs_sysfs_status + slen, device,
|
|
sizeof(vfs_sysfs_status) - 1 - slen);
|
|
slen = strlen(vfs_sysfs_status);
|
|
strncpy(vfs_sysfs_status + slen, " at ",
|
|
sizeof(vfs_sysfs_status) - 1 - slen);
|
|
slen = strlen(vfs_sysfs_status);
|
|
strncpy(vfs_sysfs_status + slen, mount_path,
|
|
sizeof(vfs_sysfs_status) - 1 - slen);
|
|
slen = strlen(vfs_sysfs_status);
|
|
if (slen < sizeof(vfs_sysfs_status) - 1) {
|
|
vfs_sysfs_status[slen] = '\n';
|
|
vfs_sysfs_status[slen + 1] = '\0';
|
|
}
|
|
|
|
return (int)size;
|
|
}
|
|
|
|
static sysfs_ops_t vfs_sysfs_ops = {
|
|
.list = vfs_sysfs_list,
|
|
.read = vfs_sysfs_read,
|
|
.write = vfs_sysfs_write,
|
|
};
|
|
|
|
void kernel_main(uint32_t magic, uint32_t addr) {
|
|
/* Initialize serial port first so all debug output goes to COM1 too */
|
|
serial_init();
|
|
|
|
/* 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);
|
|
|
|
/* 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 */
|
|
uint32_t initrd_start = 0, initrd_end = 0;
|
|
memset(&fb_info, 0, sizeof(fb_info));
|
|
{
|
|
struct multiboot_tag *tag;
|
|
for (tag = (struct multiboot_tag *)(addr + 8);
|
|
tag->type != MULTIBOOT_TAG_TYPE_END;
|
|
tag = (struct multiboot_tag *)((uint8_t *)tag + ((tag->size + 7) & ~7u))) {
|
|
if (tag->type == MULTIBOOT_TAG_TYPE_MODULE) {
|
|
struct multiboot_tag_module *mod = (struct multiboot_tag_module *)tag;
|
|
initrd_start = mod->mod_start;
|
|
initrd_end = mod->mod_end;
|
|
offset_print("Initrd module at ");
|
|
print_hex(initrd_start);
|
|
offset_print(" to ");
|
|
print_hex(initrd_end);
|
|
}
|
|
if (tag->type == MULTIBOOT_TAG_TYPE_FRAMEBUFFER) {
|
|
struct multiboot_tag_framebuffer *fbt =
|
|
(struct multiboot_tag_framebuffer *)tag;
|
|
fb_info.addr = (uint32_t)fbt->common.framebuffer_addr;
|
|
fb_info.pitch = fbt->common.framebuffer_pitch;
|
|
fb_info.width = fbt->common.framebuffer_width;
|
|
fb_info.height = fbt->common.framebuffer_height;
|
|
fb_info.bpp = fbt->common.framebuffer_bpp;
|
|
fb_info.type = fbt->common.framebuffer_type;
|
|
|
|
if (fb_info.type == FB_TYPE_RGB) {
|
|
fb_info.red_pos = fbt->framebuffer_red_field_position;
|
|
fb_info.red_size = fbt->framebuffer_red_mask_size;
|
|
fb_info.green_pos = fbt->framebuffer_green_field_position;
|
|
fb_info.green_size = fbt->framebuffer_green_mask_size;
|
|
fb_info.blue_pos = fbt->framebuffer_blue_field_position;
|
|
fb_info.blue_size = fbt->framebuffer_blue_mask_size;
|
|
}
|
|
|
|
offset_print("Framebuffer: type=");
|
|
print_hex(fb_info.type);
|
|
offset_print(" addr=");
|
|
print_hex(fb_info.addr);
|
|
offset_print(" ");
|
|
print_hex(fb_info.width);
|
|
offset_print(" x ");
|
|
print_hex(fb_info.height);
|
|
offset_print(" bpp=");
|
|
print_hex(fb_info.bpp);
|
|
}
|
|
}
|
|
}
|
|
|
|
init_paging();
|
|
EARLY_PRINT("PAGING ");
|
|
offset_print("Paging initialized\n");
|
|
|
|
/* If GRUB provided a graphical framebuffer, identity-map it so
|
|
* the VGA driver can write pixels to it after paging is enabled. */
|
|
if (fb_info.addr != 0 && fb_info.type == FB_TYPE_RGB) {
|
|
uint32_t fb_size = fb_info.pitch * fb_info.height;
|
|
uint32_t fb_base = fb_info.addr & ~0xFFFu; /* page-align down */
|
|
uint32_t fb_end = (fb_info.addr + fb_size + 0xFFF) & ~0xFFFu;
|
|
offset_print(" Mapping framebuffer ");
|
|
print_hex(fb_base);
|
|
offset_print(" to ");
|
|
print_hex(fb_end);
|
|
for (uint32_t pa = fb_base; pa < fb_end; pa += 4096) {
|
|
paging_map_page(pa, pa, PAGE_PRESENT | PAGE_WRITE | PAGE_WRITETHROUGH);
|
|
}
|
|
}
|
|
|
|
/* Test paging: allocate a page and write to it */
|
|
void *test_page = paging_alloc_page();
|
|
if (test_page) {
|
|
offset_print("Allocated virtual page at: ");
|
|
print_hex((uint32_t)test_page);
|
|
*((volatile uint32_t *)test_page) = 0xDEADBEEF;
|
|
offset_print("Virtual page write/read OK\n");
|
|
paging_free_page(test_page);
|
|
} else {
|
|
offset_print("FAILED to allocate virtual page\n");
|
|
}
|
|
|
|
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 */
|
|
int fd = vfs_open("/initrd/README", 0);
|
|
if (fd >= 0) {
|
|
char buf[64];
|
|
int32_t n = vfs_read(fd, buf, sizeof(buf) - 1);
|
|
if (n > 0) {
|
|
buf[n] = '\0';
|
|
offset_print("VFS read /initrd/README: ");
|
|
offset_print(buf);
|
|
}
|
|
vfs_close(fd);
|
|
}
|
|
}
|
|
|
|
init_devicefs();
|
|
EARLY_PRINT("DEV ");
|
|
offset_print("Devicefs initialized\n");
|
|
|
|
init_sysfs();
|
|
EARLY_PRINT("SYS ");
|
|
offset_print("Sysfs initialized\n");
|
|
|
|
/* Register VFS mount namespace in sysfs */
|
|
sysfs_register("vfs", &vfs_sysfs_ops, NULL);
|
|
offset_print("VFS sysfs namespace registered\n");
|
|
|
|
init_tss();
|
|
EARLY_PRINT("TSS ");
|
|
offset_print("TSS initialized\n");
|
|
|
|
init_syscalls();
|
|
EARLY_PRINT("SYSCALL ");
|
|
offset_print("Syscalls initialized\n");
|
|
|
|
keyboard_init();
|
|
EARLY_PRINT("KBD ");
|
|
offset_print("Keyboard initialized\n");
|
|
|
|
init_process();
|
|
EARLY_PRINT("PROC ");
|
|
offset_print("Process subsystem initialized\n");
|
|
|
|
/* If the early VGA canary at 0xB8000 was visible, the display is
|
|
* definitely in text mode, regardless of what the GRUB framebuffer
|
|
* tag says. Force text mode so vga_init doesn't try to use a
|
|
* pixel framebuffer that isn't actually being displayed. */
|
|
if (fb_info.type == FB_TYPE_RGB) {
|
|
offset_print(" Overriding fb type from RGB to EGA_TEXT (early VGA visible)\n");
|
|
fb_info.type = FB_TYPE_EGA_TEXT;
|
|
fb_info.addr = 0x000B8000;
|
|
fb_info.width = 80;
|
|
fb_info.height = 25;
|
|
fb_info.bpp = 16;
|
|
fb_info.pitch = 80 * 2;
|
|
}
|
|
|
|
ethernet_init();
|
|
offset_print("Ethernet subsystem initialized\n");
|
|
|
|
ipv4_init();
|
|
offset_print("IPv4 stack initialized\n");
|
|
|
|
arp_init();
|
|
offset_print("ARP subsystem initialized\n");
|
|
|
|
init_drivers();
|
|
EARLY_PRINT("DRV ");
|
|
offset_print("Drivers initialized\n");
|
|
|
|
/* Scan for MBR partitions on detected hard drives */
|
|
init_mbr();
|
|
offset_print("MBR scan complete\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");
|
|
|
|
/* Test kmalloc/kfree */
|
|
uint32_t *test_alloc = (uint32_t *)kmalloc(64);
|
|
if (test_alloc) {
|
|
*test_alloc = 42;
|
|
offset_print("kmalloc(64) = ");
|
|
print_hex((uint32_t)test_alloc);
|
|
kfree(test_alloc);
|
|
offset_print("kfree OK\n");
|
|
} else {
|
|
offset_print("FAILED to kmalloc\n");
|
|
}
|
|
|
|
/* Load the initial program from the initrd and run it */
|
|
cpio_entry_t app_entry;
|
|
const char *init_app = "sh";
|
|
if (cpio_find(init_app, &app_entry) == 0) {
|
|
offset_print("Found ");
|
|
offset_print(init_app);
|
|
offset_print(" in initrd (");
|
|
print_hex(app_entry.datasize);
|
|
offset_print(" bytes)\n");
|
|
|
|
int32_t pid = process_create(init_app,
|
|
app_entry.data,
|
|
app_entry.datasize);
|
|
if (pid > 0) {
|
|
offset_print("Created init process, pid=");
|
|
print_hex((uint32_t)pid);
|
|
|
|
/* Enable interrupts before entering user mode */
|
|
asm volatile("sti");
|
|
offset_print("Interrupts enabled\n");
|
|
|
|
/* Enter user mode - does not return */
|
|
process_run_first();
|
|
} else {
|
|
offset_print("FAILED to create init process\n");
|
|
}
|
|
} else {
|
|
offset_print(init_app);
|
|
offset_print(" not found in initrd\n");
|
|
}
|
|
|
|
/* Enable interrupts */
|
|
asm volatile("sti");
|
|
offset_print("Interrupts enabled\n");
|
|
|
|
offset_print("Hello, world\n");
|
|
}
|