Before jumping to the kernel, the Neutron bootloader writes a small structure at a fixed physical address and passes a pointer to it in register x0. The kernel can use this to discover load address, entry address, kernel size, board revision, and ARM memory size without hardcoding addresses.

Location and Magic

Address: Physical address 0x1000 (BOOT_INFO_ADDR in include/bootloader.h).

Magic: The first field is magic and must equal 0xB007B007 (BOOT_INFO_MAGIC). If the kernel reads a pointer from x0 and finds a different magic value, it can treat the boot as not coming from Neutron (e.g. direct load by GPU or another bootloader).

Structure Layout (boot_info_t)

The layout is defined in include/bootloader.h. The kernel must use the same layout (same offsets and types) when reading the structure.

FieldTypeDescription
magicuint32_tMust be 0xB007B007.
board_revisionuint32_tBoard revision from VideoCore mailbox. 0 on QEMU if mailbox does not provide it.
arm_mem_sizeuint32_tARM-accessible RAM size in bytes. From mailbox.
kernel_load_addruint32_tAddress where the payload was copied (same as NKRN load_addr).
kernel_entry_addruint32_tEntry address the bootloader jumped to (same as NKRN entry_addr).
kernel_sizeuint32_tPayload size in bytes (same as NKRN image_size).
bootloader_versionchar[16]Null-terminated string (e.g. “Neutron-1.0”).

The structure is packed (no padding). Total size is 4 + 4 + 4 + 4 + 4 + 4 + 16 = 40 bytes (plus any padding to alignment if you add fields in a custom build).

How the Kernel Receives It

  • The bootloader calls the kernel entry point with x0 = physical address of this structure (0x1000).
  • The kernel entry (e.g. in assembly) should save x0 (e.g. in a callee-saved register) before setting up the stack, then pass it as the first argument to the C entry (e.g. kernel_main(boot_info_t *info)).
  • In C, the first argument corresponds to x0, so the prototype can be void kernel_main(boot_info_t *info).

Using the Fields

  • magic: Check that it equals BOOT_INFO_MAGIC before trusting other fields.
  • board_revision, arm_mem_size: Use for board-specific or memory-sizing logic. On real hardware these are set from the mailbox; on QEMU they may be zero.
  • kernel_load_addr, kernel_entry_addr, kernel_size: Useful for introspection or for passing to secondary code. Usually the kernel is linked to run at the same address as load_addr.
  • bootloader_version: For logging or compatibility checks.

The test kernel in test_kernel/ defines its own copy of boot_info_t in kernel_main.c so it does not depend on the bootloader headers. For your own kernel you can either include the bootloader header (if you build against the Neutron tree) or duplicate the layout to avoid coupling.

If the kernel is entered without going through Neutron (e.g. loaded directly by the GPU at 0x80000), x0 may not point to a valid boot_info_t. Always check the magic before dereferencing.

See Building a Kernel for how to set up the kernel entry and receive x0.