The Neutron bootloader loads a packed kernel image: a 64-byte NKRN header followed by the raw AArch64 payload. This page describes how to produce that image with the provided script and how to put it on the SD card for the bootloader to find.

pack_kernel.py

The project includes a Python 3 script, pack_kernel.py, in the project root. It reads a raw kernel binary, computes the CRC32 of the payload, and writes a file that starts with the NKRN header and then the payload. When using the SD/FAT32 boot path, the bootloader expects that packed file to exist in the root of the first FAT32 partition under the name configured by kernel_filename in build.cfg.

Usage

python3 pack_kernel.py <raw_binary> [options]

Positional argument: Path to the raw kernel binary (e.g. the output of objcopy -O binary kernel.elf kernel.bin).

Options:

OptionDefaultDescription
-o, --outputkernel.binOutput file path.
-n, --nameNeutron KernelName string in the header (up to 39 bytes).
--load0x200000Load address (hex): where the bootloader will copy the payload.
--entry0x200000Entry address (hex): where the bootloader will jump.
--version-major1Major version number. When building via Makefile, this comes from kernel_version in build.cfg.
--version-minor0Minor version number. When building via Makefile, this comes from kernel_version in build.cfg.
--verify(flag)Verify an existing packed image instead of packing.

When building through the Makefile, the version numbers are automatically extracted from the kernel_version setting in build.cfg (e.g., kernel_version = "v1.0" becomes --version-major 1 --version-minor 0). You only need to specify these options manually when running pack_kernel.py directly from the command line.

Examples

Pack the default test kernel output with default load/entry:

python3 pack_kernel.py build/kernel_raw.bin -o bin/atom.bin -n "Neutron Test Kernel"

Pack with custom name and version:

python3 pack_kernel.py mykernel.bin -o myimg.bin -n "MyOS 0.1" --version-major 0 --version-minor 1

Verify an existing packed file:

python3 pack_kernel.py bin/atom.bin --verify

CRC32

The script uses Python’s zlib.crc32 with the same convention as the bootloader (IEEE 802.3 polynomial, result masked to 32 bits). The header stores the CRC of the payload only (bytes after the 64-byte header). The bootloader recomputes this and refuses to boot if it does not match.

The Makefile runs pack_kernel.py automatically when you build the kernel target. It passes the output of the kernel build (build/kernel_raw.bin) and writes the packed test kernel to bin/atom.bin.

To use your own kernel image with the build scripts, pass --kernel <path> to neutron.sh / neutron.ps1:

  • If the image is already packed (has the NKRN header): ./neutron.sh build all --kernel /path/to/prebuilt.bin
  • If it is a raw binary (no header yet): ./neutron.sh build all --kernel /path/to/raw.bin --pack

The scripts copy the file into bin/custom_kernel.bin inside the bind-mounted project directory and pass K_BIN=bin/custom_kernel.bin (and PACK=1 when --pack is given) to Make automatically. If you invoke Make directly, use K_BIN=/path/to/prebuilt.bin (packed) or K_BIN=/path/to/raw.bin PACK=1 (raw).

SD Card Layout

When embed-kernel = false, the bootloader:

  1. Initialises the SD card.
  2. Mounts the first partition as FAT32 (via MBR).
  3. Looks for the file named by kernel_filename in build.cfg (8.3 name recommended).

So the packed image must appear on the SD card in the FAT32 root directory with that filename. The Makefile’s sd-image target creates a 64 MiB disk image and copies K_BIN into the root as kernel_filename using mcopy. That image is used by QEMU as the SD card (-drive file=bin/sd.img,if=sd,format=raw).

When embed-kernel = true, the packed kernel is embedded into kernel8.img and the bootloader does not access the SD card.

Deploying to Real Hardware

For a real Raspberry Pi:

  1. Prepare an SD card with the official boot files (e.g. bootcode.bin, start.elf, fixup.dat) and your kernel8.img (the Neutron bootloader).
  2. If you are using the SD/FAT32 path (embed-kernel = false), ensure the first partition is FAT32 and contains the packed kernel in the root with the filename from kernel_filename (e.g. ATOM.BIN or CUSTOM.BIN). You can copy the contents of the first partition of bin/sd.img onto the card, or format the partition as FAT32 and copy K_BIN (or your packed image) onto it with that name.
  3. Boot the board. The GPU loads kernel8.img at 0x80000; Neutron runs and either loads the packed kernel from SD (SD path) or uses the embedded kernel (embedded path), validates the NKRN header, copies the payload to the load address, and jumps to the kernel.

If you use a different SD filename (e.g. MYOS.BIN), set kernel_filename = "MYOS.BIN" in build.cfg and rebuild. You do not need to edit the C source.

Summary

  1. Build your kernel to a raw binary (link at load address, e.g. 0x200000; objcopy -O binary).
  2. Run pack_kernel.py raw_binary.bin -o atom.bin -n "Your Name" (and adjust —load/—entry if not 0x200000).
  3. If using SD/FAT32 boot, place the output on the FAT32 root with the filename from kernel_filename in build.cfg. If using embedded boot, ensure your packed kernel is built into kernel8.img (set embed-kernel = true and rebuild).
  4. Boot with Neutron as kernel8.img; it will load and run your kernel.

For the header format and validation rules, see NKRN Image Format. For building the kernel binary, see Building a Kernel.