Manually Building the Linux Image
This page builds the YuzukiNeko F101-S3 SPI NOR boot image (RV32) from source, step by step: SyterKit, OpenSBI, the Linux kernel and a BusyBox root filesystem, then assembles them into a 16 MiB .img following the Flash layout, and flashes it in sections or in full with xfel. For the 64-bit (RV64I) changes, see Manually Building the RV64I Image; for a single command that produces an image, see Linux + Buildroot.
The boot chain is the same as the Buildroot version:
BROM → SyterKit → OpenSBI → Linux → BusyBoxPreparation
Section titled “Preparation”The host needs gcc, make, bison, flex, bc, git, squashfs-tools, and the other common host build dependencies for the kernel and SyterKit.
All commands below run in the same working directory ~/f101-linux; where a source directory must be entered, the cd is written out, and the working directory is returned to afterwards:
mkdir -p ~/f101-linux/build && cd ~/f101-linuxToolchain
Section titled “Toolchain”Two Xuantie toolchains are needed:
| Toolchain | Purpose | Prefix |
|---|---|---|
Xuantie-900-gcc-elf-newlib-x86_64-V3.2.0 | SyterKit | riscv64-unknown-elf- |
Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0 | OpenSBI, kernel, BusyBox | riscv64-unknown-linux-gnu- |
Both toolchains are downloaded from T-Head OCC, and both are version V3.2.0-20250627: elf-newlib is about 235 MB, glibc about 510 MB.
curl -fLO 'https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1751370399722/Xuantie-900-gcc-elf-newlib-x86_64-V3.2.0-20250627.tar.gz'curl -fLO 'https://occ-oss-prod.oss-cn-hangzhou.aliyuncs.com/resource//1755679709552/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0-20250627.tar.gz'sha256sum Xuantie-900-gcc-*.tar.gzThe SHA-256 of the two archives should be:
80c174c6445f7565bc082d328045021862a63beddfad8c393c534e2d9523dc3b Xuantie-900-gcc-elf-newlib-x86_64-V3.2.0-20250627.tar.gza5a6a5e517a738baffed091efce984ffa215f78bb11e296d6175bf5896778fdd Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0-20250627.tar.gzOnce the digests verify, extract them into toolchain/ under the working directory; no root privileges are needed, and later commands refer to this path directly ($HOME is your home directory):
mkdir -p toolchaintar -xzf Xuantie-900-gcc-elf-newlib-x86_64-V3.2.0-20250627.tar.gz -C toolchaintar -xzf Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0-20250627.tar.gz -C toolchainSources
Section titled “Sources”git clone https://github.com/YuzukiHD/SyterKit.gitgit clone -b sun252i-f101 https://github.com/YuzukiHD/opensbi.gitgit clone -b sun252i_f101_7.2 https://github.com/YuzukiHD/linux-mainline.gitgit clone https://github.com/YuzukiHD/buildroot-external-yuzukihd.gitcurl -fLO https://busybox.net/downloads/busybox-1.38.0.tar.bz2tar -xjf busybox-1.38.0.tar.bz2BusyBox is downloaded from busybox.net and extracts to busybox-1.38.0/. buildroot-external-yuzukihd is used only to take the kernel config fragment and a few files for the root filesystem; Buildroot is not built.
The versions used when this page was built: SyterKit main (6ef3f27ab), OpenSBI 08cbbe0b, Linux 95cd7466c, BusyBox 1.38.0.
Building SyterKit
Section titled “Building SyterKit”SyterKit is the first-stage bootloader after BROM, and it also reads the devicetree, OpenSBI and the kernel from the SPI NOR. It uses the elf-newlib toolchain, and its output goes into build/ under the working directory:
cd SyterKitmake O=../build/syterkit CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-elf-newlib-x86_64-V3.2.0/bin/riscv64-unknown-elf- yuzukineko_rv32_sram_defconfigmake -j8 O=../build/syterkit CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-elf-newlib-x86_64-V3.2.0/bin/riscv64-unknown-elf- spinor-bootcd ..The artifact is build/syterkit/build/yuzukineko/app_sram/spinor-boot/spinor-boot_spi.bin, 49,152 bytes; the reserved area is 64 KiB (65,536 bytes), so it fits.
Building OpenSBI
Section titled “Building OpenSBI”The OpenSBI repository ships a build.sh with the F101 parameters already written in (sun252i-f101.defconfig, FW_JUMP, FW_TEXT_START=0x40f80000, FW_JUMP_ADDR=0x40000000). --arch rv32 selects 32-bit, and the output goes under build/ in the repository:
cd opensbimkdir -p build./build.sh --arch rv32 $HOME/f101-linux/toolchain/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0/bin/riscv64-unknown-linux-gnu-cd ..mkdir -p build cannot be omitted; the default output directory of build.sh requires its parent directory to already exist.
The artifact is opensbi/build/sun252i-f101/platform/generic/firmware/fw_jump.bin, 266,344 bytes; the reserved area is 512 KiB (524,288 bytes). Run file on fw_jump.elf in the same directory; it should report 32-bit.
Building the kernel
Section titled “Building the kernel”The kernel uses the RV32 defconfig, then merges linux-nor.fragment from the external tree. This fragment enables MTD, OverlayFS and JFFS2, which are needed for a SquashFS root filesystem with a JFFS2 overlay on top.
cd linux-mainlinemake O=../build/kernel-rv32 ARCH=riscv CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0/bin/riscv64-unknown-linux-gnu- sun252i_f101_yuzukineko_defconfigscripts/kconfig/merge_config.sh -m -O ../build/kernel-rv32 ../build/kernel-rv32/.config ../buildroot-external-yuzukihd/board/yuzukihd/yuzukineko/scripts/linux-nor.fragmentmake O=../build/kernel-rv32 ARCH=riscv CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0/bin/riscv64-unknown-linux-gnu- olddefconfigmake -j8 O=../build/kernel-rv32 ARCH=riscv CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0/bin/riscv64-unknown-linux-gnu- Image allwinner/sun252i-f101-yuzukineko.dtbcd ..The artifacts are in build/kernel-rv32/arch/riscv/boot/:
| File | Size (bytes) | Reserved area |
|---|---|---|
Image | 5,178,200 | 6 MiB |
dts/allwinner/sun252i-f101-yuzukineko.dtb | 10,472 | 256 KiB |
The RV32 kernel is determined by the 32-bit options in the kernel configuration; the toolchain does not need to change, and the riscv64-unknown-linux-gnu- prefix is still used.
Building the root filesystem
Section titled “Building the root filesystem”Buildroot is not used here; BusyBox is compiled directly, the necessary directories and files are added, and the result is packed into a SquashFS. BusyBox is statically linked, so no C library is needed in the root filesystem.
BusyBox
Section titled “BusyBox”Four things are changed from the default config: static linking, enabling flash_eraseall, and the architecture-specific compile and link options. pivot_root is already enabled by default, and /sbin/init uses it to switch to the overlay root.
cd busybox-1.38.0mkdir -p ../build/busybox-rv32make O=../build/busybox-rv32 ARCH=riscv CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0/bin/riscv64-unknown-linux-gnu- defconfigsed -i 's/^# CONFIG_STATIC is not set/CONFIG_STATIC=y/' ../build/busybox-rv32/.configsed -i 's/^# CONFIG_FLASH_ERASEALL is not set/CONFIG_FLASH_ERASEALL=y/' ../build/busybox-rv32/.configsed -i 's/^CONFIG_EXTRA_CFLAGS=""/CONFIG_EXTRA_CFLAGS="-march=rv32imafdc -mabi=ilp32d"/' ../build/busybox-rv32/.configsed -i 's/^CONFIG_EXTRA_LDFLAGS=""/CONFIG_EXTRA_LDFLAGS="-march=rv32imafdc -mabi=ilp32d"/' ../build/busybox-rv32/.configmake O=../build/busybox-rv32 ARCH=riscv CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0/bin/riscv64-unknown-linux-gnu- oldconfig < /dev/nullmake -j8 O=../build/busybox-rv32 ARCH=riscv CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0/bin/riscv64-unknown-linux-gnu-mkdir -p ../build/rootfs-rv32make O=../build/busybox-rv32 ARCH=riscv CROSS_COMPILE=$HOME/f101-linux/toolchain/Xuantie-900-gcc-linux-6.6.0-glibc-x86_64-V3.2.0/bin/riscv64-unknown-linux-gnu- CONFIG_PREFIX=../rootfs-rv32 installcd ..file build/busybox-rv32/busybox should report 32-bit, statically linked. The CONFIG_PREFIX of the final install is relative to the output directory, so it is written as ../rootfs-rv32 and installs into build/rootfs-rv32/.
Directories and configuration files
Section titled “Directories and configuration files”The following fills in what is missing in build/rootfs-rv32/. /sbin/init and S40network come from the overlay directory of the external tree. After install, /sbin/init is a symbolic link to BusyBox, so it must be deleted before copying; otherwise the copy overwrites the BusyBox binary itself.
mkdir -p build/rootfs-rv32/{proc,sys,dev,tmp,run,root,etc/init.d,mnt/root,mnt/overlay,var/log}rm build/rootfs-rv32/sbin/initcp buildroot-external-yuzukihd/board/yuzukihd/yuzukineko/overlay/sbin/init build/rootfs-rv32/sbin/initcp buildroot-external-yuzukihd/board/yuzukihd/yuzukineko/overlay/etc/init.d/S40network build/rootfs-rv32/etc/init.d/BusyBox ships flash_eraseall; a simplified stand-in for flash_erase is added:
printf '#!/bin/sh\nexec /usr/sbin/flash_eraseall "$1"\n' > build/rootfs-rv32/usr/sbin/flash_eraseCreate build/rootfs-rv32/etc/inittab:
cat > build/rootfs-rv32/etc/inittab <<'EOF'::sysinit:/bin/mount -t tmpfs tmpfs /tmp::sysinit:/bin/mount -t tmpfs tmpfs /run::sysinit:/etc/init.d/rcSttyS1::respawn:/sbin/getty -L ttyS1 115200 vt100::ctrlaltdel:/sbin/reboot::shutdown:/bin/umount -a -rEOFCreate build/rootfs-rv32/etc/init.d/rcS:
cat > build/rootfs-rv32/etc/init.d/rcS <<'EOF'#!/bin/shfor s in /etc/init.d/S[0-9][0-9]*; do [ -x "$s" ] && "$s" start; doneEOFThe remaining small files, and the executable permissions:
echo 'root::0:0:root:/root:/bin/sh' > build/rootfs-rv32/etc/passwdecho 'root:x:0:' > build/rootfs-rv32/etc/groupecho yuzukineko > build/rootfs-rv32/etc/hostnameecho /dev/ttyS1 > build/rootfs-rv32/etc/securettychmod 755 build/rootfs-rv32/usr/sbin/flash_erase build/rootfs-rv32/etc/init.d/rcS build/rootfs-rv32/etc/init.d/S40networkThe root password is empty, the same as the Buildroot version. The console is UART1 (ttyS1), and getty runs at 115200 baud.
Packaging SquashFS
Section titled “Packaging SquashFS”xz compression is used. The device nodes /dev/console and /dev/null are written into the image directly with -p, so no root privileges are needed:
mksquashfs build/rootfs-rv32 build/rootfs-rv32.squashfs -comp xz -all-root -noappend -no-progress -p "dev/console c 600 0 0 5 1" -p "dev/null c 666 0 0 1 3"ls -l build/rootfs-rv32.squashfsThe artifact is 1,089,536 bytes; the reserved area is 0x6d0000 to 0xf00000 (8,585,216 bytes).
Flash layout
Section titled “Flash layout”The position of each component on the SPI NOR is fixed and the same as in the Buildroot image:
| Component | NOR offset | Reserved size | RAM load address |
|---|---|---|---|
| SyterKit | 0x000000 | 64 KiB | 0x00020000 (loaded into SRAM by BROM) |
| Devicetree | 0x010000 | 256 KiB | 0x40f40000 |
OpenSBI fw_jump | 0x050000 | 512 KiB | 0x40f80000 |
| Linux Image | 0x0d0000 | 6 MiB | 0x40000000 |
| SquashFS root filesystem | 0x6d0000 | up to 0xf00000 (about 8.2 MiB) | mounted from MTD |
| JFFS2 overlay | 0xf00000 | 1 MiB, kept erased | mounted from MTD |
Flashing in sections
Section titled “Flashing in sections”First install xfel as described in FEL download mode and tools, put the board into FEL, and back up the whole flash as described in SPI NOR layout and backup. Flashing in sections only changes the corresponding areas and leaves the overlay partition untouched:
xfel versionxfel spinorxfel spinor write 0x0 build/syterkit/build/yuzukineko/app_sram/spinor-boot/spinor-boot_spi.binxfel spinor write 0x10000 build/kernel-rv32/arch/riscv/boot/dts/allwinner/sun252i-f101-yuzukineko.dtbxfel spinor write 0x50000 opensbi/build/sun252i-f101/platform/generic/firmware/fw_jump.binxfel spinor write 0xd0000 build/kernel-rv32/arch/riscv/boot/Imagexfel spinor write 0x6d0000 build/rootfs-rv32.squashfsAfter writing, read back by the file size and compare, for example the kernel (5178200 is the byte count of Image; if you replaced the kernel, change it to the actual size from ls -l):
xfel spinor read 0xd0000 5178200 Image-readback.bincmp Image-readback.bin build/kernel-rv32/arch/riscv/boot/ImageFinally, reset:
xfel resetPackaging into a full image
Section titled “Packaging into a full image”Flashing in sections is suitable for debugging. To get a single 16 MiB file that can be written in one go, first confirm that no component exceeds the reserved area above (use ls -l to check the sizes), then write each at its offset onto an all-0xFF base:
head -c 16777216 /dev/zero | tr '\000' '\377' > build/yuzukineko-manual-rv32.imgdd if=build/syterkit/build/yuzukineko/app_sram/spinor-boot/spinor-boot_spi.bin of=build/yuzukineko-manual-rv32.img bs=1 seek=0 conv=notrunc status=nonedd if=build/kernel-rv32/arch/riscv/boot/dts/allwinner/sun252i-f101-yuzukineko.dtb of=build/yuzukineko-manual-rv32.img bs=1 seek=$((0x10000)) conv=notrunc status=nonedd if=opensbi/build/sun252i-f101/platform/generic/firmware/fw_jump.bin of=build/yuzukineko-manual-rv32.img bs=1 seek=$((0x50000)) conv=notrunc status=nonedd if=build/kernel-rv32/arch/riscv/boot/Image of=build/yuzukineko-manual-rv32.img bs=1 seek=$((0xd0000)) conv=notrunc status=nonedd if=build/rootfs-rv32.squashfs of=build/yuzukineko-manual-rv32.img bs=1 seek=$((0x6d0000)) conv=notrunc status=nonels -l build/yuzukineko-manual-rv32.imgsha256sum build/yuzukineko-manual-rv32.imgThe file should be exactly 16,777,216 bytes, with everything after 0xf00000 set to 0xFF, that is, an empty overlay partition.
Flashing the whole flash
Section titled “Flashing the whole flash”Writing the whole flash overwrites the entire flash, including the changes on the board in the overlay partition; back up first.
xfel versionxfel spinorxfel spinor write 0x0 build/yuzukineko-manual-rv32.imgxfel spinor read 0x0 0x1000000 readback.imgsha256sum build/yuzukineko-manual-rv32.img readback.imgOnce the two SHA-256 digests match, reset:
xfel resetFor the serial parameters after boot, the login method, and the note that the overlay must be formatted on first boot, see Prebuilt image.