This page describes how to build an AArch64 kernel that the Neutron bootloader can load: link address, entry point, and minimal layout. The bundled test kernel under test_kernel/ serves as a reference.
Load and Entry Address
The bootloader copies the NKRN payload to the address specified in the header field load_addr and then jumps to entry_addr. In the default setup both are 0x200000. Your kernel must be linked so that:
- Its code and data are placed assuming the image is loaded at 0x200000 (or whatever load_addr you use).
- The first instruction of the binary is the kernel entry point (the instruction that will run at
entry_addr).
So the linker script must set the load address (VMA/LMA) to 0x200000 (or your chosen address), and the entry symbol must be the first code in the linked image.
Linker Script
Use a linker script that sets the load address and orders sections so the entry is first. Example (from test_kernel/linker/kernel.ld):
ENTRY(kernel_start)so the entry symbol is clear.. = 0x200000;so all addresses are based at 0x200000.- A section such as
.text.kernel_entrywithKEEP(*(.text.kernel_entry))so the entry routine is first. - Then
.text,.rodata,.data,.bssas needed.
The kernel does not run from ELF; it runs from the raw binary. So the linker script defines where the code will sit in memory when the bootloader has copied the payload to 0x200000. Use objcopy -O binary kernel.elf kernel.bin to produce the payload. That binary is what gets packed with the NKRN header.
Entry Point (Assembly)
The bootloader jumps to the kernel with x0 = address of boot_info_t (0x1000). Your first instruction must preserve x0 (or pass it along) and set up a stack before calling C code.
Example pattern (from test_kernel/boot/kernel_start.S):
- Save x0 in a callee-saved register (e.g. x19).
- Set the stack pointer to a region below the kernel image (e.g. 0x1F0000 so the stack does not overlap the kernel at 0x200000).
- Move the saved value back into x0 and branch to the C entry (e.g.
kernel_main). The C function will receive the boot_info pointer as its first argument.
The kernel runs at EL1 with MMU and caches off. It is responsible for initialising any hardware it needs (e.g. UART). The bootloader does not guarantee peripheral state after the jump.
Compiler and Linker Flags
Use the same kind of flags as the Neutron build: -ffreestanding -fno-builtin -nostdlib -nostdinc, and for the linker -nostdlib -T your_kernel.ld. You can share the same cross-compiler (e.g. aarch64-linux-gnu-gcc, aarch64-linux-gnu-ld). Do not link the C library or startup files.
Output
Build steps:
- Assemble and compile sources.
- Link with your kernel linker script to produce
kernel.elf. - Run
objcopy -O binary kernel.elf kernel_raw.bin. - Run
pack_kernel.py(or equivalent) to prepend the NKRN header and produce the image that will be placed on the SD card as ATOM.BIN (see Packing and Deploying).
Using a Different Load Address
If you want the kernel at a different address (e.g. 0x300000):
- Change the linker script so the image is linked for that address.
- When packing, pass
--load 0x300000and--entry 0x300000(or your entry symbol address) topack_kernel.py. - Ensure the chosen region does not overlap the bootloader (0x80000), staging (0x100000), or boot_info (0x1000). Stay within RAM (e.g. below 0x3F000000 on this platform).
The staging area at 0x100000 is overwritten when the bootloader reads ATOM.BIN. Do not link the kernel to run from 0x100000; the payload is copied out of that region to the load address.
For the exact NKRN header format and what the bootloader checks, see NKRN Image Format. For the structure passed in x0, see Boot Info Structure.