]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
feat: multi-arch ARM64/RISC-V bring-up with QEMU virt boot
authorTulio A M Mendes <[email protected]>
Sun, 15 Feb 2026 03:50:50 +0000 (00:50 -0300)
committerTulio A M Mendes <[email protected]>
Sun, 15 Feb 2026 03:50:50 +0000 (00:50 -0300)
ARM64 (AArch64):
- boot.S: EL2->EL1 transition, FP/SIMD enable (CPACR_EL1.FPEN),
  BSS zeroing, 16KB stack
- PL011 UART at 0x09000000 for serial console
- Linker script at 0x40000000 with proper section alignment
- Stubs for kernel subsystems not yet ported (PMM, VMM, scheduler,
  filesystem, syscalls, etc.)

RISC-V 64:
- boot.S: M-mode CSR init, BSS zeroing, 16KB stack
- NS16550 UART at 0x10000000 for serial console
- Linker script at 0x80000000 with proper section alignment
- Stubs matching ARM64 coverage

Build system:
- Makefile restructured: x86 gets full kernel/drivers/mm wildcards,
  ARM/RISC-V get minimal KERNEL_COMMON set (main, console, utils,
  cmdline, driver, cpu_features) + HAL + arch sources
- BOOT_OBJ now arch-specific (build/ARCH/arch/ARCH/boot.o)
- Added QEMU run targets: make run-arm, make run-riscv
- ARM64: -mno-outline-atomics to avoid libgcc atomic calls

Spinlock portability:
- Added AArch64 irq_save/irq_restore using DAIF register
- Simple volatile-flag spinlock for AArch64/RISC-V single-core
  bring-up (exclusive monitors need cacheable memory / MMU)

Key bug fix:
- AArch64 variadic functions (kprintf etc.) trap without FP/SIMD
  enabled — GCC saves q0-q7 in va_list register save area

Both architectures boot on QEMU virt and reach idle loop:
  make ARCH=arm && make run-arm
  make ARCH=riscv && make run-riscv

x86 unaffected: 35/35 smoke, 16/16 battery, cppcheck clean.

12 files changed:
Makefile
include/spinlock.h
src/arch/arm/arch_early_setup.c
src/arch/arm/boot.S
src/arch/arm/linker.ld
src/arch/arm/stubs.c [new file with mode: 0644]
src/arch/riscv/arch_early_setup.c
src/arch/riscv/boot.S
src/arch/riscv/linker.ld
src/arch/riscv/stubs.c [new file with mode: 0644]
src/hal/arm/usermode.c
src/hal/riscv/usermode.c

index 7b94337d5db695b3e5ce7f269c0be8d5d0695b6c..cbf50a94468ab13cad01583b53c16cf040c29aa9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -8,16 +8,21 @@ KERNEL_NAME := adros-$(ARCH).bin
 SRC_DIR := src
 BUILD_DIR := build/$(ARCH)
 
