GNU-Linux Rapid Embedded Programming
上QQ阅读APP看书,第一时间看更新

The Kernel and DTS files

The main target of this book is to give several suggestions for rapid programming methods to be used on an embedded GNU/Linux system. However, the main target of every embedded developer is to realize programs to manage peripherals, to monitor or to control devices, and other similar tasks to interact with the real world. So, we mainly need to know the techniques useful to get access to the peripheral's data and settings.

That's why, we need to know how to recompile the kernel and how to configure it.

Recompiling the kernel

Our developer kits are well supported, and in this situation, it is quite rare that we need a complete kernel recompilation. However, knowing how to do this step is quite essential for every embedded developer (it may happen that we need to add some external peripherals or modify the default configuration).

Since we decided to use the Robert C. Nelson repositories, we can still continue using them. However, some words must be spent to clarify some basic commands useful to manage the kernel code even if we use a generic kernel repository.

Referring to the SAMA5D3 Xplained kernel compilation in SAMA5D3 Xplained section in  Chapter 1 , Installing the Developing System, we used the build_kernel.sh script to operate on the sources. However, this is not the standard way to manage the kernel code. In fact, Robert C. Nelson did a very good job, but we want to learn how to manage the kernel even without using his tools! So, let's take a look at what steps build_kernel.sh follows to do its job.

Like every good program, the main procedure is at the end of the file, and there, we will see the following code:

... 
. "${DIR}/version.sh" 
export LINUX_GIT 
 
unset FULL_REBUILD 
#FULL_REBUILD=1 
if [ "${FULL_REBUILD}" ] ; then 
    /bin/sh -e "${DIR}/scripts/git.sh" || { exit 1 ; } 
 
    if [ "${RUN_BISECT}" ] ; then 
        /bin/sh -e "${DIR}/scripts/bisect.sh" || { exit 1 ; } 
    fi 
 
    if [ ! -f "${DIR}/.yakbuild" ] ; then 
        patch_kernel 
    fi 
    copy_defconfig 
fi 
if [ ! "${AUTO_BUILD}" ] ; then 
    make_menuconfig 
fi 
if [  -f "${DIR}/.yakbuild" ] ; then 
    BUILD=$(echo ${kernel_tag} | sed 's/[^-]*//'|| true) 
fi 
make_kernel 
make_modules_pkg 
make_firmware_pkg 
if grep -q dtbs "${DIR}/KERNEL/arch/${KERNEL_ARCH}/Makefile"; then 
    make_dtbs_pkg 
fi 
echo "-----------------------------" 
echo "Script Complete" 
echo "${KERNEL_UTS}" > kernel_version 
echo "eewiki.net: [user@host:~$ export kernel_version=${KERNEL_UTS}]" 
echo "-----------------------------" 

So, the following steps are performed:

  1. FULL_REBUILD : Where the sources are checkout out form the Git repositories and then pached
  2. AUTO_BUILD: Where we configure the kernel
  3. make_kernel: Where we effectively build up the kernel and its components (modules, firmware, and device trees)
  4. make_modules_pkg, make_firmware_pkg, and make_dtbs_pkg: Where we create some packages holding the kernel's modules, the firmware binaries, and the device trees

The first step is not related to kernel compilation since it's needed to get the kernel's sources only.

Tip

At this point, we should notice that in order to avoid download the kernel sources each time we execute the script, we should disable this step by unsetting the FULL_REBUILD variable with the following patch:

    --- a/build_kernel.sh
    +++ b/build_kernel.sh
    @@ -227,8 +227,7 @@ fi
     . "${DIR}/version.sh"
     export LINUX_GIT
    -#unset FULL_REBUILD
    -FULL_REBUILD=1
    +unset FULL_REBUILD
     if [ "${FULL_REBUILD}" ] ; then
     /bin/sh -e "${DIR}/scripts/git.sh" || { exi
    t 1 ; }

Again, the fourth step is just to pack the modules and other kernel's components, and it's not relevant to us. So, the second and third steps are the ones we have to look at carefully:

The make_menuconfig function is defined here:

