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
This commit is contained in:
Fábio Coutada 2026-07-06 22:51:56 +01:00
parent e6ec3881af
commit 173b2c3330
8 changed files with 250 additions and 0 deletions

6
.gitignore vendored
View file

@ -55,6 +55,12 @@ test/results/
test/coverage/ test/coverage/
*.log *.log
# Universalisos kernel build artifacts
kernel/*.o
kernel/*.elf
kernel/*.bin
kernel/arch/arm/*.o
# Temporary files # Temporary files
*.tmp *.tmp
*.temp *.temp

52
kernel/Makefile Normal file
View file

@ -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

58
kernel/README.md Normal file
View file

@ -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`.

34
kernel/arch/arm/boot.S Normal file
View file

@ -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 .

39
kernel/arch/arm/linker.ld Normal file
View file

@ -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);
}

32
kernel/arch/arm/uart.c Normal file
View file

@ -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++);
}
}

12
kernel/arch/arm/uart.h Normal file
View file

@ -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 */

17
kernel/kernel.c Normal file
View file

@ -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");
}
}