refactor(kernel): rewrite Stage 1 kernel in C++

- Rename kernel.c -> kernel.cpp and uart.c -> uart.cpp
- Add universalisos::uart namespace with constexpr register definitions
- Use extern "C" linkage for kernel_main() called from boot.S
- Compile with arm-none-eabi-g++ using C++17 freestanding flags
  (-fno-exceptions, -fno-rtti, -fno-threadsafe-statics, -fno-use-cxa-atexit)
- Update Makefile to use g++ and .cpp build rules
This commit is contained in:
Fábio Coutada 2026-07-06 22:53:38 +01:00
parent 6a33d926f9
commit bafa872415
9 changed files with 120 additions and 69 deletions

View file

@ -564,10 +564,10 @@ class AurelioAgentSchema:
### Stage 1: Bare-Metal Hypervisor Skeleton ✅ Implemented ### Stage 1: Bare-Metal Hypervisor Skeleton ✅ Implemented
**Deliverables**: **Deliverables**:
- Bootable bare-metal kernel for QEMU ARM virt - Bootable bare-metal kernel for QEMU ARM virt, written in **C++**
- Assembly startup with stack/BSS setup - Assembly startup with stack/BSS setup
- PL011 UART driver for serial output - PL011 UART driver for serial output
- Build system using `arm-none-eabi-gcc` - Build system using `arm-none-eabi-g++`
**Code Components**: **Code Components**:
| File | Purpose | | File | Purpose |

View file

@ -55,7 +55,8 @@ See [BUILD_ENVIRONMENT.md](BUILD_ENVIRONMENT.md) for detailed setup instructions
## Hypervisor Kernel (C) ## Hypervisor Kernel (C)
The `kernel/` directory contains the actual Universalisos type-1 hypervisor The `kernel/` directory contains the actual Universalisos type-1 hypervisor
implementation. Stage 1 is a bootable bare-metal skeleton for QEMU ARM virt: implementation, written in **C++**. Stage 1 is a bootable bare-metal skeleton
for QEMU ARM virt:
```bash ```bash
cd kernel cd kernel

View file

@ -1,18 +1,23 @@
# Universalisos kernel build for QEMU ARM virt # Universalisos kernel build for QEMU ARM virt (C++)
CROSS_COMPILE ?= arm-none-eabi- CROSS_COMPILE ?= arm-none-eabi-
CC := $(CROSS_COMPILE)gcc CXX := $(CROSS_COMPILE)g++
LD := $(CROSS_COMPILE)ld LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump OBJDUMP := $(CROSS_COMPILE)objdump
CFLAGS := \ CXXFLAGS := \
-mcpu=cortex-a15 \ -mcpu=cortex-a15 \
-marm \ -marm \
-O2 \ -O2 \
-std=c++17 \
-ffreestanding \ -ffreestanding \
-nostdlib \ -nostdlib \
-nostartfiles \ -nostartfiles \
-fno-exceptions \
-fno-rtti \
-fno-threadsafe-statics \
-fno-use-cxa-atexit \
-Wall \ -Wall \
-Wextra \ -Wextra \
-Werror \ -Werror \
@ -31,16 +36,16 @@ OBJS := \
all: kernel.bin kernel.elf all: kernel.bin kernel.elf
kernel.elf: $(OBJS) arch/arm/linker.ld kernel.elf: $(OBJS) arch/arm/linker.ld
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJS)
kernel.bin: kernel.elf kernel.bin: kernel.elf
$(OBJCOPY) -O binary $< $@ $(OBJCOPY) -O binary $< $@
%.o: %.S %.o: %.S
$(CC) $(CFLAGS) -c -o $@ $< $(CXX) $(CXXFLAGS) -c -o $@ $<
%.o: %.c %.o: %.cpp
$(CC) $(CFLAGS) -c -o $@ $< $(CXX) $(CXXFLAGS) -c -o $@ $<
dump: kernel.elf dump: kernel.elf
$(OBJDUMP) -d $< $(OBJDUMP) -d $<

View file

@ -1,8 +1,8 @@
# Universalisos Kernel — Stage 1 Bare-Metal Skeleton # Universalisos Kernel — Stage 1 Bare-Metal Skeleton (C++)
This is the first bootable type-1 hypervisor milestone for Universalisos: a 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 minimal bare-metal kernel written in **C++** that runs on QEMU's ARM `virt`
the PL011 UART. machine and prints to the PL011 UART.
## Building ## Building
@ -30,7 +30,7 @@ Expected output:
``` ```
Universalisos type-1 hypervisor booted. Universalisos type-1 hypervisor booted.
Stage 1: bare-metal skeleton running on QEMU ARM virt. Stage 1: bare-metal C++ skeleton running on QEMU ARM virt.
``` ```
Press `Ctrl+A` then `X` to exit QEMU. Press `Ctrl+A` then `X` to exit QEMU.
@ -47,9 +47,9 @@ make dump
|------|---------| |------|---------|
| `arch/arm/boot.S` | Assembly entry point, stack/BSS setup | | `arch/arm/boot.S` | Assembly entry point, stack/BSS setup |
| `arch/arm/linker.ld` | Memory layout for QEMU virt (load at 0x40000000) | | `arch/arm/linker.ld` | Memory layout for QEMU virt (load at 0x40000000) |
| `arch/arm/uart.c` | PL011 UART driver | | `arch/arm/uart.cpp` | PL011 UART driver |
| `arch/arm/uart.h` | UART interface | | `arch/arm/uart.h` | UART interface |
| `kernel.c` | `kernel_main()` entry point | | `kernel.cpp` | `kernel_main()` entry point |
## Design notes ## Design notes

