From: Tulio A M Mendes Date: Sun, 15 Feb 2026 03:50:50 +0000 (-0300) Subject: feat: multi-arch ARM64/RISC-V bring-up with QEMU virt boot X-Git-Url: https://projects.tadryanom.me/docs/static/gitweb.js?a=commitdiff_plain;h=cce0d2f5918806f9f6cb23a203280e1d047a9765;p=AdrOS.git feat: multi-arch ARM64/RISC-V bring-up with QEMU virt boot 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. --- diff --git a/Makefile b/Makefile index 7b94337..cbf50a9 100644 --- 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: diff --git a/include/spinlock.h b/include/spinlock.h index 573fb0f..f3c7c35 100644 --- a/include/spinlock.h +++ b/include/spinlock.h @@ -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; diff --git a/src/arch/arm/arch_early_setup.c b/src/arch/arm/arch_early_setup.c index c7f27a6..6757263 100644 --- a/src/arch/arm/arch_early_setup.c +++ b/src/arch/arm/arch_early_setup.c @@ -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; diff --git a/src/arch/arm/boot.S b/src/arch/arm/boot.S index e2f04da..30ceb41 100644 --- a/src/arch/arm/boot.S +++ b/src/arch/arm/boot.S @@ -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 diff --git a/src/arch/arm/linker.ld b/src/arch/arm/linker.ld index 7042ae1..2c547e1 100644 --- a/src/arch/arm/linker.ld +++ b/src/arch/arm/linker.ld @@ -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 index 0000000..30e1c69 --- /dev/null +++ b/src/arch/arm/stubs.c @@ -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 +#include +#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; } diff --git a/src/arch/riscv/arch_early_setup.c b/src/arch/riscv/arch_early_setup.c index c7f27a6..570368a 100644 --- a/src/arch/riscv/arch_early_setup.c +++ b/src/arch/riscv/arch_early_setup.c @@ -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; diff --git a/src/arch/riscv/boot.S b/src/arch/riscv/boot.S index 8f6bd24..2f29598 100644 --- a/src/arch/riscv/boot.S +++ b/src/arch/riscv/boot.S @@ -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 diff --git a/src/arch/riscv/linker.ld b/src/arch/riscv/linker.ld index df0e989..d6e1e67 100644 --- a/src/arch/riscv/linker.ld +++ b/src/arch/riscv/linker.ld @@ -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 index 0000000..e487c82 --- /dev/null +++ b/src/arch/riscv/stubs.c @@ -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 +#include +#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; } diff --git a/src/hal/arm/usermode.c b/src/hal/arm/usermode.c index ef09a7f..f91d185 100644 --- a/src/hal/arm/usermode.c +++ b/src/hal/arm/usermode.c @@ -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; } diff --git a/src/hal/riscv/usermode.c b/src/hal/riscv/usermode.c index ef09a7f..f91d185 100644 --- a/src/hal/riscv/usermode.c +++ b/src/hal/riscv/usermode.c @@ -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; }