This page describes the sequence of execution from power-on (or QEMU start) until the kernel is running.
1. Load and Entry
The GPU (on real hardware) or QEMU loads the bootloader binary at physical address 0x80000. Execution starts at the first instruction of the image, which must be _start in boot/start.S. All four cores may enter here; only core 0 is allowed to continue.
2. Assembly Initialisation (boot/start.S)
Park secondary cores. Read mpidr_el1 and mask to the bottom 8 bits (aff0). If the result is non-zero, the core branches to _park_core and spins in a WFE loop. Only core 0 proceeds.
Detect exception level. Read CurrentEL and shift right by 2 to obtain the level number. If the level is 2 (EL2), jump to _from_el2. If it is 1 (EL1), jump to _el1_entry. Any other level (0 or 3) is treated as unsupported and the core parks.
EL2 to EL1 (if applicable). Set HCR_EL2 so that EL1 is AArch64. Set SCTLR_EL1 to zero (MMU and caches off). Set SPSR_EL2 so that on return, execution is at EL1 with SP_EL1 and interrupts masked. Set ELR_EL2 to the address of _el1_entry. Execute eret to drop to EL1.
EL1 setup. At _el1_entry, clear the MMU and cache bits in SCTLR_EL1 again and execute an ISB. Load the address of _start (0x80000) into the stack pointer. Zero the BSS range between __bss_start and __bss_end. Then call neutron_main() (C). If it ever returns, execution falls through to _park_core.
3. C Bootloader (neutron/main.c)
UART and banner. neutron_main() calls uart_init() and prints a short banner so that serial output is visible.
CPU state. It reads the current exception level and MPIDR and prints them for debugging.
Mailbox. It calls mbox_get_board_revision() and mbox_get_arm_mem_size() and prints the results. On QEMU these may return 0; on real hardware they provide board and memory information.
SD card. It calls sdcard_init(). If this fails, it prints an error and halts (WFE loop). The SD card must be present and initialised for the rest of boot.
FAT32. It calls fat32_mount() to read the MBR and mount the first partition as FAT32. If mount fails, it halts.
Load kernel file. It calls fat32_read_file("ATOM.BIN", ...) to read the entire file into the buffer at KERNEL_LOAD_ADDR (0x100000). If the file is not found or read fails, it halts.
Magic check. It reads the first 4 bytes at 0x100000 and checks for the NKRN magic (0x4E4B524E). If the magic is wrong, it prints an error (e.g. suggesting that the kernel was not packed with pack_kernel.py) and halts.
Validate and load. It calls bl_load_kernel(KERNEL_LOAD_ADDR, &boot_info). That function (in neutron/bootloader.c) validates the NKRN header, verifies the payload CRC32, copies the payload to the address given in the header (typically 0x200000), and fills the boot_info_t at 0x1000. If validation fails, the bootloader halts.
Handoff. It fills in board_revision and arm_mem_size in boot_info from the mailbox, then calls bl_boot_kernel(entry_addr, &boot_info). That function performs a DSB and ISB, then calls the kernel entry point with x0 set to the address of boot_info. It does not return.
4. Kernel Entry
The kernel runs at EL1 with MMU and caches off. Register x0 holds the physical address of boot_info_t. The kernel is responsible for setting up its own stack and initialising any hardware it needs (including UART if it wants serial output). The bootloader does not guarantee the state of peripherals after the jump.
Summary
- GPU/QEMU loads image at 0x80000; core 0 runs
_start. - Secondary cores park; core 0 drops from EL2 to EL1 (if needed), sets stack, zeroes BSS, calls
neutron_main. - UART, mailbox, SD, FAT32; read
ATOM.BINto 0x100000; validate NKRN; copy payload to 0x200000; fill boot_info at 0x1000. - Jump to kernel entry with
x0= boot_info pointer.
See Components for the source files that implement each step.