View file

@ -1,32 +0,0 @@
/*
* 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++);
}
}

73
kernel/arch/arm/uart.cpp Normal file
View file

@ -0,0 +1,73 @@
/*
* PL011 UART driver for QEMU ARM virt machine.
*
* The virt machine exposes a PL011 UART at 0x09000000.
*/
#include "uart.h"
namespace universalisos::uart {
namespace {
constexpr unsigned int UART0_BASE = 0x09000000U;
constexpr unsigned int REG_DR = 0x00U; // Data Register
constexpr unsigned int REG_FR = 0x18U; // Flag Register
constexpr unsigned int REG_IBRD = 0x24U; // Integer Baud Rate Divisor
constexpr unsigned int REG_FBRD = 0x28U; // Fractional Baud Rate Divisor
constexpr unsigned int REG_LCR_H = 0x2CU; // Line Control Register
constexpr unsigned int REG_CR = 0x30U; // Control Register
constexpr unsigned int REG_IMSC = 0x38U; // Interrupt Mask Set/Clear
constexpr unsigned int REG_ICR = 0x44U; // Interrupt Clear Register
constexpr unsigned int FR_TXFF = 1U << 5; // Transmit FIFO full
constexpr unsigned int CR_UARTEN = 1U << 0; // UART enable
constexpr unsigned int CR_TXE = 1U << 8; // Transmit enable
constexpr unsigned int CR_RXE = 1U << 9; // Receive enable
constexpr unsigned int LCR_H_WLEN_8 = 3U << 5; // 8 data bits
constexpr unsigned int LCR_H_FEN = 1U << 4; // Enable FIFOs
inline volatile unsigned int &reg(unsigned int offset)
{
return *reinterpret_cast<volatile unsigned int *>(UART0_BASE + offset);
}
} // namespace
void init()
{
// Disable UART while configuring.
reg(REG_CR) = 0U;
// Clear pending interrupts.
reg(REG_ICR) = 0x7FFU;
// 8 data bits, no parity, one stop bit, FIFO enabled.
reg(REG_LCR_H) = LCR_H_WLEN_8 | LCR_H_FEN;
// Baud rate divisor for 115200 with a 24 MHz UARTCLK.
// QEMU ignores the actual baud value, but a valid divisor is required.
reg(REG_IBRD) = 13U;
reg(REG_FBRD) = 1U;
// Enable UART, TX and RX.
reg(REG_CR) = CR_UARTEN | CR_TXE | CR_RXE;
}
void putc(char c)
{
while ((reg(REG_FR) & FR_TXFF) != 0U) {
// Wait until the transmit FIFO is not full.
}
reg(REG_DR) = static_cast<unsigned int>(c);
}
void puts(const char *s)
{
while (*s != '\0') {
putc(*s++);
}
}
} // namespace universalisos::uart

View file

@ -1,12 +1,16 @@
/* /*
* PL011 UART driver header for QEMU ARM virt machine. * PL011 UART driver for QEMU ARM virt machine.
*/ */
#ifndef UNIVERSALISOS_UART_H #ifndef UNIVERSALISOS_UART_H
#define UNIVERSALISOS_UART_H #define UNIVERSALISOS_UART_H
void uart_init(void); namespace universalisos::uart {
void uart_putc(char c);
void uart_puts(const char *s); void init();
void putc(char c);
void puts(const char *s);
} // namespace universalisos::uart
#endif /* UNIVERSALISOS_UART_H */ #endif /* UNIVERSALISOS_UART_H */

View file

@ -1,17 +0,0 @@
/*
* 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");
}
}

17
kernel/kernel.cpp Normal file
View file

@ -0,0 +1,17 @@
/*
* Universalisos kernel entry point.
*/
#include "arch/arm/uart.h"
extern "C" void kernel_main(void)
{
universalisos::uart::init();
universalisos::uart::puts("Universalisos type-1 hypervisor booted.\r\n");
universalisos::uart::puts("Stage 1: bare-metal C++ skeleton running on QEMU ARM virt.\r\n");
/* Idle loop. */
for (;;) {
__asm__ __volatile__("wfi");
}
}