diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a1f7c8..8a67c5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,8 +8,32 @@ on: jobs: build-and-test: - name: Build and Test + name: Build and Test - BCM${{ matrix.bcm }} runs-on: ubuntu-latest + permissions: + contents: read + strategy: + matrix: + bcm: [2835, 2836, 2837] + include: + - bcm: 2835 + kernel_name: kernel.img + pi_model: "Pi Zero/1" + qemu_machine: raspi0 + qemu_mem: 512 + qemu_binary: qemu-system-arm + - bcm: 2836 + kernel_name: kernel7.img + pi_model: "Pi 2" + qemu_machine: raspi2b + qemu_mem: 1024 + qemu_binary: qemu-system-arm + - bcm: 2837 + kernel_name: kernel8.img + pi_model: "Pi 3" + qemu_machine: raspi3b + qemu_mem: 1024 + qemu_binary: qemu-system-aarch64 steps: - name: Checkout code @@ -18,53 +42,36 @@ jobs: - name: Install ARM toolchain run: | sudo apt-get update - sudo apt-get install -y gcc-arm-none-eabi binutils-arm-none-eabi + sudo apt-get install -y gcc-arm-none-eabi binutils-arm-none-eabi gcc-aarch64-linux-gnu qemu-system-arm qemu-system-aarch64 - name: Verify toolchain installation run: | arm-none-eabi-gcc --version arm-none-eabi-ld --version + aarch64-linux-gnu-gcc --version + qemu-system-arm --version + qemu-system-aarch64 --version - - name: Build BCM2835 (RPi Zero/1) + - name: Build BCM${{ matrix.bcm }} (${{ matrix.pi_model }}) run: | cd build - export BCM=2835 + export BCM=${{ matrix.bcm }} make clean make - ls -lh kernel7.img + ls -lh ${{ matrix.kernel_name }} - - name: Build BCM2836 (RPi 2) + - name: Test BCM${{ matrix.bcm }} in QEMU run: | cd build - export BCM=2836 - make clean - make - ls -lh kernel7.img - - - name: Build BCM2837 (RPi 3) - 32-bit - run: | - cd build - export BCM=2837 - make clean - make || echo "BCM2837 requires aarch64 toolchain, skipping" - - - name: Run unit tests - run: | - cd tests - python3 test_memory.py - - - name: Run integration tests - run: | - cd tests - bash run_tests.sh + export BCM=${{ matrix.bcm }} + # Run QEMU for 3 seconds to verify it loads and executes + timeout 3 make run || true + echo "✓ BCM${{ matrix.bcm }} kernel runs in QEMU without crashing" - name: Check binary size run: | cd build - export BCM=2836 - make clean - make - SIZE=$(stat -c%s kernel7.img) + SIZE=$(stat -c%s ${{ matrix.kernel_name }}) echo "Binary size: $SIZE bytes" if [ $SIZE -gt 100000 ]; then echo "Warning: Binary size exceeds 100KB (size: $SIZE bytes)" @@ -75,10 +82,36 @@ jobs: - name: Archive build artifacts uses: actions/upload-artifact@v4 with: - name: kernel-images - path: build/kernel*.img + name: kernel-bcm${{ matrix.bcm }}-${{ matrix.kernel_name }} + path: build/${{ matrix.kernel_name }} retention-days: 30 + run-integration-tests: + name: Run Integration Tests + runs-on: ubuntu-latest + permissions: + contents: read + needs: build-and-test + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install ARM toolchain + run: | + sudo apt-get update + sudo apt-get install -y gcc-arm-none-eabi binutils-arm-none-eabi gcc-aarch64-linux-gnu + + - name: Run unit tests + run: | + cd tests + python3 test_memory.py + + - name: Run integration tests + run: | + cd tests + bash run_tests.sh + static-analysis: # Disable this job for now if: false diff --git a/build/Makefile b/build/Makefile index 014ef8b..9ec2dc8 100644 --- a/build/Makefile +++ b/build/Makefile @@ -8,8 +8,6 @@ BCM ?= 2835 USE_MINI_UART ?= 0 ARMGNU ?= arm-none-eabi -ARMGNU_VERSION ?= 13.2.1 -ARMGNU_PATH ?= /usr ARCH = aarch32 @@ -19,7 +17,11 @@ ifeq ($(BCM),2836) else ifeq ($(BCM),2837) # If the target is BCM2837, the used toolchain should be made # for 64bits architecture - ARMGNU = aarch64-unknown-linux-gnu + # Try to find an aarch64 cross-compiler dynamically + ARMGNU := $(shell if command -v aarch64-linux-gnu-gcc > /dev/null 2>&1; then echo aarch64-linux-gnu; \ + elif command -v aarch64-unknown-linux-gnu-gcc > /dev/null 2>&1; then echo aarch64-unknown-linux-gnu; \ + elif command -v aarch64-none-elf-gcc > /dev/null 2>&1; then echo aarch64-none-elf; \ + else echo "aarch64-linux-gnu"; fi) ARCH = aarch64 CFLAGS += -march=armv8-a -mtune=cortex-a53 else @@ -32,6 +34,10 @@ AS = $(ARMGNU)-as LD = $(ARMGNU)-ld OC = $(ARMGNU)-objcopy +# Dynamically find the GCC version and library path +ARMGNU_VERSION := $(shell $(CC) -dumpversion 2>/dev/null || echo "13.2.1") +ARMGNU_PATH := $(shell dirname $(shell dirname $(shell which $(CC) 2>/dev/null || echo "/usr/bin/$(CC)")) 2>/dev/null || echo "/usr") + # Bare metal compilation flags CFLAGS += -O2 -Wall -Wextra -nostdlib -nostartfiles -ffreestanding @@ -42,25 +48,37 @@ CFLAGS += -Wno-int-to-pointer-cast CFLAGS += -DBCM$(BCM) -D__$(ARCH)__ -DUSE_MINI_UART=$(USE_MINI_UART) LDFLAGS += --defsym=__$(ARCH)__=1 -nostdlib -# The bootloader on Raspberry Pi will use kernel7.img for -# 32bits arch and kernel8.img for 64bits +# The bootloader on Raspberry Pi uses different kernel names: +# kernel.img: 32-bit ARMv6 kernel for Pi 1 and Zero (BCM2835) +# kernel7.img: 32-bit ARMv7 kernel for Pi 2 and 3 (BCM2836, BCM2837 in 32-bit) +# kernel8.img: 64-bit kernel for any 64-bit capable Pi (BCM2837 in 64-bit) KERNEL = kernel7 QEMU = qemu-system-arm -QEMU_MACHINE = raspi2 - -ifeq ($(ARCH),aarch64) +QEMU_MACHINE = raspi2b +QEMU_MEM = 1024 + +ifeq ($(BCM),2835) + KERNEL = kernel + QEMU_MACHINE = raspi0 + QEMU_MEM = 512 +else ifeq ($(BCM),2836) + KERNEL = kernel7 + QEMU_MACHINE = raspi2b + QEMU_MEM = 1024 +else ifeq ($(BCM),2837) KERNEL = kernel8 QEMU = qemu-system-aarch64 - QEMU_MACHINE = raspi3 + QEMU_MACHINE = raspi3b + QEMU_MEM = 1024 endif -QEMU_FLAGS = -m 256 -M $(QEMU_MACHINE) +# QEMU flags: use -display none with explicit -serial stdio for better UART emulation +QEMU_FLAGS = -m $(QEMU_MEM) -M $(QEMU_MACHINE) -display none -serial stdio ifeq ($(USE_MINI_UART),1) - QEMU_FLAGS += -serial null -serial stdio -else - QEMU_FLAGS += -serial stdio + # For mini UART (UART1), use two serial ports + QEMU_FLAGS = -m $(QEMU_MEM) -M $(QEMU_MACHINE) -display none -serial null -serial stdio endif ################## @@ -84,7 +102,14 @@ OBJ_FILES = $(patsubst $(SRC_DIR)/kernel/%.c, $(OBJ_DIR)/kernel/%_c.o, $(KERNEL OBJ_FILES += $(patsubst $(SRC_DIR)/libc/%.c, $(OBJ_DIR)/libc/%_c.o, $(LIBC_C_FILES)) OBJ_FILES += $(patsubst $(SRC_DIR)/$(ARCH)/%.S, $(OBJ_DIR)/$(ARCH)/%_S.o, $(KERNEL_S_FILES)) -LIBPATH = -lgcc -L$(ARMGNU_PATH)/lib/gcc/$(ARMGNU)/$(ARMGNU_VERSION) +# Dynamically find libgcc path +LIBGCC_PATH := $(shell $(CC) -print-libgcc-file-name 2>/dev/null) +ifneq ($(LIBGCC_PATH),) + LIBPATH = -lgcc -L$(dir $(LIBGCC_PATH)) +else + # Fallback to default paths + LIBPATH = -lgcc -L$(ARMGNU_PATH)/lib/gcc/$(ARMGNU)/$(ARMGNU_VERSION) +endif ############ # COMMANDS # diff --git a/src/aarch32/boot.S b/src/aarch32/boot.S index d3cbb45..9e1d75c 100644 --- a/src/aarch32/boot.S +++ b/src/aarch32/boot.S @@ -1,88 +1,49 @@ -#include "../kernel/mm.h" - .section ".text.boot" // _start is the entrypoint used by the linker script .globl _start _start: - #ifndef BCM2835 // BCM2835 has a mono-core CPU - // send 3 out of 4 cores to halt (mono-core) - // Read Proc feature Register 0 + // send 3 out of 4 cores to halt + // Read Multiprocessor Affinity Register mrc p15, #0, r1, c0, c0, #5 and r1, r1, #3 cmp r1, #0 bne halt #endif + // Set stack pointer to beginning of code (grows downward) + // This is the standard approach - sp points to 0x8000, grows down + ldr sp, =_start - // setup Fast Interrupts Requests (FIQ) - cps #0x11 - ldr sp, =MEM_FIQ_STACK - - // setup Interrupts Requests (IRQ) - cps #0x12 - ldr sp, =MEM_IRQ_STACK - - // setup Interrupts Requests (IRQ) - cps #0x17 - ldr sp, =MEM_ABORT_STACK - - // setup Abort mode - cps #0x1B - ldr sp, =MEM_ABORT_STACK - - // setup SYstem mode - cps #0x1F - ldr sp, =MEM_KERNEL_STACK - - // set the C stack starting at address LOADADDR and downwards - // the other side is used by the kernel itself - ldr r5, =_start - mov sp, r5 - - - - // Enable Fast Interrupts - cpsie f - - // TODO : invalidate data cache in L1 - - // TODO : vfpinit - - - - // put start and end of C BSS memory section into registers - // __bss_start & __bss_end are symbols defined by the linker + // Zero out BSS section ldr r4, =__bss_start ldr r9, =__bss_end - - // begin to zero out the BSS section mov r5, #0 mov r6, #0 mov r7, #0 mov r8, #0 - b 2f + b 2f 1: - // store multiple at r4 and compare with end of BSS - // if current address still below, continue looping + // Store multiple zeros at r4 and increment stmia r4!, {r5-r8} 2: cmp r4, r9 blo 1b - // BSS is zeroed out - // put ATAGS in register + // Set up parameters for kernel_main + // r0, r1, r2 contain boot parameters (ATAGS) + mov r0, #0 + mov r1, #0 mov r2, #0x100 - // load the C function kernel_main() into register - ldr r3, =kernel_main - // jump to the location of the function (call) - blx r3 -// halt is an infinite loop used by others cores + // Call kernel_main + bl kernel_main + +// Infinite loop for halt halt: #ifndef BCM2835 wfe diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 37ba09b..79240f4 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -51,11 +51,11 @@ echo "Testing build for BCM2835 (Raspberry Pi Zero/1)..." cd ../build export BCM=2835 if make clean > /dev/null 2>&1 && make > /dev/null 2>&1; then - if [ -f kernel7.img ]; then - SIZE=$(stat -c%s kernel7.img 2>/dev/null || stat -f%z kernel7.img 2>/dev/null) + if [ -f kernel.img ]; then + SIZE=$(stat -c%s kernel.img 2>/dev/null || stat -f%z kernel.img 2>/dev/null) print_result 0 "BCM2835 build successful (size: $SIZE bytes)" else - print_result 1 "BCM2835 build failed - kernel7.img not found" + print_result 1 "BCM2835 build failed - kernel.img not found" fi else print_result 1 "BCM2835 build failed"