13 min read

In this article by Alex González, author of the book Embedded LinuxProjects Using Yocto Project Cookbook, we will see how the embedded Linux projects require both custom hardware and software. An early task in the development process is to test different hardware reference boards and the selection of one to base our design on. We have chosen the Wandboard, a Freescale i.MX6-based platform, as it is an affordable and open board, which makes it perfect for our needs.

On an embedded project, it is usually a good idea to start working on the software as soon as possible, probably before the hardware prototypes are ready, so that it is possible to start working directly with the reference design.

But at some point, the hardware prototypes will be ready and changes will need to be introduced into Yocto to support the new hardware.

This article will explain how to create a BSP layer to contain those hardware-specific changes, as well as show how to work with the U-Boot bootloader and the Linux kernel, components which are likely to take most of the customization work.

(For more resources related to this topic, see here.)

Creating a custom BSP layer

These custom changes are kept on a separate Yocto layer, called a Board Support Package (BSP) layer. This separation is best for future updates and patches to the system. A BSP layer can support any number of new machines and any new software feature that is linked to the hardware itself.

How to do it…

By convention, Yocto layer names start with meta, short for metadata. A BSP layer may then add a bsp keyword, and finally a unique name. We will call our layer meta-bsp-custom.

There are several ways to create a new layer:

  • Manually, once you know what is required
  • By copying the meta-skeleton layer included in Poky
  • By using the yocto-layer command-line tool

You can have a look at the meta-skeleton layer in Poky and see that it includes the following elements:

  • A layer.conf file, where the layer configuration variables are set
  • A COPYING.MIT license file
  • Several directories named with the recipes prefix with example recipes for BusyBox, the Linux kernel and an example module, an example service recipe, an example user management recipe, and a multilib example.

How it works…

We will cover some of the use cases that appear in the available examples, so for our needs, we will use the yocto-layer tool, which allows us to create a minimal layer.

Open a new terminal and change to the fsl-community-bsp directory. Then set up the environment as follows:

$ source setup-environment wandboard-quad

Note that once the build directory has been created, the MACHINE variable has already been configured in the conf/local.conf file and can be omitted from the command line.

Change to the sources directory and run:

$ yocto-layer create bsp-custom

Note that the yocto-layer tool will add the meta prefix to your layer, so you don’t need to. It will prompt a few questions:

  • The layer priority which is used to decide the layer precedence in cases where the same recipe (with the same name) exists in several layers simultaneously. It is also used to decide in what order bbappends are applied if several layers append the same recipe. Leave the default value of 6. This will be stored in the layer’s conf/layer.conf file as BBFILE_PRIORITY.
  • Whether to create example recipes and append files. Let’s leave the default no for the time being.

Our new layer has the following structure:

meta-bsp-custom/
   conf/layer.conf
   COPYING.MIT
   README

There’s more…

The first thing to do is to add this new layer to your project’s conf/bblayer.conf file. It is a good idea to add it to your template conf directory’s bblayers.conf.sample file too, so that it is correctly appended when creating new projects. The highlighted line in the following code shows the addition of the layer to the conf/bblayers.conf file:

LCONF_VERSION = "6"
 
BBPATH = "${TOPDIR}"
BSPDIR := "${@os.path.abspath(os.path.dirname(d.getVar('FILE', "" True)) + '/../..')}"
 
BBFILES ?= ""
BBLAYERS = " 
${BSPDIR}/sources/poky/meta 
${BSPDIR}/sources/poky/meta-yocto 

${BSPDIR}/sources/meta-openembedded/meta-oe 
${BSPDIR}/sources/meta-openembedded/meta-multimedia 

${BSPDIR}/sources/meta-fsl-arm 
${BSPDIR}/sources/meta-fsl-arm-extra 
${BSPDIR}/sources/meta-fsl-demos 
${BSPDIR}/sources/meta-bsp-custom 
"

