From 173b2c33307ae984986d9a6f366c8839fb4d948e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Coutada?= Date: Mon, 6 Jul 2026 22:51:56 +0100 Subject: [PATCH] feat(kernel): add Stage 1 bare-metal hypervisor skeleton for QEMU ARM virt - Add kernel/arch/arm/boot.S with ARMv7 assembly entry, stack setup, BSS clear - Add kernel/arch/arm/linker.ld for QEMU virt memory layout (load at 0x40000000) - Add kernel/arch/arm/uart.c/uart.h for PL011 serial output - Add kernel/kernel.c with kernel_main() idle loop - Add kernel/Makefile for arm-none-eabi-gcc cross-compile and QEMU launch - Add kernel/README.md with build/run instructions - Ignore kernel build artifacts in .gitignore --- .gitignore | 6 ++++ kernel/Makefile | 52 +++++++++++++++++++++++++++++++++++ kernel/README.md | 58 +++++++++++++++++++++++++++++++++++++++ kernel/arch/arm/boot.S | 34 +++++++++++++++++++++++ kernel/arch/arm/linker.ld | 39 ++++++++++++++++++++++++++ kernel/arch/arm/uart.c | 32 +++++++++++++++++++++ kernel/arch/arm/uart.h | 12 ++++++++ kernel/kernel.c | 17 ++++++++++++ 8 files changed, 250 insertions(+) create mode 100644 kernel/Makefile create mode 100644 kernel/README.md create mode 100644 kernel/arch/arm/boot.S create mode 100644 kernel/arch/arm/linker.ld create mode 100644 kernel/arch/arm/uart.c create mode 100644 kernel/arch/arm/uart.h create mode 100644 kernel/kernel.c diff --git a/.gitignore b/.gitignore index df4a3a812..08ad794d8 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,12 @@ test/results/ test/coverage/ *.log +# Universalisos kernel build artifacts +kernel/*.o +kernel/*.elf +kernel/*.bin +kernel/arch/arm/*.o + # Temporary files *.tmp *.temp diff --git a/kernel/Makefile b/kernel/Makefile new file mode 100644 index 000000000..551ec958c --- /dev/null +++ b/kernel/Makefile @@ -0,0 +1,52 @@ +# Universalisos kernel build for QEMU ARM virt + +CROSS_COMPILE ?= arm-none-eabi- +CC := $(CROSS_COMPILE)gcc +LD := $(CROSS_COMPILE)ld +OBJCOPY := $(CROSS_COMPILE)objcopy +OBJDUMP := $(CROSS_COMPILE)objdump + +CFLAGS := \ + -mcpu=cortex-a15 \ + -marm \ + -O2 \ + -ffreestanding \ + -nostdlib \ + -nostartfiles \ + -Wall \ + -Wextra \ + -Werror \ + -Iinclude \ + -g + +LDFLAGS := -T arch/arm/linker.ld + +OBJS := \ + arch/arm/boot.o \ + arch/arm/uart.o \ + kernel.o + +.PHONY: all clean run dump + +all: kernel.bin kernel.elf + +kernel.elf: $(OBJS) arch/arm/linker.ld + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) + +kernel.bin: kernel.elf + $(OBJCOPY) -O binary $< $@ + +%.o: %.S + $(CC) $(CFLAGS) -c -o $@ $< + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +dump: kernel.elf + $(OBJDUMP) -d $< + +run: kernel.elf + qemu-system-arm -M virt -cpu cortex-a15 -nographic -kernel kernel.elf + +clean: + rm -f $(OBJS) kernel.elf kernel.bin diff --git a/kernel/README.md b/kernel/README.md new file mode 100644 index 000000000..573f2dd98 --- /dev/null +++ b/kernel/README.md @@ -0,0 +1,58 @@ +# Universalisos Kernel — Stage 1 Bare-Metal Skeleton + +This is the first bootable type-1 hypervisor milestone for Universalisos: a +minimal bare-metal kernel that runs on QEMU's ARM `virt` machine and prints to +the PL011 UART. + +## Building + +Requires `arm-none-eabi-gcc` and `qemu-system-arm`. + +```bash +cd kernel +make +``` + +Outputs: +- `kernel.elf` — ELF image for debugging +- `kernel.bin` — raw binary for QEMU `-kernel` + +## Running in QEMU + +```bash +make run +``` + +This runs `qemu-system-arm` with the ELF image so that QEMU loads the kernel +at the address specified in the linker script. + +Expected output: + +``` +Universalisos type-1 hypervisor booted. +Stage 1: bare-metal skeleton running on QEMU ARM virt. +``` + +Press `Ctrl+A` then `X` to exit QEMU. + +## Inspecting the binary + +```bash +make dump +``` + +## Layout + +| File | Purpose | +|------|---------| +| `arch/arm/boot.S` | Assembly entry point, stack/BSS setup | +| `arch/arm/linker.ld` | Memory layout for QEMU virt (load at 0x40000000) | +| `arch/arm/uart.c` | PL011 UART driver | +| `arch/arm/uart.h` | UART interface | +| `kernel.c` | `kernel_main()` entry point | + +## Design notes + +- The kernel is loaded by QEMU at RAM base `0x40000000`. +- A 64 KiB stack is reserved immediately after the BSS section. +- Interrupts are disabled in the boot stub; the kernel idles with `wfi`. diff --git a/kernel/arch/arm/boot.S b/kernel/arch/arm/boot.S new file mode 100644 index 000000000..40d8e51ee --- /dev/null +++ b/kernel/arch/arm/boot.S @@ -0,0 +1,34 @@ +/* + * Universalisos ARMv7 boot code for QEMU virt machine. + * + * This is the first code executed after QEMU loads the kernel image. + * It sets up the stack, zeroes the BSS, and jumps to kernel_main(). + */ + +.syntax unified +.arch armv7-a + +.section .text.boot +.global _start +_start: + /* Disable interrupts until the kernel is ready. */ + cpsid if + + /* Set up the stack at the top of the reserved boot region. */ + ldr sp, =_stack_top + + /* Clear the BSS section. */ + ldr r0, =_bss_start + ldr r1, =_bss_end + mov r2, #0 +1: + cmp r0, r1 + bge 2f + str r2, [r0], #4 + b 1b +2: + /* Enter the C kernel. */ + bl kernel_main + + /* Halt if kernel_main ever returns. */ + b . diff --git a/kernel/arch/arm/linker.ld b/kernel/arch/arm/linker.ld new file mode 100644 index 000000000..8d05dbbfa --- /dev/null +++ b/kernel/arch/arm/linker.ld @@ -0,0 +1,39 @@ +/* + * Universalisos linker script for QEMU ARM virt machine. + * + * QEMU loads the raw kernel image at the start of RAM (0x40000000) + * when using -kernel. + */ + +ENTRY(_start) + +SECTIONS +{ + . = 0x40000000; + + .text : { + *(.text.boot) + *(.text .text.*) + } + + .rodata : { + *(.rodata .rodata.*) + } + + .data : { + *(.data .data.*) + } + + .bss : { + _bss_start = .; + *(.bss .bss.*) + *(COMMON) + _bss_end = .; + } + + /* 64 KiB boot stack placed immediately after BSS, 8-byte aligned. */ + _stack_bottom = .; + . = ALIGN(8); + . = . + 0x10000; + _stack_top = ALIGN(8); +} diff --git a/kernel/arch/arm/uart.c b/kernel/arch/arm/uart.c new file mode 100644 index 000000000..c043b0ed9 --- /dev/null +++ b/kernel/arch/arm/uart.c @@ -0,0 +1,32 @@ +/* + * PL011 UART driver for QEMU ARM virt machine. + * + * The virt machine exposes a PL011 UART at 0x09000000. + */ + +#include "uart.h" + +#define UART0_BASE 0x09000000U +#define UART_DR (*(volatile unsigned int *)(UART0_BASE + 0x00U)) +#define UART_FR (*(volatile unsigned int *)(UART0_BASE + 0x18U)) +#define UART_FR_TXFF (1U << 5) + +void uart_init(void) +{ + /* QEMU initializes the UART for us. */ +} + +void uart_putc(char c) +{ + while ((UART_FR & UART_FR_TXFF) != 0U) { + /* Wait until the transmit FIFO is not full. */ + } + UART_DR = (unsigned int)c; +} + +void uart_puts(const char *s) +{ + while (*s != '\0') { + uart_putc(*s++); + } +} diff --git a/kernel/arch/arm/uart.h b/kernel/arch/arm/uart.h new file mode 100644 index 000000000..002696350 --- /dev/null +++ b/kernel/arch/arm/uart.h @@ -0,0 +1,12 @@ +/* + * PL011 UART driver header for QEMU ARM virt machine. + */ + +#ifndef UNIVERSALISOS_UART_H +#define UNIVERSALISOS_UART_H + +void uart_init(void); +void uart_putc(char c); +void uart_puts(const char *s); + +#endif /* UNIVERSALISOS_UART_H */ diff --git a/kernel/kernel.c b/kernel/kernel.c new file mode 100644 index 000000000..db484fea8 --- /dev/null +++ b/kernel/kernel.c @@ -0,0 +1,17 @@ +/* + * Universalisos kernel entry point. + */ + +#include "arch/arm/uart.h" + +void kernel_main(void) +{ + uart_init(); + uart_puts("Universalisos type-1 hypervisor booted.\r\n"); + uart_puts("Stage 1: bare-metal skeleton running on QEMU ARM virt.\r\n"); + + /* Idle loop. */ + for (;;) { + __asm__ __volatile__("wfi"); + } +}