make_menuconfig () { 
    cd "${DIR}/KERNEL" || exit 
    make ARCH=${KERNEL_ARCH} CROSS_COMPILE="${CC}" menuconfig 
    if [ ! -f "${DIR}/.yakbuild" ] ; then 
        cp -v .config "${DIR}/patches/defconfig" 
    fi 
    cd "${DIR}/" || exit 
} 

This tells to us that the command to execute the kernel configuration menu is as follows:

make ARCH=${KERNEL_ARCH} CROSS_COMPILE="${CC}" menuconfig

Of course, we have to specify the KERNEL_ARCH and CROSS_COMPILE variables, but considering what we did before, it's quite obvious that the command changes to this:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

To test it, we can go into the directory where SAMA5D3 Xplained's kernel sources are placed (directory A5D3/armv7_devel/KERNEL) and then execute the make command:

$ cd A5D3/armv7_devel/KERNEL
$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

Great, it works! This is the configuration kernel command to use in every situation. When we look at the make_menuconfig function, we see that the result of the kernel configuration is stored in the .config file. This is the file where every setting we do in the kernel configuration stage is placed.

The make_kernel function is a bit more complex, but the relevant code is shown here:

##uImage, if you really really want a uImage, zreladdr needs to be
##defined on the build line going forward... 
##make sure to install your distro's version of mkimage 
#image="uImage" 
#address="LOADADDR=${ZRELADDR}" 
 
cd "${DIR}/KERNEL" || exit 
echo "-----------------------------" 
echo "make -j${CORES} ARCH=${KERNEL_ARCH} LOCALVERSION=${BUILD} CROSS_
COMPILE="${CC}" ${address} ${image} modules" 
echo "-----------------------------" 
make -j${CORES} ARCH=${KERNEL_ARCH} LOCALVERSION=${BUILD} CROSS_COMPIL
E="${CC}" ${address} ${image} modules 
echo "-----------------------------" 
 
if grep -q dtbs "${DIR}/KERNEL/arch/${KERNEL_ARCH}/Makefile"; then 
    echo "make -j${CORES} ARCH=${KERNEL_ARCH} LOCALVERSION=${BUILD} CR
OSS_COMPILE="${CC}" dtbs" 
    echo "-----------------------------" 
    make -j${CORES} ARCH=${KERNEL_ARCH} LOCALVERSION=${BUILD} CROSS_CO
MPILE="${CC}" dtbs 
    echo "-----------------------------" 
fi 

We can find the make commands again that do the job. They're reported here:

make -j${CORES} ARCH=${KERNEL_ARCH} LOCALVERSION=${BUILD} CROSS_COMPIL
E="${CC}" ${address} ${image} modules
make -j${CORES} ARCH=${KERNEL_ARCH} LOCALVERSION=${BUILD} CROSS_COMPIL
E="${CC}" dtbs

The former compiles the kernel and its modules, while the latter generates the device tree files (see the next section for further information on the device tree).

Again, we have to substitute the proper values to the variables mentioned earlier, but this is a tricky task. In fact, the CORES variable is just a number equal to the core number of our host PC. KERNEL_ARCH and CROSS_COMPILE must be obviously set to arm and arm-linux-gnueabihf-, while BUILD is just a descriptive string. So, we can choose whatever we wish. The last variables address and image need some more explanation. In fact, we can choose to compile the kernel in two main formats: uImage and zImage. For the former compilation, we need to specify a load address also (the LOADADDR variable), while for the latter, we don't. Since it's the form we used in our developer kits, the two commands mentioned earlier can be rewritten as follows:

make -j8 ARCH=arm LOCALVERSION="dummy" CROSS_COMPILE=arm-linux-gnueabi
hf- zImage modules dtbs

Then, to verify that everything is well configured, we just have to test the command:

$ make -j8 ARCH=arm LOCALVERSION="dummy" CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs
CHK include/config/kernel.release
...
LD kernel/built-in.o
LINK vmlinux
LD vmlinux.o
MODPOST vmlinux.o
GEN .version
CHK include/generated/compile.h
UPD include/generated/compile.h
CC init/version.o
LD init/built-in.o
KSYM .tmp_kallsyms1.o
KSYM .tmp_kallsyms2.o
LD vmlinux
SORTEX vmlinux
SYSMAP System.map
Building modules, stage 2.
OBJCOPY arch/arm/boot/Image
Kernel: arch/arm/boot/Image is ready
...