-# Common sources
-C_SOURCES := $(wildcard $(SRC_DIR)/kernel/*.c)
-C_SOURCES += $(wildcard $(SRC_DIR)/drivers/*.c)
-C_SOURCES += $(wildcard $(SRC_DIR)/mm/*.c)
- # HAL sources (architecture-specific)
- C_SOURCES += $(wildcard $(SRC_DIR)/hal/$(ARCH)/*.c)
+# Minimal kernel sources shared across all architectures
+KERNEL_COMMON := main.c console.c utils.c cmdline.c driver.c cpu_features.c
+C_SOURCES := $(addprefix $(SRC_DIR)/kernel/,$(KERNEL_COMMON))
+
+# HAL sources (architecture-specific)
+C_SOURCES += $(wildcard $(SRC_DIR)/hal/$(ARCH)/*.c)
 
 # --- x86 Configuration ---
 ifeq ($(ARCH),x86)
+    # x86 gets ALL kernel sources, drivers, and mm
+    C_SOURCES := $(wildcard $(SRC_DIR)/kernel/*.c)
+    C_SOURCES += $(wildcard $(SRC_DIR)/drivers/*.c)
+    C_SOURCES += $(wildcard $(SRC_DIR)/mm/*.c)
+    C_SOURCES += $(wildcard $(SRC_DIR)/hal/$(ARCH)/*.c)
+
     # Default Toolchain Prefix (can be overridden)
     ifdef CROSS
         TOOLPREFIX ?= i686-elf-
@@ -86,9 +91,10 @@ ifeq ($(ARCH),arm)
     CC := aarch64-linux-gnu-gcc
     AS := aarch64-linux-gnu-as
     LD := aarch64-linux-gnu-ld
-    CFLAGS := -ffreestanding -O2 -Wall -Wextra -Werror -Wno-error=cpp -Iinclude
+    OBJCOPY := aarch64-linux-gnu-objcopy
+    CFLAGS := -ffreestanding -O2 -Wall -Wextra -Werror -Wno-error=cpp -mno-outline-atomics -Iinclude
     LDFLAGS := -T $(SRC_DIR)/arch/arm/linker.ld
-    ASFLAGS := 
+    ASFLAGS :=
     ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/arm/*.S)
     C_SOURCES += $(wildcard $(SRC_DIR)/arch/arm/*.c)
 endif
@@ -98,9 +104,10 @@ ifeq ($(ARCH),riscv)
     CC := riscv64-linux-gnu-gcc
     AS := riscv64-linux-gnu-as
     LD := riscv64-linux-gnu-ld
+    OBJCOPY := riscv64-linux-gnu-objcopy
     CFLAGS := -ffreestanding -O2 -Wall -Wextra -Werror -Wno-error=cpp -Iinclude -mcmodel=medany
     LDFLAGS := -T $(SRC_DIR)/arch/riscv/linker.ld
-    ASFLAGS := 
+    ASFLAGS :=
     ASM_SOURCES := $(wildcard $(SRC_DIR)/arch/riscv/*.S)
     C_SOURCES += $(wildcard $(SRC_DIR)/arch/riscv/*.c)
 endif
@@ -134,7 +141,7 @@ ifneq ($(QEMU_INT),)
 QEMU_DFLAGS := $(QEMU_DFLAGS) -d int
 endif
 
-BOOT_OBJ := $(BUILD_DIR)/arch/x86/boot.o
+BOOT_OBJ := $(BUILD_DIR)/arch/$(ARCH)/boot.o
 KERNEL_OBJ := $(filter-out $(BOOT_OBJ), $(OBJ))
 
 all: $(KERNEL_NAME)
@@ -205,6 +212,16 @@ run: iso
                -serial file:serial.log -monitor none -no-reboot -no-shutdown \
                $(QEMU_DFLAGS)
 
+run-arm: adros-arm.bin
+       @rm -f serial-arm.log
+       @qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -nographic \
+               -kernel adros-arm.bin -serial mon:stdio $(QEMU_DFLAGS)
+
+run-riscv: adros-riscv.bin
+       @rm -f serial-riscv.log
+       @qemu-system-riscv64 -M virt -m 128M -nographic -bios none \
+               -kernel adros-riscv.bin -serial mon:stdio $(QEMU_DFLAGS)
+
 # ---- Static Analysis ----
 
 cppcheck:
index 573fb0f583c2b9abb32de23dee39314d6cb1afb1..f3c7c35a5093a7aceafa08980b69c50684f65ae2 100644 (file)
@@ -45,7 +45,33 @@ static inline int spin_is_locked(spinlock_t* l) {
  *   ARM:    LDREX/STREX
  *   RISC-V: AMOSWAP.W.AQ
  *   MIPS:   LL/SC
+ *
+ * Note: AArch64/RISC-V without MMU may need simpler locking since
+ * exclusive monitors (LDAXR/STXR) require cacheable memory.
  */
+#if defined(__aarch64__) || defined(__riscv)
+/* Simple volatile flag lock — safe for single-core bring-up without MMU.
+ * Will be replaced with proper atomics once MMU is enabled. */
+static inline void spin_lock(spinlock_t* l) {
+    while (l->locked) {
+        cpu_relax();
+    }
+    l->locked = 1;
+    __sync_synchronize();
+}
+
+static inline int spin_trylock(spinlock_t* l) {
+    if (l->locked) return 0;
+    l->locked = 1;
+    __sync_synchronize();
+    return 1;
+}
+
+static inline void spin_unlock(spinlock_t* l) {
+    __sync_synchronize();
+    l->locked = 0;
+}
+#else
 static inline void spin_lock(spinlock_t* l) {
     while (__sync_lock_test_and_set(&l->locked, 1)) {
         while (l->locked) {
@@ -62,6 +88,7 @@ static inline void spin_unlock(spinlock_t* l) {
     __sync_synchronize();
     __sync_lock_release(&l->locked);
 }
+#endif
 
 #if defined(__i386__) || defined(__x86_64__)
 static inline uintptr_t irq_save(void) {
@@ -81,6 +108,17 @@ static inline void irq_restore(uintptr_t flags) {
     __asm__ volatile ("push %0; popf" :: "r"(flags) : "memory", "cc");
 #endif
 }
+#elif defined(__aarch64__)
+static inline uintptr_t irq_save(void) {
+    uintptr_t daif;
+    __asm__ volatile("mrs %0, daif\n\tmsr daifset, #2" : "=r"(daif) :: "memory");
+    return daif;
+}
+
+static inline void irq_restore(uintptr_t flags) {
+    __asm__ volatile("msr daif, %0" :: "r"(flags) : "memory");
+}
+
 #elif defined(__arm__)
 static inline uintptr_t irq_save(void) {
     uintptr_t cpsr;
index c7f27a61ed6dbe7ccc9482d7411fb5923a740aa6..67572631940b33f90ee962cb6739d5933f489184 100644 (file)
@@ -10,7 +10,7 @@ extern void kernel_main(const struct boot_info* bi);
     (void)args;
 
     uart_init();
-    kprintf("\n[AdrOS] Booting...\n");
+    kprintf("\n[AdrOS/arm64] Booting on QEMU virt...\n");
 
     struct boot_info bi;
     bi.arch_magic = 0;
index e2f04daaf1100a2587b21f03d94a88e0eb9c7331..30ceb41e27ad32885c5c768ea681bf716fad5b55 100644 (file)
@@ -1,29 +1,60 @@
 /*
  * AdrOS - ARM64 (AArch64) Bootstrap
- * Target: QEMU 'virt' machine
+ * Target: QEMU 'virt' machine (-cpu cortex-a57)
  */
 
 .section .text
 .global _start
 
 _start:
-    /* 
-     * CPU usually starts in EL2 or EL3 on QEMU.
-     * Ideally we should switch to EL1, but for a "hello world"
-     * we will just set up the stack and run.
-     */
+    /* Disable all interrupts */
+    msr daifset, #0xf
 
-    /* Set up stack pointer (x30 is link register, don't clobber it yet) */
+    /* Check current exception level */
+    mrs x0, CurrentEL
+    lsr x0, x0, #2
+    cmp x0, #2
+    b.ne 1f
+
+    /* EL2 -> EL1 transition */
+    mov x0, #(1 << 31)         /* EL1 is AArch64 */
+    msr hcr_el2, x0
+    mov x0, #0x3c5             /* EL1h, all DAIF masked */
+    msr spsr_el2, x0
+    adr x0, 1f
+    msr elr_el2, x0
+    eret
+
+1:
+    /* Now in EL1 (or started in EL1) */
+
+    /* Enable FP/SIMD — required for variadic function prologues
+     * which save q0-q7 as part of the va_list register save area.
+     * Without this, any variadic call (kprintf etc.) traps. */
+    mrs x0, cpacr_el1
+    orr x0, x0, #(3 << 20)     /* FPEN = 0b11 (no trapping) */
+    msr cpacr_el1, x0
+    isb
+
+    /* Set up stack pointer */
     ldr x0, =stack_top
     mov sp, x0
 
+    /* Zero BSS section */
+    ldr x0, =__bss_start
+    ldr x1, =__bss_end
+3:  cmp x0, x1
+    b.ge 4f
+    str xzr, [x0], #8
+    b 3b
+4:
     /* Build arch_boot_args (in .bss) and call arch_early_setup(args) */
     ldr x0, =arch_boot_args
     bl arch_early_setup
 
     /* Hang */
-1:  wfi
-    b 1b
+2:  wfi
+    b 2b
 
 .section .bss
 .align 16
index 7042ae19efb3ca2b717a0a76bd7aab7d248037ce..2c547e1fcbeeda10c87add941beb31a9e439c5de 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * AdrOS - ARM64 Linker Script
- * Target: QEMU virt (RAM starts at 0x40000000 usually)
+ * Target: QEMU virt (RAM starts at 0x40000000)
  */
 
 ENTRY(_start)
@@ -8,25 +8,33 @@ ENTRY(_start)
 SECTIONS
 {
     . = 0x40000000;
-    
-    _start = .;
 
     .text : {
-        *(.text)
+        *(.text .text.*)
     }
 
+    . = ALIGN(4096);
     .rodata : {
-        *(.rodata)
+        *(.rodata .rodata.*)
     }
 
+    . = ALIGN(4096);
     .data : {
-        *(.data)
+        *(.data .data.*)
     }
 
+    . = ALIGN(4096);
     .bss : {
-        *(.bss)
+        __bss_start = .;
+        *(.bss .bss.*)
         *(COMMON)
+        __bss_end = .;
     }
-    
+
     _end = .;
+
+    /DISCARD/ : {
+        *(.comment)
+        *(.note*)
+    }
 }
diff --git a/src/arch/arm/stubs.c b/src/arch/arm/stubs.c
new file mode 100644 (file)
index 0000000..30e1c69
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * ARM64 stub implementations for kernel subsystems not yet ported.
+ * Provides weak symbols so main.c and console.c link successfully.
+ */
+#include <stdint.h>
+#include <stddef.h>
+#include "hal/uart.h"
+#include "spinlock.h"
+
+/* ---- UART console (wraps HAL UART) ---- */
+static spinlock_t uart_lock = {0};
+
+void uart_init(void) {
+    hal_uart_init();
+}
+
+void uart_put_char(char c) {
+    uintptr_t flags = spin_lock_irqsave(&uart_lock);
+    hal_uart_putc(c);
+    spin_unlock_irqrestore(&uart_lock, flags);
+}
+
+void uart_print(const char* str) {
+    uintptr_t flags = spin_lock_irqsave(&uart_lock);
+    for (int i = 0; str[i] != '\0'; i++)
+        hal_uart_putc(str[i]);
+    spin_unlock_irqrestore(&uart_lock, flags);
+}
+
+/* ---- VGA console (no-op on ARM) ---- */
+void vga_init(void) { }
+void vga_put_char(char c) { (void)c; }
+void vga_write_buf(const char* buf, uint32_t len) { (void)buf; (void)len; }
+void vga_print(const char* str) { (void)str; }
+void vga_set_color(uint8_t fg, uint8_t bg) { (void)fg; (void)bg; }
+void vga_flush(void) { }
+void vga_clear(void) { }
+void vga_scroll_back(void) { }
+void vga_scroll_fwd(void) { }
+
+/* ---- Kernel subsystem stubs (not yet ported) ---- */
+void pmm_init(void* mboot_info) { (void)mboot_info; }
+void kheap_init(void) { }
+void shm_init(void) { }
+void kaslr_init(void) { }
+void process_init(void) { }
+void vdso_init(void) { }
+void timer_init(uint32_t hz) { (void)hz; }
+int  init_start(const void* bi) { (void)bi; return -1; }
+void kconsole_enter(void) { }
+
+/* ---- Keyboard (no-op) ---- */
+void keyboard_init(void) { }
+int  keyboard_getchar(void) { return -1; }
+int  keyboard_read_nonblock(void) { return -1; }
+
+/* ---- HAL CPU extras ---- */
+void hal_cpu_set_address_space(uintptr_t as) { (void)as; }
+void hal_cpu_disable_interrupts(void) {
+    __asm__ volatile("msr daifset, #2" ::: "memory");
+}
+uint64_t hal_cpu_read_timestamp(void) { return 0; }
+void hal_cpu_set_tls(uintptr_t base) { (void)base; }
index c7f27a61ed6dbe7ccc9482d7411fb5923a740aa6..570368aa31c2783f87de7a05602f1ac77797ff67 100644 (file)
@@ -10,7 +10,7 @@ extern void kernel_main(const struct boot_info* bi);
     (void)args;
 
     uart_init();
-    kprintf("\n[AdrOS] Booting...\n");
+    kprintf("\n[AdrOS/riscv64] Booting on QEMU virt...\n");
 
     struct boot_info bi;
     bi.arch_magic = 0;
index 8f6bd2468ab6b3b681a8ce930551292e061d0056..2f295980afd8901b6acd1fab60fc11a07f2cf538 100644 (file)
@@ -1,27 +1,33 @@
 /*
  * AdrOS - RISC-V 64-bit Bootstrap
- * Target: QEMU 'virt' machine (starts at 0x80000000)
+ * Target: QEMU 'virt' machine (-bios none, starts at 0x80000000)
  */
 
 .section .text
 .global _start
 
 _start:
-    /* Disable interrupts (sie = Supervisor Interrupt Enable) */
-    csrw sie, zero
+    /* Disable interrupts */
+    csrw mie, zero
 
-    /* Set up stack pointer.
-     * In linker.ld we will define a symbol for the stack top.
-     */
+    /* Set up stack pointer */
     la sp, stack_top
 
-    /* Build arch_boot_args (in .bss) and call arch_early_setup(args) */
+    /* Zero BSS section */
+    la t0, __bss_start
+    la t1, __bss_end
+1:  bge t0, t1, 2f
+    sd zero, 0(t0)
+    addi t0, t0, 8
+    j 1b
+2:
+    /* Call arch_early_setup(args) */
     la a0, arch_boot_args
     call arch_early_setup
 
     /* Hang if return */
-1:  wfi
-    j 1b
+3:  wfi
+    j 3b
 
 .section .bss
 .align 16
index df0e9895c226819c32d340a547cddefab39c4055..d6e1e670186c69122a4c9f3a29d7c9303eed687c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * AdrOS - RISC-V Linker Script
+ * AdrOS - RISC-V 64-bit Linker Script
  * Target: QEMU virt (RAM starts at 0x80000000)
  */
 
@@ -8,25 +8,33 @@ ENTRY(_start)
 SECTIONS
 {
     . = 0x80000000;
-    
-    _start = .;
 
     .text : {
-        *(.text)
+        *(.text .text.*)
     }
 
+    . = ALIGN(4096);
     .rodata : {
-        *(.rodata)
+        *(.rodata .rodata.*)
     }
 
+    . = ALIGN(4096);
     .data : {
-        *(.data)
+        *(.data .data.*)
     }
 
+    . = ALIGN(4096);
     .bss : {
-        *(.bss)
+        __bss_start = .;
+        *(.bss .bss.*)
         *(COMMON)
+        __bss_end = .;
     }
-    
+
     _end = .;
+
+    /DISCARD/ : {
+        *(.comment)
+        *(.note*)
+    }
 }
diff --git a/src/arch/riscv/stubs.c b/src/arch/riscv/stubs.c
new file mode 100644 (file)
index 0000000..e487c82
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * RISC-V 64 stub implementations for kernel subsystems not yet ported.
+ * Provides weak symbols so main.c and console.c link successfully.
+ */
+#include <stdint.h>
+#include <stddef.h>
+#include "hal/uart.h"
+#include "spinlock.h"
+
+/* ---- UART console (wraps HAL UART) ---- */
+static spinlock_t uart_lock = {0};
+
+void uart_init(void) {
+    hal_uart_init();
+}
+
+void uart_put_char(char c) {
+    uintptr_t flags = spin_lock_irqsave(&uart_lock);
+    hal_uart_putc(c);
+    spin_unlock_irqrestore(&uart_lock, flags);
+}
+
+void uart_print(const char* str) {
+    uintptr_t flags = spin_lock_irqsave(&uart_lock);
+    for (int i = 0; str[i] != '\0'; i++)
+        hal_uart_putc(str[i]);
+    spin_unlock_irqrestore(&uart_lock, flags);
+}
+
+/* ---- VGA console (no-op on RISC-V) ---- */
+void vga_init(void) { }
+void vga_put_char(char c) { (void)c; }
+void vga_write_buf(const char* buf, uint32_t len) { (void)buf; (void)len; }
+void vga_print(const char* str) { (void)str; }
+void vga_set_color(uint8_t fg, uint8_t bg) { (void)fg; (void)bg; }
+void vga_flush(void) { }
+void vga_clear(void) { }
+void vga_scroll_back(void) { }
+void vga_scroll_fwd(void) { }
+
+/* ---- Kernel subsystem stubs (not yet ported) ---- */
+void pmm_init(void* mboot_info) { (void)mboot_info; }
+void kheap_init(void) { }
+void shm_init(void) { }
+void kaslr_init(void) { }
+void process_init(void) { }
+void vdso_init(void) { }
+void timer_init(uint32_t hz) { (void)hz; }
+int  init_start(const void* bi) { (void)bi; return -1; }
+void kconsole_enter(void) { }
+
+/* ---- Keyboard (no-op) ---- */
+void keyboard_init(void) { }
+int  keyboard_getchar(void) { return -1; }
+int  keyboard_read_nonblock(void) { return -1; }
+
+/* ---- HAL CPU extras ---- */
+void hal_cpu_set_address_space(uintptr_t as) { (void)as; }
+void hal_cpu_disable_interrupts(void) {
+    __asm__ volatile("csrci mstatus, 0x8" ::: "memory");
+}
+uint64_t hal_cpu_read_timestamp(void) {
+    uint64_t val;
+    __asm__ volatile("rdcycle %0" : "=r"(val));
+    return val;
+}
+void hal_cpu_set_tls(uintptr_t base) { (void)base; }
index ef09a7f56c67548ba492c598c7ad9e9e87db4882..f91d18571dfeb3620dbd564076cf3a4804818352 100644 (file)
@@ -6,6 +6,6 @@ int hal_usermode_enter(uintptr_t user_eip, uintptr_t user_esp) {
     return -1;
 }
 
-void hal_usermode_enter_regs(const struct registers* regs) {
+void hal_usermode_enter_regs(const void* regs) {
     (void)regs;
 }
index ef09a7f56c67548ba492c598c7ad9e9e87db4882..f91d18571dfeb3620dbd564076cf3a4804818352 100644 (file)
@@ -6,6 +6,6 @@ int hal_usermode_enter(uintptr_t user_eip, uintptr_t user_esp) {
     return -1;
 }
 
-void hal_usermode_enter_regs(const struct registers* regs) {
+void hal_usermode_enter_regs(const void* regs) {
     (void)regs;
 }