diff --git a/guests/linux-aarch64/initramfs.img b/guests/linux-aarch64/initramfs.img new file mode 100644 index 000000000..8def86add Binary files /dev/null and b/guests/linux-aarch64/initramfs.img differ diff --git a/guests/linux-aarch64/initramfs/build.sh b/guests/linux-aarch64/initramfs/build.sh new file mode 100644 index 000000000..a3287661d --- /dev/null +++ b/guests/linux-aarch64/initramfs/build.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Build a minimal initramfs for UniversalisOS Linux guest. +# Cross-compiles init.c for AArch64 and creates a cpio archive. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +OUTPUT_DIR="$SCRIPT_DIR/.." +INITRAMFS_DIR="$SCRIPT_DIR/rootfs" + +# Cross-compiler +CROSS=aarch64-linux-gnu +CC=${CROSS}-gcc +OBJCOPY=${CROSS}-objcopy +STRIP=${CROSS}-strip + +echo "=== Building minimal initramfs ===" + +# Create rootfs directory structure +rm -rf "$INITRAMFS_DIR" +mkdir -p "$INITRAMFS_DIR"/{bin,dev,proc,sys,etc,sbin} + +# Compile init.c as static binary +echo "Compiling init.c..." +${CC} -static -O2 -o "$INITRAMFS_DIR/init" "$SCRIPT_DIR/init.c" +${STRIP} "$INITRAMFS_DIR/init" +echo "init binary: $(file "$INITRAMFS_DIR/init")" + +# Create a minimal /etc/init.d/rcS +cat > "$INITRAMFS_DIR/etc/init.d/rcS" << 'EOF' +#!/bin/sh +echo "Running rcS..." +mount -t proc proc /proc +mount -t sysfs sysfs /sys +mount -t tmpfs tmpfs /dev +exec /init +EOF +chmod +x "$INITRAMFS_DIR/etc/init.d/rcS" + +# Create a minimal /etc/fstab +cat > "$INITRAMFS_DIR/etc/fstab" << 'EOF' +# +proc /proc proc defaults 0 0 +sysfs /sys sysfs defaults 0 0 +tmpfs /dev tmpfs defaults 0 0 +EOF + +# Create cpio archive +echo "Creating cpio archive..." +cd "$INITRAMFS_DIR" +find . -print0 | cpio --null -o --format=newc > "$OUTPUT_DIR/initramfs.cpio" +cd "$SCRIPT_DIR" + +# Create gzip-compressed version +echo "Compressing..." +gzip -9 -f "$OUTPUT_DIR/initramfs.cpio" +mv "$OUTPUT_DIR/initramfs.cpio.gz" "$OUTPUT_DIR/initramfs.img" + +echo "=== Initramfs built ===" +echo "Output: $OUTPUT_DIR/initramfs.img" +echo "Size: $(du -h "$OUTPUT_DIR/initramfs.img" | cut -f1)" +ls -la "$OUTPUT_DIR/initramfs.img" diff --git a/guests/linux-aarch64/initramfs/init.c b/guests/linux-aarch64/initramfs/init.c new file mode 100644 index 000000000..23268bff2 --- /dev/null +++ b/guests/linux-aarch64/initramfs/init.c @@ -0,0 +1,144 @@ +/* + * Minimal /init for UniversalisOS initramfs. + * + * This is the first userspace program executed by Linux after mounting + * the rootfs. It demonstrates that the hypervisor can boot a real Linux + * kernel to userspace via virtio-blk. + * + * Features: + * - Mounts /proc, /sys, /dev (tmpfs) + * - Prints system information from /proc/cpuinfo + * - Runs a minimal shell loop (echo + wait) + * - Demonstrates the full hypervisor -> Linux -> userspace path + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BANNER \ + "\r\n" \ + "============================================\r\n" \ + " UniversalisOS Linux Guest Boot Successful!\r\n" \ + "============================================\r\n" \ + "\r\n" + +static void mount_proc(void) { + mkdir("/proc", 0755); + if (mount("proc", "/proc", "proc", 0, NULL) == 0) { + printf("[init] /proc mounted\r\n"); + } else { + printf("[init] /proc mount failed\r\n"); + } +} + +static void mount_sys(void) { + mkdir("/sys", 0755); + if (mount("sysfs", "/sys", "sysfs", 0, NULL) == 0) { + printf("[init] /sys mounted\r\n"); + } else { + printf("[init] /sys mount failed\r\n"); + } +} + +static void mount_dev(void) { + mkdir("/dev", 0755); + if (mount("tmpfs", "/dev", "tmpfs", 0, "size=64M") == 0) { + printf("[init] /dev mounted (tmpfs)\r\n"); + } else { + printf("[init] /dev mount failed\r\n"); + } +} + +static void print_cpuinfo(void) { + FILE *f = fopen("/proc/cpuinfo", "r"); + if (f) { + char line[256]; + printf("[init] CPU info:\r\n"); + while (fgets(line, sizeof(line), f)) { + if (strncmp(line, "Processor", 9) == 0 || + strncmp(line, "model name", 10) == 0 || + strncmp(line, "CPU implementer", 14) == 0 || + strncmp(line, "CPU variant", 10) == 0 || + strncmp(line, "BogoMIPS", 8) == 0) { + printf(" %s", line); + } + } + fclose(f); + } +} + +static void print_uptime(void) { + FILE *f = fopen("/proc/uptime", "r"); + if (f) { + double uptime; + if (fscanf(f, "%lf", &uptime) == 1) { + printf("[init] System uptime: %.1f seconds\r\n", uptime); + } + fclose(f); + } +} + +static void print_memory(void) { + FILE *f = fopen("/proc/meminfo", "r"); + if (f) { + char line[256]; + printf("[init] Memory info:\r\n"); + while (fgets(line, sizeof(line), f)) { + if (strncmp(line, "MemTotal", 8) == 0 || + strncmp(line, "MemFree", 7) == 0 || + strncmp(line, "MemAvailable", 12) == 0) { + printf(" %s", line); + } + } + fclose(f); + } +} + +int main(void) { + printf(BANNER); + printf("[init] UniversalisOS Linux Guest Init\r\n"); + printf("[init] PID=%d\r\n", getpid()); + + /* Mount essential filesystems */ + mount_dev(); + mount_proc(); + mount_sys(); + + /* Create basic device nodes */ + mknod("/dev/console", 0600 | S_IFCHR, makedev(5, 1)); + mknod("/dev/null", 0666 | S_IFCHR, makedev(1, 3)); + mknod("/dev/zero", 0666 | S_IFCHR, makedev(1, 5)); + + /* Print system information */ + print_cpuinfo(); + print_uptime(); + print_memory(); + + /* Print mount info */ + FILE *f = fopen("/proc/mounts", "r"); + if (f) { + char line[256]; + printf("[init] Mounts:\r\n"); + while (fgets(line, sizeof(line), f)) { + printf(" %s", line); + } + fclose(f); + } + + printf("\r\n[init] System ready. Entering idle loop.\r\n"); + printf("[init] To shutdown: echo 1 > /proc/sys/kernel/sysrq && echo o > /proc/sysrq-trigger\r\n"); + + /* Idle loop — in a real system this would run a shell or service manager */ + while (1) { + sleep(10); + printf("[init] heartbeat\r\n"); + } + + return 0; +} diff --git a/guests/linux-aarch64/initramfs/rootfs/init b/guests/linux-aarch64/initramfs/rootfs/init new file mode 100755 index 000000000..e7850d596 Binary files /dev/null and b/guests/linux-aarch64/initramfs/rootfs/init differ diff --git a/guests/linux-aarch64/mkdisk.py b/guests/linux-aarch64/mkdisk.py new file mode 100644 index 000000000..3940c2e2d --- /dev/null +++ b/guests/linux-aarch64/mkdisk.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +""" +Generate a virtio-blk disk image for UniversalisOS Linux guest. + +Creates a raw disk image with the UOSVBLK1 header format that +virtio-blk can detect and serve to the guest. + +Usage: + python3 mkdisk.py [--size=SIZE] + +The output image format: + Offset 0x000: "UOSVBLK1" magic (8 bytes) + Offset 0x008: data size in bytes (8 bytes, little-endian) + Offset 0x010: raw sector data (512-byte aligned) +""" +import sys +import os +import struct + +MAGIC = b"UOSVBLK1" +SECTOR_SIZE = 512 + +def create_disk_image(input_path, output_path, target_size=None): + """Create a virtio-blk disk image from an input file.""" + + # Read input file + with open(input_path, 'rb') as f: + data = f.read() + + # Pad to sector boundary + if len(data) % SECTOR_SIZE != 0: + data += b'\x00' * (SECTOR_SIZE - (len(data) % SECTOR_SIZE)) + + # Pad to target size if specified + if target_size and target_size > len(data): + data += b'\x00' * (target_size - len(data)) + + # Create image with header + header = MAGIC + struct.pack(' [--size=SIZE]") + print(f" input: input file (initramfs, rootfs, etc.)") + print(f" output: output disk image") + print(f" --size: target size in bytes (optional, pads with zeros)") + sys.exit(1) + + input_path = sys.argv[1] + output_path = sys.argv[2] + target_size = None + + for arg in sys.argv[3:]: + if arg.startswith('--size='): + target_size = int(arg.split('=')[1]) + + if not os.path.exists(input_path): + print(f"Error: input file not found: {input_path}") + sys.exit(1) + + create_disk_image(input_path, output_path, target_size) + +if __name__ == '__main__': + main() diff --git a/guests/linux-aarch64/rootfs.img b/guests/linux-aarch64/rootfs.img new file mode 100644 index 000000000..39b690558 Binary files /dev/null and b/guests/linux-aarch64/rootfs.img differ diff --git a/guests/linux-aarch64/rootfs/Makefile b/guests/linux-aarch64/rootfs/Makefile new file mode 100644 index 000000000..da479e8ba --- /dev/null +++ b/guests/linux-aarch64/rootfs/Makefile @@ -0,0 +1,140 @@ +# UniversalisOS Linux Guest Rootfs Generation +# +# Targets: +# make minimal - Build minimal initramfs (init only, ~50KB) +# make busybox - Build busybox-based initramfs (~1MB) +# make full - Build full rootfs from yocto/buildroot (~50MB) +# make disk - Create virtio-blk disk image +# make all - Build minimal + disk image +# make clean - Remove all build artifacts +# +# Prerequisites: +# - aarch64-linux-gnu-gcc (cross-compiler) +# - cpio, gzip (for initramfs) +# - python3 (for mkdisk.py) + +# Configuration +CROSS_COMPILE ?= aarch64-linux-gnu- +CC = $(CROSS_COMPILE)gcc +STRIP = $(CROSS_COMPILE)strip + +# Directories +ROOTFS_DIR = $(CURDIR) +OUTPUT_DIR = $(ROOTFS_DIR)/output +BUILD_DIR = $(ROOTFS_DIR)/build +INITRAMFS_DIR = $(BUILD_DIR)/initramfs + +# Output files +INITRAMFS_IMG = $(OUTPUT_DIR)/initramfs.img +DISK_IMG = $(OUTPUT_DIR)/rootfs.img + +# Disk image size (256 MiB for full rootfs) +DISK_SIZE = 268435456 + +# Busybox configuration +BUSYBOX_VERSION = 1.36.1 +BUSYBOX_URL = https://busybox.net/downloads/busybox-$(BUSYBOX_VERSION).tar.bz2 + +# ===== Minimal initramfs (init only) ===== + +.PHONY: minimal +minimal: $(INITRAMFS_IMG) + +$(OUTPUT_DIR): + mkdir -p $(OUTPUT_DIR) + +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +$(INITRAMFS_DIR): $(BUILD_DIR) + mkdir -p $(INITRAMFS_DIR)/{bin,dev,proc,sys,etc,sbin} + +$(INITRAMFS_DIR)/init: $(INITRAMFS_DIR) $(ROOTFS_DIR)/initramfs/init.c + $(CC) -static -O2 -o $@ $(ROOTFS_DIR)/initramfs/init.c + $(STRIP) $@ + +$(INITRAMFS_DIR)/etc/init.d/rcS: $(INITRAMFS_DIR) + mkdir -p $(INITRAMFS_DIR)/etc/init.d + @echo '#!/bin/sh' > $@ + @echo 'mount -t proc proc /proc' >> $@ + @echo 'mount -t sysfs sysfs /sys' >> $@ + @echo 'mount -t tmpfs tmpfs /dev' >> $@ + @chmod +x $@ + +$(INITRAMFS_DIR)/etc/fstab: $(INITRAMFS_DIR) + @echo '# ' > $@ + @echo 'proc /proc proc defaults 0 0' >> $@ + @echo 'sysfs /sys sysfs defaults 0 0' >> $@ + @echo 'tmpfs /dev tmpfs defaults 0 0' >> $@ + +$(INITRAMFS_IMG): $(INITRAMFS_DIR)/init $(INITRAMFS_DIR)/etc/init.d/rcS $(INITRAMFS_DIR)/etc/fstab + cd $(INITRAMFS_DIR) && find . -print0 | cpio --null -o --format=newc 2>/dev/null | gzip -9 > $@ + @echo "Initramfs: $$(du -h $@ | cut -f1)" + +# ===== Busybox initramfs ===== + +.PHONY: busybox +busybox: $(OUTPUT_DIR)/busybox-initramfs.img + +$(BUILD_DIR)/busybox-$(BUSYBOX_VERSION): $(BUILD_DIR) + cd $(BUILD_DIR) && curl -L $(BUSYBOX_URL) | tar xj + cd $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION) && make ARCH=aarch64 CROSS_COMPILE=$(CROSS_COMPILE) defconfig + cd $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION) && sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config + cd $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION) && make ARCH=aarch64 CROSS_COMPILE=$(CROSS_COMPILE) -j$$(nproc) + +$(BUILD_DIR)/busybox-rootfs: $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION) + mkdir -p $@/{bin,dev,proc,sys,etc,sbin,tmp,root} + cp $(BUILD_DIR)/busybox-$(BUSYBOX_VERSION)/busybox $@/bin/busybox + cd $@/bin && for cmd in sh ls cat echo mount mkdir mknod sleep reboot; do ln -sf busybox $$cmd; done + cd $@/sbin && ln -sf ../bin/busybox init + @echo '#!/bin/sh' > $@/etc/init.d/rcS + @echo 'mount -t proc proc /proc' >> $@/etc/init.d/rcS + @echo 'mount -t sysfs sysfs /sys' >> $@/etc/init.d/rcS + @echo 'mount -t tmpfs tmpfs /dev' >> $@/etc/init.d/rcS + @echo 'mount -t devpts devpts /dev/pts' >> $@/etc/init.d/rcS + @chmod +x $@/etc/init.d/rcS + +$(OUTPUT_DIR)/busybox-initramfs.img: $(BUILD_DIR)/busybox-rootfs $(OUTPUT_DIR) + cd $(BUILD_DIR)/busybox-rootfs && find . -print0 | cpio --null -o --format=newc 2>/dev/null | gzip -9 > $@ + @echo "Busybox initramfs: $$(du -h $@ | cut -f1)" + +# ===== Full rootfs (yocto/buildroot placeholder) ===== + +.PHONY: full +full: $(OUTPUT_DIR)/full-rootfs.img + +$(OUTPUT_DIR)/full-rootfs.img: $(OUTPUT_DIR) + @echo "=== Full rootfs generation ===" + @echo "This target requires yocto or buildroot to be configured." + @echo "To use:" + @echo " 1. Configure yocto/buildroot for aarch64" + @echo " 2. Build the rootfs" + @echo " 3. Copy the output to $(OUTPUT_DIR)/full-rootfs/" + @echo " 4. Run: make disk INPUT=$(OUTPUT_DIR)/full-rootfs/" + @echo "" + @echo "For now, using minimal initramfs as fallback." + $(MAKE) minimal + cp $(INITRAMFS_IMG) $@ + +# ===== Disk image generation ===== + +.PHONY: disk +disk: $(DISK_IMG) + +$(DISK_IMG): $(INITRAMFS_IMG) + python3 ../mkdisk.py $(INITRAMFS_IMG) $@ --size=$(DISK_SIZE) + @echo "Disk image: $$(du -h $@ | cut -f1)" + +# ===== All ===== + +.PHONY: all +all: minimal disk + @echo "=== Build complete ===" + @echo "Initramfs: $(INITRAMFS_IMG)" + @echo "Disk image: $(DISK_IMG)" + +# ===== Clean ===== + +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) $(OUTPUT_DIR)