![]() |
RT-Thread RTOS
An open source embedded real-time operating system
|
This guide walks through the multi-core boot path of RT-Thread on AArch64 using bsp/qemu-virt64-aarch64 as the concrete reference. It is written to be beginner-friendly and mirrors the current BSP implementation: from _start assembly, early MMU bring-up, rtthread_startup(), PSCI wakeup of secondary cores, to all CPUs entering the scheduler. The key transitions are paired with static SVG diagrams so the flow stays readable on GitHub and in generated documentation.
-machine virt, -cpu cortex-a57, -smp >=2, RT_USING_SMP enabled, device tree contains enable-method = "psci".The overview highlights the two key boundaries in this BSP: CPU0 completes the early assembly and common board bring-up, then rt_hw_secondary_cpu_up() hands secondary cores over to their own ASM plus C initialization path before everyone meets in the scheduler.
Input registers: QEMU firmware loads the image and jumps to _start at libcpu/aarch64/cortex-a/entry_point.S, passing the DTB physical address in x0 (with x1~x3 reserved).
What _start does (short version)
tpidr_el1/tpidrro_el0 to avoid stale per-cpu state.init_cpu_el drops to EL1h, enables timer access, masks unwanted traps.init_kernel_bss fills __bss with zeros so globals start clean.init_cpu_stack_early switches to SP_EL1 and uses .boot_cpu_stack_top as the early stack.rt_hw_fdt_install_early(x0) stores DTB address/size before MMU is enabled.init_mmu_early/enable_mmu_early build a 0~1G identity map, set TTBR0/TTBR1 and SCTLR_EL1, flush I/D cache and TLB, then branch to rtthread_startup() (address in x8).Tip: the early page table only covers minimal kernel space; the C phase will remap a fuller layout.
rtthread_startup() (in src/components.c) is the spine of the sequence:
rt_hw_local_irq_disable() followed by _cpus_lock init to keep early steps non-preemptible.rt_hw_board_init() directly calls the BSP hook rt_hw_common_setup() (libcpu/aarch64/common/setup.c) to:RT_SCHEDULE_IPI, RT_STOP_IPI, RT_SMP_CALL_IPI) and unmask them;rt_hw_idle_wfi so idle CPUs enter low-power wait.rt_system_scheduler_start() runs main_thread_entry() first.main_thread_entry() calls rt_hw_secondary_cpu_up() before invoking user main(), so all CPUs join scheduling.
_secondary_cpu_entry to a physical address via rt_kmem_v2p()—the real entry the firmware jumps to.cpu_info_init() stored DTB info in cpu_np[] and rt_cpu_mpidr_table[]).enable-method:"psci" → use cpu_psci_ops.cpu_boot() to issue CPU_ON(target, entry) to firmware."spin-table" → write cpu-release-addr and sev to wake._secondary_cpu_entry:mpidr_el1, compare with rt_cpu_mpidr_table to find the logical CPU id, store it back, and write it into TPIDR for per-cpu access.ARCH_SECONDARY_CPU_STACK_SIZE per core.init_cpu_el/init_cpu_stack_early, reuse the same early MMU path, then branch to rt_hw_secondary_cpu_bsp_start().rt_hw_secondary_cpu_bsp_start() (libcpu/aarch64/common/setup.c):_cpus_lock.MMUTable.loops_per_tick for microsecond delay if needed.rt_dm_secondary_cpu_init() to register the CPU device, then enter the scheduler via rt_system_scheduler_start().Read this figure from top to bottom: CPU0 reaches rt_system_scheduler_start() first, main_thread_entry() triggers CPU_ON, and each secondary CPU repeats the minimal EL plus MMU path before entering rt_hw_secondary_cpu_bsp_start().
| Stage | File | Role |
|---|---|---|
| Boot assembly | libcpu/aarch64/cortex-a/entry_point.S | _start, _secondary_cpu_entry, early MMU enable |
| BSP hook | bsp/qemu-virt64-aarch64/drivers/board.c | Wires rt_hw_board_init() to rt_hw_common_setup() |
| Memory/GIC/IPI init | libcpu/aarch64/common/setup.c | rt_hw_common_setup(), rt_hw_secondary_cpu_up(), rt_hw_secondary_cpu_bsp_start() |
| C entry skeleton | src/components.c | rtthread_startup(), main_thread_entry() |
enable-method = "psci" and QEMU is started with -machine virt (PSCI firmware included)._secondary_cpu_entry physical address: rt_kmem_v2p() must not return 0, otherwise a check fails.rt_hw_secondary_cpu_up(); if you fork a custom BSP, do these first.Call cpu X on success/failed; add extra prints in _secondary_cpu_entry if needed, and use QEMU -d cpu_reset -smp N to debug.init_cpu_el descends to EL1h where the kernel runs.spsel #1 selects SP_EL1 so user mode cannot touch the kernel stack.SCTLR_EL1.M/C/I → isb.rt_cpu_mpidr_table[] to map logical CPU ids and IPI targets.With these in place, the QEMU virt64 AArch64 BSP SMP path is clear: the boot CPU prepares memory and shared peripherals, main_thread_entry() issues PSCI wakeups, secondary cores land with the same MMU/EL setup, and all CPUs join the scheduler.