Rust Enabled Linux Kernel On A BeagleBone Black

Published

Overview

Rust is becoming an increasingly popular language for development in embedded systems. Its greatest appeal over traditional system languages such as C is that memory management is inherently built into the language (and compiler) itself. Whereas developers have to take extra steps when managing memory in C, such caution is mitigated when developing in Rust. Additionally, incorporation of Rust into the Linux Kernel is imminent.

In this blog post, we’ll take a step-by-step look at installing a Linux kernel with Rust support on a BeagleBone Black (https://beagleboard.org/black). Choosing the BeagleBone Black as our target platform has two advantages. First, it is relatively inexpensive. Second, it is an embedded system platform that is generally supported out-of-the box in the upstream Linux kernel and BSP frameworks, such as The Yocto Project/OpenEmbedded.

Initial Setup

First, we need a baseline BeagleBone Black system running, containing the u-boot bootloader, the Linux kernel, and a root filesystem. This can be achieved by using The Yocto Project/OpenEmbedded. Let’s first create a set of directories to contain the source files and build artifacts of our initial BSP build, and fetch the latest “poky” reference BSP from The Yocto Project, which corresponds to the “kirkstone” branch:

$> mkdir -p yocto/sources
$> cd yocto/sources
$> git clone --branch kirkstone git://git.yoctoproject.org/poky

Next, we can use a Docker container that has the necessary dependencies for The Yocto Project to build our BSP for the BeagleBone Black. To accomplish this, we can clone the relevant repository to a separate location on our build machine:

$> cd ~/
$> git clone https://github.com/mabembedded/dockerfiles.git

Next, we can copy the “Dockerfile.yocto” file to “Dockerfile” and build the container:

$> cd dockerfiles
$> cp Dockerfile.yocto Dockerfile
$> docker build -t yocto-builder .

Then, we can run our container and “mount” the “yocto” directory that we created earlier inside the container:

$> docker run -v/home/mbilloo/rust/yocto:/home/dev/bsp:z -i -t yocto-builder

Once we’re inside the Docker container, we can configure our environment:

dev@ca262579f4f4> cd bsp/
dev@ca262579f4f4> source sources/poky/oe-init-build-env

We now need to configure The Yocto Project BSP builder so that it creates a BSP that we can deploy on the BeagleBoneBlack. This can be accomplished by uncommenting the following line in build/conf/local.conf; we can also add a configuration option that can significantly reduce the amount of disk space occupied by the BSP:

MACHINE ?= "beaglebone-yocto"
.
.
.
INHERIT += “rm_work”

Then, we can create a minimal build that will allow our BeagleBone Black to boot to a console interface (Note: This process may take a while depending on the resources of your build machine):

dev@ca262579f4f4> bitbake core-image-minimal

Once the build completes, we can exit the Docker container, navigate to the location of the final build artifact, and flash it to a micro SD card:

dev@ca262579f4f4> exit
$> cd /home/mbilloo/rust/yoct/build/tmp/deploy/images/beaglebone-yocto/
$> sudo dd if=core-image-minimal-beaglebone-yocto.wic of=/dev/sdc status=progress

Once the micro SD card is flashed, we can connect a console cable to J1 of the BeagleBone Black, apply power, and wait for it to complete booting. Once it completes booting, we can login (with username root and no password), and check the version of the kernel:

root@beaglebone-yocto:~# uname -a
Linux beaglebone-yocto 5.15.36-yocto-standard #1 PREEMPT Thu Apr 28 13:26:58 UTC 2022 armv7l GNU/Linux

Building and Installing a Recent Upstream Linux Kernel

Now that we have a baseline system up and running, we notice that the version of the Linux kernel on our system is 5.15, which is old (as of this writing, the latest release version is 5.19). However, to compile the latest kernel, we’ll need a cross-compiler that targets our processor and board. We can retrieve the toolchain from The Yocto Project/OpenEmbedded. First, let’s go back to the Docker container and re-configure our environment:

$> docker run -v/home/mbilloo/rust/yocto:/home/dev/bsp:z -i -t yocto-builder
dev@ca262579f4f4> cd bsp/
dev@ca262579f4f4> source sources/poky/oe-init-build-env

Then we can build the SDK, which contains the cross-compiler that we’ll need to build the Linux kernel:

dev@ca262579f4f4> bitbake -c populate_sdk core-image-minimal

There should now be a shell script that we can execute to extract our cross-compiler (and the rest of the SDK) to a directory of our choosing. We can exit out of the Docker container, create a directory to store the SDK, and copy the shell script to that directory:

dev@ca262579f4f4 $> exit
$> mkdir ~/rust/sdk
$> cd ~/rust/yocto/build/tmp/deploy/sdk/
$> cp poky-glibc-x86_64-core-image-minimal-cortexa8hf-neon-beaglebone-yocto-toolchain-4.0.2.sh ~/rust/sdk

Once the SDK has been installed we can source the environment file that will configure the PATH to the cross compiler and other necessary environment variables:

$> cd ~/rust/sdk
$> source environment-setup-cortexa8hf-neon-poky-linux-gnueabi

Next, we can clone the upstream Linux kernel and check out the 5.19 tag:

$> cd ~/rust/
$> git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
$> git checkout tags/v5.19

Before compiling the Linux kernel, we need to instruct it on the features that we wish to enable:

$> cd ~/rust/linux
$> ARCH=arm make omap2plus_defconfig

Then, we can build the Linux kernel itself:

$> ARCH=arm CROSS_COMPILE=arm-poky-linux-gnueabi- make

We can insert our micro SD card to our build PC and copy over the kernel binary that was just built:

$> cp arch/arm/boot/zImage /media/mbilloo/boot/
$> sync
$> sync

Finally, we can eject the SD card from our PC and insert it into the BeagleBone Black. After the BeagleBone Black has successfully booted, we can confirm that it’s running the kernel that we just built:

sroot@beaglebone-yocto:~# uname -a
Linux beaglebone-yocto 5.19.0-rc7 #1 SMP Sat Jul 23 12:12:19 EDT 2022 armv7l GNU/Linux

Docker Detour

We can use a Docker container that satisfies the necessary dependencies for building the Linux kernel with Rust support. First, we can go back to the directory that contains the repository with the Dockerfiles that we cloned before, rename the Dockerfile for Rust, and create the Docker container to allow us to build our Rust kernel:

$> cd ~/dockerfiles
$> cp Dockerfile.rust Dockerfile
$> docker build -i -t rust-builder .

Once the Docker container is built, we will have to install the toolchain inside the container. Although Rust doesn’t technically require a cross-compiler, since Rust uses Clang/LLVM, there are still some elements of the kernel that require the assembler.  We will need to give the Docker container access to the shell script with the toolchain that was just created a few steps ago along with the Rust kernel that we just cloned :

dev@3dc29d59c7e7 $> source sdk/environment-setup-cortexa8hf-neon-poky-linux-gnueabi

Then, we can go ahead and configure the Rust kernel. As previously, we first need to instruct the Linux kernel which peripherals and features to enable:

Building and Installing a Rust Linux Kernel

dev@3dc29d59c7e7 $> cd linux
dev@3dc29d59c7e7 $> ARCH=arm make omap2plus_defconfig

Then, we need to manually enable support for Rust in the kernel, by navigating the kernel’s configuration interface. This can be accomplished by executing the following command in our Docker container:

dev@3dc29d59c7e7 $> ARCH=arm make menuconfig

After executing the above command, we are greeted with the following interface:

Rust Linux Kernel KConfig

We can search for the option to enable Rust by entering the “/” key, which opens the search menu:

Linux Kernel Rust Search

Then, if we simply type “Rust” and hit the Enter key, we see the following:

Linux Kernel Rust Results

We would like to enable the first option, which is disabled by default. We can’t enable this option directly because one of the dependencies, called “MODVERSIONS”, must be disabled (which we will discuss in more detail in a separate blog post) to enable Rust support. To disable the MODVERSIONS option, we can search it by following the same instructions as earlier. We are presented with the following results:

Linux Kernel Rust Modversions

If  we enter the “1” key, we are presented with the configuration option to enable or disable MODVERSIONS. We can simply hit the spacebar to deselect that option:

Linux Kernel Modversion

Now, we need to search again for “Rust”, and we are presented with the appropriate results. We can hit the “1” key, and are presented with the following:

Linux Kernel Enable Rust

We can hit the spacebar to select the option and can exit out of this interface by continuing to select the “Exit” option at the bottom. We just need to make sure that we save our new configuration when we are asked to do so. Then, we can execute the following command to compile the kernel:

$> ARCH=arm make LLVM=1 LLVM_IAS=0 CROSS_COMPILE=arm-poky-linux-gnueabi-

Before the compilation process begins, will be presented with a new configuration option that we need to manually select (since it’s not defined in the BeagleBone Black configuration file). We can select any of the options (I simply selected option 3):

*                                                                                                        
* Restart config...                                                                                      
*                                                                                                        
*                                                  
* Memory initialization                                                                                  
*                                                                                                        
Initialize kernel stack variables at function entry                                                      
> 1. no automatic stack variable initialization (weakest) (INIT_STACK_NONE)                              
  2. pattern-init everything (strongest) (INIT_STACK_ALL_PATTERN) (NEW)                                  
  3. zero-init everything (strongest and safest) (INIT_STACK_ALL_ZERO) (NEW)                             
choice[1-3?]: 3

After the Rust kernel successfully compiles, we can transfer it over to the SD card, insert the SD card into the BeagleBone Black, apply power, and wait for a login prompt. After we have logged in, we can type “uname -a” on the command line, and we are greeted with the following:

root@beaglebone-yocto:~# uname -a
Linux beaglebone-yocto 5.19.0-159241-g459035ab65c0-dirty #1 SMP Sat Sep 24 13:33:37 UTC 2022 armv7l GNU/Linux

And we have a Linux Rust kernel running on a BeagleBone Black!