Marcio Cunha

How to compile custom Linux kernels for RISC-V hardware

Learn step by step how to cross-compile and optimize toolchains to build lean Linux kernels tailored for RISC-V development boards.

Marcio Cunha•5 min
Also available in:PortuguêsEspañol
Summary
  • The RISC-V architecture relies on an open instruction set allowing deep and flexible hardware customizations.
  • Cross-compilation overcomes performance bottlenecks by building target code using powerful workstation hardware.
  • Optimizing compilation flags like O3 and march reduces kernel footprint and accelerates device boot times.
  • Using dedicated toolchains ensures vector and floating-point extensions are correctly leveraged by the system.
  • Validating generated images with proper debugging tools prevents catastrophic failures during initial boots.

Why compile your own kernel for RISC-V?

The RISC-V ecosystem has completely transformed the semiconductor industry by offering an open instruction set architecture, allowing anyone to design chips without paying proprietary licensing royalties. However, when deploying development boards or servers based on this technology for real-world workloads, the generic kernels provided by manufacturers rarely deliver optimal performance. Compiling a custom tailored Linux kernel means you strip away unnecessary drivers, enable specific security features, and ensure the operating system squeezes every drop of processing power from the silicon.

In practice, this prevents limited RAM and processing power from being wasted on subsystems that your test board or server will never use. Furthermore, mastering this process grants you the autonomy to integrate experimental patches and recent open-source community fixes even before they reach traditional distribution repositories. If you have ever dealt with challenges related to compiling native binaries in your homelab, you already know that fine-grained control over the development environment is the ultimate key to operational success.

Understanding the challenges of cross-compilation

Unlike traditional architectures where the computer compiling the code is the exact same one executing it, in the RISC-V world we often rely on a robust x86_64 workstation to generate packages destined for a tiny embedded board. This process is called cross-compiling. The primary technical challenge here lies in the accurate translation of system calls, memory alignment, and binary compatibility across completely distinct architectures. If the translation tool—the toolchain—is not perfectly calibrated, the result will be a corrupted kernel that fails to initialize even at the serial console.

Another critical point involves optional RISC-V extensions, known as Instruction Set Architecture (ISA) extensions. While a basic CPU might only support fundamental integer operations, more advanced boards include vector, cryptographic, or double-precision floating-point extensions. If your compiler does not know precisely which extensions are present in the physical silicon, it will generate generic instructions that fail to utilize dedicated hardware accelerators, making your server considerably slower than it could be.

Configuring the Toolchain and environment variables

To begin the process in practice, we need to prepare the cross-compilation environment by installing the GCC compiler optimized for RISC-V and essential source code manipulation utilities. The first step involves exporting environment variables that instruct the build system on the target architecture, the binary prefix, and the working directory where artifacts will be safely stored.

Run the commands below in your main workstation terminal to configure the base environment:

export ARCH=riscv
export CROSS_COMPILE=riscv64-unknown-linux-gnu-
export INSTALL_MOD_PATH=/mnt/target-rootfs
mkdir -p /opt/riscv-kernels

These variables ensure that the Linux kernel source code knows it must invoke the 64-bit cross-compiler rather than attempting to compile for your host machine architecture. Any slip-up at this stage will result in cryptic linking errors and incompatible executable files.

Tuning the .config file for maximum performance

The beating heart of any custom kernel is its configuration file, commonly referred to as .config. Loading a generic default configuration is easy, but it leaves the system bloated. To trim down the kernel, we use interactive utilities like menuconfig, where we can disable drivers for non-existent graphics cards, obsolete networking protocols, and file systems that won't be used in your server or embedded environments.

Run the command below to open the interactive configuration interface and refine your kernel options:

make ARCH=riscv defconfig
make ARCH=riscv menuconfig

Inside menuconfig, carefully navigate through device driver categories, networking support, and power management options. Uncheck everything your hardware does not physically possess. A leaner kernel not only occupies less space in flash memory or eMMC storage, but it also boots significantly faster, which is crucial in high-availability scenarios.

Practical tip: Pay close attention to compiler optimization flags configured in the main Makefile or via extra variables. Size-optimized flags (like -Os) can be excellent for devices with severe instruction cache constraints, while pure performance tuning (-O3 with specific microarchitecture flags) makes all the difference on high-performance development boards.

Compiling and generating boot artifacts

With the configuration file properly tuned and stripped of unnecessary bloat, we are ready to trigger the compilation process itself. Since compiling a complete kernel demands substantial processing power, it is highly recommended to utilize multiple CPU cores on your development machine by appending the -j flag followed by the number of threads available on your processor.

Run the following commands to compile the kernel image, modules, and Device Tree Blobs:

make ARCH=riscv -j$(nproc) Image
make ARCH=riscv -j$(nproc) modules
make ARCH=riscv -j$(nproc) dtbs

The resulting file named Image will be the uncompressed RISC-V kernel image, while the Device Tree files (.dtb) detail the buses, memory addresses, and peripherals connected to the specific board you are using. Without the correct Device Tree matching your exact hardware, the kernel simply will not be able to map physical components during bootup.

Validating the kernel and flashing the development board

The final step before deploying the system into production involves copying the compiled modules to the board's root filesystem and transferring the kernel image to the boot partition or microSD card. It is crucial to ensure that paths are correct to prevent the operating system from booting without access to essential network or storage drivers.

Run the commands below to install modules to the target directory and prepare boot files:

sudo make ARCH=riscv INSTALL_MOD_PATH=/media/user/rootfs modules_install
cp arch/riscv/boot/Image /media/user/boot/
cp arch/riscv/boot/dts/vendor/*.dtb /media/user/boot/

After completing file transfers, safely unmount the storage media, insert it into your RISC-V board, and connect the UART serial adapter to monitor initialization messages. Watching the boot log rise through the serial port is the best way to confirm that all your toolchain optimizations and driver trims achieved the desired effect. To complement this implementation in production environments, see our related technical analyses on Voltage drop in power supply? How to find bad parts on bench and How to configure WebRTC to overcome CGNAT on mobile.

Final Thoughts

Compiling custom Linux kernels for the RISC-V architecture requires patience, attention to toolchain details, and a solid understanding of the physical hardware characteristics at hand. By mastering cross-compilation and fine-tuning optimization flags, you stop depending on generic manufacturer-provided images and gain full control over your operating system lifecycle. This level of engineering opens doors to creating highly stable, secure, and optimized solutions for the future of open computing.