Now, BitBake will parse the bblayers.conf file and find the conf/layers.conf file from your layer. In it, we find the following line:

BBFILES += "${LAYERDIR}/recipes-*/*/*.bb 
       ${LAYERDIR}/recipes-*/*/*.bbappend"

It tells BitBake which directories to parse for recipes and append files. You need to make sure your directory and file hierarchy in this new layer matches the given pattern, or you will need to modify it.

BitBake will also find the following:

BBPATH .= ":${LAYERDIR}"

The BBPATH variable is used to locate the bbclass files and the configuration and files included with the include and require directives. The search finishes with the first match, so it is best to keep filenames unique.

Some other variables we might consider defining in our conf/layer.conf file are:

LAYERDEPENDS_bsp-custom = "fsl-arm"
LAYERVERSION_bsp-custom = "1"

The LAYERDEPENDS literal is a space-separated list of other layers your layer depends on, and the LAYERVERSION literal specifies the version of your layer in case other layers want to add a dependency to a specific version.

The COPYING.MIT file specifies the license for the metadata contained in the layer. The Yocto project is licensed under the MIT license, which is also compatible with the General Public License (GPL). This license applies only to the metadata, as every package included in your build will have its own license.

The README file will need to be modified for your specific layer. It is usual to describe the layer and provide any other layer dependencies and usage instructions.

Adding a new machine

When customizing your BSP, it is usually a good idea to introduce a new machine for your hardware. These are kept under the conf/machine directory in your BSP layer. The usual thing to do is to base it on the reference design. For example, wandboard-quad has the following machine configuration file:

include include/wandboard.inc
 
SOC_FAMILY = "mx6:mx6q:wandboard"
 
UBOOT_MACHINE = "wandboard_quad_config"
 
KERNEL_DEVICETREE = "imx6q-wandboard.dtb"
 
MACHINE_FEATURES += "bluetooth wifi"
 
MACHINE_EXTRA_RRECOMMENDS += " 
bcm4329-nvram-config 
bcm4330-nvram-config 
"

A machine based on the Wandboard design could define its own machine configuration file, wandboard-quad-custom.conf, as follows:

include conf/machine/include/wandboard.inc
 
SOC_FAMILY = "mx6:mx6q:wandboard"
 
UBOOT_MACHINE = "wandboard_quad_custom_config"
 
KERNEL_DEVICETREE = "imx6q-wandboard-custom.dtb"
 
MACHINE_FEATURES += "wifi"

The wandboard.inc file now resides on a different layer, so in order for BitBake to find it, we need to specify the full path from the BBPATH variable in the corresponding layer. This machine defines its own U-Boot configuration file and Linux kernel device tree in addition to defining its own set of machine features.

Adding a custom device tree to the Linux kernel

To add this device tree file to the Linux kernel, we need to add the device tree file to the arch/arm/boot/dts directory under the Linux kernel source and also modify the Linux build system’s arch/arm/boot/dts/Makefile file to build it as follows:

dtb-$(CONFIG_ARCH_MXC) += 
+imx6q-wandboard-custom.dtb 

This code uses diff formatting, where the lines with a minus prefix are removed, the ones with a plus sign are added, and the ones without a prefix are left as reference.

Once the patch is prepared, it can be added to the meta-bsp-custom/recipes-kernel/linux/linux-wandboard-3.10.17/ directory and the Linux kernel recipe appended adding a meta-bsp-custom/recipes-kernel/linux/linux-wandboard_3.10.17.bbappend file with the following content:

SRC_URI_append = " file://0001-ARM-dts-Add-wandboard-custom-dts- "" file.patch"

Adding a custom U-Boot machine

In the same way, the U-Boot source may be patched to add a new custom machine. Bootloader modifications are not as likely to be needed as kernel modifications though, and most custom platforms will leave the bootloader unchanged. The patch would be added to the meta-bsp-custom/recipes-bsp/u-boot/u-boot-fslc-v2014.10/ directory and the U-Boot recipe appended with a meta-bsp-custom/recipes-bsp/u-boot/u-boot-fslc_2014.10.bbappend file with the following content:

SRC_URI_append = " file://0001-boards-Add-wandboard-custom.patch"

Adding a custom formfactor file

Custom platforms can also define their own formfactor file with information that the build system cannot obtain from other sources, such as defining whether a touchscreen is available or defining the screen orientation. These are defined in the recipes-bsp/formfactor/ directory in our meta-bsp-custom layer. For our new machine, we could define a meta-bsp-custom/recipes-bsp/formfactor/formfactor_0.0.bbappend file to include a formfactor file as follows:

FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"

And the machine-specific meta-bsp-custom/recipes-bsp/formfactor/formfactor/wandboard-quadcustom/machconfig file would be as follows:

HAVE_TOUCHSCREEN=1

Debugging the Linux kernel booting process

We have seen the most general techniques for debugging the Linux kernel. However, some special scenarios require the use of different methods. One of the most common scenarios in embedded Linux development is the debugging of the booting process. This section will explain some of the techniques used to debug the kernel’s booting process.

How to do it…

A kernel crashing on boot usually provides no output whatsoever on the console. As daunting as that may seem, there are techniques we can use to extract debug information. Early crashes usually happen before the serial console has been initialized, so even if there were log messages, we would not see them. The first thing we will show is how to enable early log messages that do not need the serial driver.

In case that is not enough, we will also show techniques to access the log buffer in memory.

How it works…

Debugging booting problems have two distinctive phases, before and after the serial console is initialized. After the serial is initialized and we can see serial output from the kernel, debugging can use the techniques described earlier.

Before the serial is initialized, however, there is a basic UART support in ARM kernels that allows you to use the serial from early boot. This support is compiled in with the CONFIG_DEBUG_LL configuration variable.

This adds supports for a debug-only series of assembly functions that allow you to output data to a UART. The low-level support is platform specific, and for the i.MX6, it can be found under arch/arm/include/debug/imx.S. The code allows for this low-level UART to be configured through the CONFIG_DEBUG_IMX_UART_PORT configuration variable.

We can use this support directly by using the printascii function as follows:

extern void printascii(const char *);
printascii("Literal stringn");

However, much more preferred would be to use the early_print function, which makes use of the function explained previously and accepts formatted input in printf style; for example:

early_print("%08xt%sn", p->nr, p->name);

Dumping the kernel’s printk buffer from the bootloader

Another useful technique to debug Linux kernel crashes at boot is to analyze the kernel log after the crash. This is only possible if the RAM memory is persistent across reboots and does not get initialized by the bootloader.

As U-Boot keeps the memory intact, we can use this method to peek at the kernel login memory in search of clues.

Looking at the kernel source, we can see how the log ring buffer is set up in kernel/printk/printk.c and also note that it is stored in __log_buf.

To find the location of the kernel buffer, we will use the System.map file created by the Linux build process, which maps symbols with virtual addresses using the following command:

$grep __log_buf System.map
80f450c0 b __log_buf

To convert the virtual address to physical address, we look at how __virt_to_phys() is defined for ARM:

x - PAGE_OFFSET + PHYS_OFFSET

The PAGE_OFFSET variable is defined in the kernel configuration as:

config PAGE_OFFSET
       hex
       default 0x40000000 if VMSPLIT_1G
       default 0x80000000 if VMSPLIT_2G
       default 0xC0000000

Some of the ARM platforms, like the i.MX6, will dynamically patch the __virt_to_phys() translation at runtime, so PHYS_OFFSET will depend on where the kernel is loaded into memory. As this can vary, the calculation we just saw is platform specific.

For the Wandboard, the physical address for 0x80f450c0 is 0x10f450c0.

We can then force a reboot using a magic SysRq key, which needs to be enabled in the kernel configuration with CONFIG_MAGIC_SYSRQ, but is enabled in the Wandboard by default:

$ echo b > /proc/sysrq-trigger

We then dump that memory address from U-Boot as follows:

> md.l 0x10f450c0
10f450c0: 00000000 00000000 00210038 c6000000   ........8.!.....
10f450d0: 746f6f42 20676e69 756e694c 6e6f2078   Booting Linux on
10f450e0: 79687020 61636973 5043206c 78302055     physical CPU 0x
10f450f0: 00000030 00000000 00000000 00000000   0...............
10f45100: 009600a8 a6000000 756e694c 65762078   ........Linux ve
10f45110: 6f697372 2e33206e 312e3031 2e312d37   rsion 3.10.17-1.
10f45120: 2d322e30 646e6177 72616f62 62672b64   0.2-wandboard+gb
10f45130: 36643865 62323738 20626535 656c6128   e8d6872b5eb (ale
10f45140: 6f6c4078 696c2d67 2d78756e 612d7068   x@log-linux-hp-a
10f45150: 7a6e6f67 20296c61 63636728 72657620   gonzal) (gcc ver
10f45160: 6e6f6973 392e3420 2820312e 29434347   sion 4.9.1 (GCC)
10f45170: 23202920 4d532031 52502050 504d4545     ) #1 SMP PREEMP
10f45180: 75532054 6546206e 35312062 3a323120   T Sun Feb 15 12:
10f45190: 333a3733 45432037 30322054 00003531   37:37 CET 2015..
10f451a0: 00000000 00000000 00400050 82000000   ........P.@.....
10f451b0: 3a555043 4d524120 50203776 65636f72   CPU: ARMv7 Proce

There’s more…

Another method is to store the kernel log messages and kernel panics or oops into persistent storage. The Linux kernel’s persistent store support (CONFIG_PSTORE) allows you to log in to the persistent memory kept across reboots.

To log panic and oops messages into persistent memory, we need to configure the kernel with the CONFIG_PSTORE_RAM configuration variable, and to log kernel messages, we need to configure the kernel with CONFIG_PSTORE_CONSOLE.

We then need to configure the location of the persistent storage on an unused memory location, but keep the last 1 MB of memory free. For example, we could pass the following kernel command-line arguments to reserve a 128 KB region starting at 0x30000000:

ramoops.mem_address=0x30000000 ramoops.mem_size=0x200000

We would then mount the persistent storage by adding it to /etc/fstab so that it is available on the next boot as well:

/etc/fstab:
pstore /pstore pstore defaults 0 0

We then mount it as follows:

# mkdir /pstore
# mount /pstore

Next, we force a reboot with the magic SysRq key:

# echo b > /proc/sysrq-trigger

On reboot, we will see a file inside /pstore:

-r--r--r-- 1 root root 4084 Sep 16 16:24 console-ramoops

This will have contents such as the following:

SysRq : Resetting
CPU3: stopping
CPU: 3 PID: 0 Comm: swapper/3 Not tainted 3.14.0-rc4-1.0.0-wandboard-37774-g1eae
[<80014a30>] (unwind_backtrace) from [<800116cc>] (show_stack+0x10/0x14)
[<800116cc>] (show_stack) from [<806091f4>] (dump_stack+0x7c/0xbc)
[<806091f4>] (dump_stack) from [<80013990>] (handle_IPI+0x144/0x158)
[<80013990>] (handle_IPI) from [<800085c4>] (gic_handle_irq+0x58/0x5c)
[<800085c4>] (gic_handle_irq) from [<80012200>] (__irq_svc+0x40/0x70)
Exception stack(0xee4c1f50 to 0xee4c1f98)

We should move it out of /pstore or remove it completely so that it doesn’t occupy memory.

Summary

This article guides you through the customization of the BSP for your own product. It then explains how to debug the Linux kernel booting process.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here