Yes, it works! Now, we're ready to recompile every kernel tree.

The device tree

The device tree is a data structure to describe hardware. That's all. Rather than hard coding every kernel setting into the code, it can be described in a well-defined data structure that is passed to the kernel at boot time.

The difference between the device tree and the kernel configuration file (the .config file) is that while the .config file tells us which components of the kernel are enabled and which are not, the device tree holds their configurations. So, if we wish to add a driver from the kernel's sources to our system, we have to specify it into the .config file. On the other hand, if we wish to specify the driver settings (memory addresses, special settings, and so on), we have to specify them in the device tree.

In all our developer kits, during the boot stage, we can see some messages from U-Boot's serial console as shown here:

...
reading /dtbs/at91-sama5d3_xplained.dtb
34918 bytes read in 11 ms (3 MiB/s)
reading zImage
3810568 bytes read in 244 ms (14.9 MiB/s)
Kernel image @ 0x22000000 [ 0x000000 - 0x3a2508 ]
## Flattened Device Tree blob at 21000000
 Booting using the fdt blob at 0x21000000
 Loading Device Tree to 2fadc000, end 2fae7865 ... OK
Starting kernel ...
...

Here, we can see that U-Boots loads a DTB file (the binary form of a device tree) and that it passes the file to the kernel.

So, it's time to take a look at how a device tree source (DTS) file is done and how we can generate a device tree binary (DTB) file  from it in order to be used to our kernel. As a very simple example, let's see how the LEDs driver (will see this special driver in LEDs and triggers sectionChapter 6 , General Purposes Input Output signals – GPIO ) can be enabled and how it's defined into the default SAMA5D3 Xplained's DTS file.

To enable the LED driver compilation, we must open the Kernel Configuration menu and then navigate to Device Drivers | LED Support sub menu to enable the proper entries, as shown in the following screenshot:

These settings result in the following output in the.config file:

$ grep '^CONFIG_LEDS_' .config
CONFIG_LEDS_CLASS=y
CONFIG_LEDS_GPIO=y
CONFIG_LEDS_PWM=y
CONFIG_LEDS_TRIGGERS=y
CONFIG_LEDS_TRIGGER_TIMER=y
CONFIG_LEDS_TRIGGER_HEARTBEAT=y
CONFIG_LEDS_TRIGGER_GPIO=y

This enabled the de-facto kernel compilation. However, to effectively define the LEDs present into the SAMA5D3 Xplained, we have to use the following code hold in the arch/arm/boot/dts/at91-sama5d3_xplained.dts file from the kernel's tree:

leds { 
    compatible = "gpio-leds"; 
 
    d2 { 
        label = "d2"; 
        gpios = <&pioE 23 GPIO_ACTIVE_LOW>;
        linux,default-trigger = "heartbeat"; 
    };
 
    d3 { 
        label = "d3"; 
        gpios = <&pioE 24 GPIO_ACTIVE_HIGH>; 
    }; 
}; 

The scope of this book does not explain how the device tree works. However, we must explain the preceding code a bit since we're going to use the device tree in several parts of this book.

Tip

You can get more information on the device tree at the project's home site at:  http://www.devicetree.org/ .

Text between the characters pairs /* and */ are comments, while the leds string on the first line of the preceding code is the label of a new block related to the LEDs driver. In fact, this is specified by the next line where the compatible property is. The gpio-leds string defines the LEDs driver as specified in the drivers/leds/leds-gpio.c file:

static const struct of_device_id of_gpio_leds_match[] = { 
    { .compatible = "gpio-leds", }, 
    {}, 
}; 

Then, the sub-blocks labeled as d2 and d3 define two LEDs labeled with the same names. The gpios lines definition follows, that is, the specification of the GPIOs where the LEDs are physically connected.

As a final note, we see that in the d2 block definition, there is specified a special setting:

linux,default-trigger = "heartbeat"; 

This is to define the trigger to be used for the LED.

Tip

For further information regarding the LEDs driver and its trigger, you can take a look at the Documentation/leds/ directory in the Linux's code repository.

The preceding device tree code is very simple, and it cannot explain all the device tree's components. However, don't worry. Each time, we'll use a device tree settings, we're going to explain it as best as possible.