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:
| Option | Default | Description |
|---|---|---|
-o, --output | kernel.bin | Output file path. |
-n, --name | Neutron Kernel | Name string in the header (up to 39 bytes). |
--load | 0x200000 | Load address (hex): where the bootloader will copy the payload. |
--entry | 0x200000 | Entry address (hex): where the bootloader will jump. |
--version-major | 1 | Major version number. When building via Makefile, this comes from kernel_version in build.cfg. |
--version-minor | 0 | Minor 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_versionsetting inbuild.cfg(e.g.,kernel_version = "v1.0"becomes--version-major 1 --version-minor 0). You only need to specify these options manually when runningpack_kernel.pydirectly 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 1Verify an existing packed file:
python3 pack_kernel.py bin/atom.bin --verifyCRC32
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.pyautomatically when you build thekerneltarget. It passes the output of the kernel build (build/kernel_raw.bin) and writes the packed test kernel tobin/atom.bin.To use your own kernel image with the build scripts, pass
--kernel <path>toneutron.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 --packThe scripts copy the file into
bin/custom_kernel.bininside the bind-mounted project directory and passK_BIN=bin/custom_kernel.bin(andPACK=1when--packis given) to Make automatically. If you invoke Make directly, useK_BIN=/path/to/prebuilt.bin(packed) orK_BIN=/path/to/raw.bin PACK=1(raw).
SD Card Layout
When embed-kernel = false, the bootloader:
- Initialises the SD card.
- Mounts the first partition as FAT32 (via MBR).
- Looks for the file named by
kernel_filenameinbuild.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:
- Prepare an SD card with the official boot files (e.g. bootcode.bin, start.elf, fixup.dat) and your kernel8.img (the Neutron bootloader).
- 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 fromkernel_filename(e.g.ATOM.BINorCUSTOM.BIN). You can copy the contents of the first partition ofbin/sd.imgonto the card, or format the partition as FAT32 and copyK_BIN(or your packed image) onto it with that name. - 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), setkernel_filename = "MYOS.BIN"inbuild.cfgand rebuild. You do not need to edit the C source.
Summary
- Build your kernel to a raw binary (link at load address, e.g. 0x200000; objcopy -O binary).
- Run
pack_kernel.py raw_binary.bin -o atom.bin -n "Your Name"(and adjust —load/—entry if not 0x200000). - If using SD/FAT32 boot, place the output on the FAT32 root with the filename from
kernel_filenameinbuild.cfg. If using embedded boot, ensure your packed kernel is built intokernel8.img(setembed-kernel = trueand rebuild). - 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.