Qemu Emulating Raspberry Pi 4B for Linux Module Development
Timeline
2025-12-31
init
2026-01-14
add buildroot
Environment
Host OS
Using WSL2 Archlinux:
Ubuntu also works, refer to:
Compiler
ARM has released 8 architectures so far: ARMv1, ARMv2, ARMv3, ARMv4, ARMv5, ARMv6, ARMv7, ARMv8.
For processors supporting the ARMv8 instruction set, use -march=armv8-a to compile code. The ARM GNU toolchain can be downloaded from:
For example, the aarch64-linux-gnu-gcc installed via pacman on Archlinux is version 15.1.0 with a flat file layout, which is not suitable for buildroot’s toolchain.
ARM’s official compiler is recommended — it’s portable and can be used after extraction.
This article uses version 11.2 cross-compiler:
Add it to the environment variables:
1 | # ~/.bashrc or ~/.zshrc |
Using aarch64-none-linux-gnu-gcc version 11.2.1:
Building the Kernel
1 | sudo pacman -Syu bc |
Outputs:
arch/arm64/boot/Imagearch/arm64/boot/dts/broadcom/bcm2711-rpi-4-b.dtb
Building the Root Filesystem
busybox
Reference:
Note that this blog uses the aarch64-linux-gnu-gcc compiler installed via pacman on Archlinux. The compiler should ideally match the kernel compiler — here we choose aarch64-none-linux-gnu-gcc.
When copying dynamic library files, copy from the compiler directory:
1 | cp ~/tools/gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/aarch64-none-linux-gnu/libc/lib/ld-linux-aarch64.so.1 ./lib/ |
After compilation, enter the Linux kernel source directory and install drivers:
1 | # Install drivers, copy to rootfs |
buildroot
Archlinux’s default toolchain and the AUR cross-compilation toolchains cannot be used directly because Buildroot copies the toolchain to the working directory during the build process. Therefore, we need a Portable toolchain.
1 | wget https://buildroot.org/downloads/buildroot-2025.11.tar.gz |
Configuration as follows:
1 | Target Options ---> |
Do not enable any kernel options here (default is disabled), as we will compile the kernel ourselves. Also, disable all bootloader options since Qemu boot doesn’t require a bootloader.
External toolchain kernel headers series (4.20.x) refers to the kernel version corresponding to the toolchain. Check
gcc-arm-11.2-2022.02-x86_64-aarch64-none-linux-gnu/aarch64-none-linux-gnu/libc/usr/include/linux/version.h— it should be 4.20.x.
Build:
1 | sudo pacman -Syu unzip cpio rsync |
Enter the Linux kernel directory and install drivers:
1 | # Install drivers, copy to rootfs |
yocto
1 | mkdir -p ~/repository/linux/yocto |
Yocto will not automatically recognize arbitrary external toolchains!
A dedicated layer must provide support for that toolchain (viatcmode-*.incandtoolchain-*.conffiles).
Common support layer:meta-arm→ supports ARM’s official GNU Toolchain.
Distribution configurations available in the Yocto Project / OpenEmbedded build system:
-
poky-master— Poky reference distribution,-masteruses the latest main branch (unreleased, under development). -
oe-nodistro-master— OpenEmbedded no-distribution mode,-masteruses the development mainline. -
oe-nodistro-whinlatter— Based on OpenEmbeddednodistroconfig; uses Yocto 5.3 codename “whinlatter” stable branch; LTS version. -
poky-whinlatter— Poky distribution 5.3 “whinlatter” stable version; includes full default configuration (systemd, RPM/deb packages, default toolchain, etc.); LTS version; Yocto Project’s officially tested and certified reference platform.
“whinlatter” is Yocto Project 5.3’s codename (Yocto started using bird names from version 4.0; 5.3 = Whinchat + Lark → “Whinlatter”).
BitBake build configurations (layers / templates):
-
poky— Standard Poky build config. All packages built from source (unless local cache exists). Safe, reliable, predictable. -
poky-with-sstate— Enables remote shared state (sstate) cache. Attempts to download precompiled intermediate artifacts from Yocto’s official or designated sstate mirror servers, significantly speeding up builds. Strict prerequisites: local network must be very stable with sufficient bandwidth; must be able to access external sstate servers; network interruptions or mirror mismatches may cause build failures or inconsistencies. “Use with caution” is the warning here.
sstate is Yocto’s caching mechanism: it saves task outputs (such as
do_compileresults) so that subsequent builds of the same content can reuse them directly without redoing the work.
Target Machines:
machine/qemux86-64— Emulates x86_64 PC via QEMU virtual machine.machine/qemuarm64— Emulates ARM64 (AArch64) architecture virtual device (e.g., Cortex-A57 based system), also via QEMU.machine/qemuriscv64— Emulates RISC-V 64-bit architecture VM.machine/genericarm64— Reference config for generic ARM64 real hardware (not emulated). Requires flashing to a real ARM64 dev board.machine/genericx86-64— Reference config for generic x86_64 real hardware. Generated image can be written to USB and booted on a real x86_64 PC.
Distribution configuration variants:
-
distro/poky— Standard Poky distribution. Full Linux system: glibc, systemd, package manager (RPM or IPK), common tools. Default uses systemd as init. Suitable for general development. -
distro/poky-altcfg— Poky alternative configuration. Typically used for testing different low-level component combinations (e.g., musl libc instead of glibc, busybox + sysvinit instead of systemd). Less documentation, stability inferior to standard Poky. -
distro/poky-tiny— Minimal Poky for resource-constrained devices. Features: musl libc, busybox + sysvinit (no systemd), removes many non-essential packages. Root filesystem can be as small as 10-20 MB. Suitable for microcontroller-level applications.
1 | # Enter build environment |
Kernel Module
Kernel module example code:
1 |
|
External Module
Using busybox rootfs as an example.
Makefile:
1 | DRIVER_NAME := hello_world |
The rootfs packaging script deploy.sh:
1 |
|
There is an issue on WSL2 Archlinux where neither aarch64-linux-gnu-gcc nor aarch64-none-linux-gnu-gcc produces the “CC [M]” build output. A detailed description of the problem is on Stack Overflow:
This issue does not occur with WSL2 Ubuntu 20.04.
Built-in Module
In drivers/char (using char driver as an example), create a helloworld folder, place the driver source inside, then create a Kconfig file:
1 | config HELLO_WORLD |
Modify the parent Kconfig:
1 | emacs ../Kconfig |
Create a Makefile in the driver source directory:
1 | obj-$(CONFIG_HELLO_WORLD) += helloworld.o |
Then add to the parent Makefile:
1 | emacs ../Makefile |
Then rebuild the kernel.
Running in Qemu
Qemu version:
1 | $ sudo pacman -Syu qemu-system-aarch64 |
busybox Root Filesystem
For busybox, we compile it into initramfs.cpio.gz and pass it as the initrd parameter:
1 | qemu-system-aarch64 \ |
Press command + a, then x to exit Qemu.
buildroot Root Filesystem
For buildroot, we use an SD card to mount the filesystem:
1 | $ qemu-system-aarch64 \ |
Press command + a, then x to exit Qemu.
yocto Root Filesystem
Using initramfs.cpio.gz or SD card to mount the filesystem:
1 | $ qemu-system-aarch64 \ |


