Linux Kernel Image and Modules
- Introduction
- Required Host Packages
- Setting the Environment
- Getting the Sources
- Configuring the Linux Kernel
- Building the Linux Kernel Image
- Building the Linux Kernel Modules
- Generating a FIT Image
Introduction
The Linux Kernel is the core component of a Linux based runtime and provides multiple functionalities:
- device drivers
- supported filesystem
- multitasking capabilities
- multicore processing
- etc.
It's composed by two main blocks:
- A monolithic Linux Kernel Image that contains the main functionality and drivers that must be loaded at any time.
- An optional set of external Linux Kernel Modules that are located in the root filesystem and loaded only when needed.
In this section, we will build the Linux Kernel components for booting a complete Linux based runtime in the Zynq UltraScale+ MPSoC.
Required Host Packages
Before performing the exercises, some packages need to be installed in the development host Linux Operating System. For Debian/Ubuntu Debian distributions, these packages can be installed via the package manager with these commands:
sudo apt-get install build-essential
sudo apt-get install flex
sudo apt-get install bison
sudo apt-get install libncurses-dev
sudo apt-get install libssl-dev
Setting the Environment
Load the Vitis environment so that the cross-toolchain tools included in the Xilinx toolchain are available:
source /tools/Xilinx/Vitis/2019.2/settings64.sh
Then export the CROSS_COMPILE and ARCH environment variables for the aarch64 APU in MPSoC:
export CROSS_COMPILE=aarch64-linux-gnu-
export ARCH=arm64
NOTE: AArch64 is the 64-bit state introduced in the ARMv8-A architecture. This is the reason why, in the GNU world, the ARM 64-bit ISA is denoted as aarch64 and this was the value for the ARCH environment variable in all of the previous steps. In this case, the Linux kernel community chose to call their port of the Linux Kernel to this architecture arm64 rather than aarch64.
Finally, for an easier path handling, we move into the linux directory we created in previous steps:
cd ~/soc-course/linux/
Getting the Sources
As a first step, we need to get the Linux Kernel sources from the Xilinx GitHub repository and checkout the version that matches our Xilinx toolchain:
git clone https://github.com/Xilinx/linux-xlnx.git
cd linux-xlnx
git checkout xilinx-v2019.2.01
NOTE: there is not a v2019.2 tag in the GitHub repository, this has been superseded by the corrected v2019.2.01 one.
Configuring the Linux Kernel
The Linux Kernel configuration files for ARM64 in the Xilinx release are located in this folder:
./arch/arm64/configs/
If we take a look to this folder, we will see that it only includes:
- defconfig: default configuration for generic ARM64 machines.
- xilinx_versal_defconfig: configuration for the new Xilinx Versal MPSoC.
- xilinx_zynqmp_defconfig: configuration for the Xilinx Zynq UltraScale+ MPSoC.
The reason is that, in opposition to the U-Boot case, the Kernel configuration is totally generic for a given MPSoC family and the Device Tree is in charge of further System-on-Chip or board level customizations.
For this reason, in order to configure the Linux Kernel with the generic features to support a Zynq UltraScale+ MPSoC based platform, we just need to execute this command:
make xilinx_zynqmp_defconfig
If we need to perform some customization in the Linux Kernel image, we can access to the ncurses based graphical configuration menu:
make menuconfig
After doing this, the terminal will become a (quite) friendly graphical interface for the Linux Kernel configuration:
This is very useful to choose which extra features do we want into the Kernel and which drivers will be included in the monolithic Image or supplied as external modules.
As we do not need to perform any customization in our examples, we can just Exit the interface.
Building the Linux Kernel Image
Once we have configured the Linux sources, we can build the Linux Kernel Image itself by using the make command:
make
NOTE: if you want to speed-up your build, replace the standard make command with the one that makes use of multi-core hosts for parallel compilation. e.g. if your host has 4 cores or threads:
make -j4
Once this process ends, the uncompressed Linux Kernel image or Image is created in the following path (we will use this):
./arch/arm64/boot/Image
In addition, the compressed Linux Kernel image or Image.gz is created in the following path (we won't use this):
./arch/arm64/boot/Image.gz
Building the Linux Kernel Modules
Now, we need to build the Linux Kernel modules that we need to deploy into the root filesystem:
make modules
NOTE: After Linux Kernel 2.6 make performs make modules too, so this previous step is included just as a general case.
Then, we create a dummy root filesystem directory to temporary host the kernel modules, e.g.:
mkdir ~/soc-course/linux/kernel_modules
Once done, we can install the modules in that directory so that we can use them later:
make INSTALL_MOD_PATH=~/soc-course/linux/kernel_modules modules_install
This command will create the a /lib/modules/<kernel_version> folder that contains the Linux Kernel module tree, so that it can be easily copied to an already existing root filesystem.
Inside this folder, we can find the Userspace I/O (UIO) drivers that we will need to use to control custom peripherals in the Programmable Logic.
Generating a FIT Image
The FIT (Flattened Image Tree) format allows for more flexibility in handling images of various types to be used by U-Boot. By using this format, the U-Boot mkimage tool is able to create bootable single binary blobs containing:
- The Linux Kernel Image
- The Device Tree Blob
- Optional RAM-disk Root Filesystems
NOTE: the Yocto Project based Xilinx PetaLinux tool generates by default images containing the Linux Kernel Image, the Device Tree Blob and the generated Root Filesystem.
As an example, we will integrate the Linux Kernel Image we have just built with the Device Tree Blob we previously generated.
We move to the linux folder we use as the container for all of the elements of the runtime:
cd ~/soc-course/linux
There, we create a FIT file named fit_image.its with our preferred text editor, e.g.:
vi fit_image.its
Now, we copy this device tree in fit_image.its with the data elements we want to include in the FIT image:
/dts-v1/;
/ {
description = "U-Boot fitImage for MPSoC Linux Kernel";
#address-cells = <1>;
images {
kernel@0 {
description = "Linux Kernel";
data = /incbin/("linux-xlnx/arch/arm64/boot/Image");
type = "kernel";
arch = "arm64";
os = "linux";
compression = "none";
load = <0x80000>;
entry = <0x80000>;
hash@1 {
algo = "sha1";
};
};
fdt@0 {
description = "Flattened Device Tree blob";
data = /incbin/("my_dts/system.dtb");
type = "flat_dt";
arch = "arm64";
compression = "none";
hash@1 {
algo = "sha1";
};
};
};
configurations {
default = "conf@1";
conf@1 {
description = "Boot Linux kernel with FDT blob";
kernel = "kernel@0";
fdt = "fdt@0";
hash@1 {
algo = "sha1";
};
};
};
};
Now, we will generate the FIT Image by using mkimage. We will call it image.ub to follow the convention used in Xilinx Linux tools:
mkimage -f fit_image.its image.ub
Once this is done, we can use the generated image.ub to boot Linux from U-Boot.