]> 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 60ebdd403c318a9d3fafd7bed7ced1d24aca865e..6c18c6a194234050e2294d4bb8c23043d916d7f1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -17,16 +17,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-
@@ -95,9 +100,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
@@ -107,9 +113,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
@@ -143,7 +150,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)
@@ -214,6 +221,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 e06601183495b6e2df07c4ec63b1f6444268cba4..decc422cb13a0bb53b1bb4bcaa525a75f089af11 100644 (file)
@@ -54,7 +54,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) {
@@ -71,6 +97,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) {
@@ -90,6 +117,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 0fe75a6f1a4e49982216edcdaa5926c0809f553d..95f7c2abe7b7b2cd34ec6ebb6d229fb7b2966eb7 100644 (file)
@@ -19,7 +19,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 4f1f5440f5707f436f7b193f19048962c86356e3..f0d596ed6a563b2bf6a55434d9e3db62595e529d 100644 (file)
@@ -9,30 +9,61 @@
 
 /*
  * 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 bd13983f1d1ff4e07ecff4dfe9ddcc1022d69747..5517c98864dbc6598d88d7d301437ef380392b71 100644 (file)
@@ -9,7 +9,7 @@
 
 /*
  * AdrOS - ARM64 Linker Script
- * Target: QEMU virt (RAM starts at 0x40000000 usually)
+ * Target: QEMU virt (RAM starts at 0x40000000)
  */
 
 ENTRY(_start)
@@ -17,25 +17,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..6dd8333
--- /dev/null
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2018, Tulio A M Mendes <[email protected]>
+ * All rights reserved.
+ * See LICENSE for details.
+ *
+ * Source: https://github.com/tadryanom/AdrOS
+ */
+
+/*
+ * 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 0fe75a6f1a4e49982216edcdaa5926c0809f553d..11f4ad811e5a12fca67fc821237e58d1cd40d03e 100644 (file)
@@ -19,7 +19,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 1c4c5a4f76fab0b9585fdfcda3d7ed865b22189f..2f1e92e893a6e7f5a43473247554f9881f3ce6ae 100644 (file)
@@ -9,28 +9,34 @@
 
 /*
  * 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 afee350406cea06aba2c97f45fc5789be2a3a81c..3aaacc7fa85a5eb9fdb4e2ccd49bff1fdfc46a1a 100644 (file)
@@ -8,7 +8,7 @@
  */
 
 /*
- * AdrOS - RISC-V Linker Script
+ * AdrOS - RISC-V 64-bit Linker Script
  * Target: QEMU virt (RAM starts at 0x80000000)
  */
 
@@ -17,25 +17,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..7be1daa
--- /dev/null
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: BSD-3-Clause
+/*
+ * Copyright (c) 2018, Tulio A M Mendes <[email protected]>
+ * All rights reserved.
+ * See LICENSE for details.
+ *
+ * Source: https://github.com/tadryanom/AdrOS
+ */
+
+/*
+ * 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 222645af6d536fafaed72404be4927826d177c37..c1b4efdbef0bc80bb96e6f291c956b5d053b9b25 100644 (file)
@@ -15,6 +15,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 222645af6d536fafaed72404be4927826d177c37..c1b4efdbef0bc80bb96e6f291c956b5d053b9b25 100644 (file)
@@ -15,6 +15,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;
 }