Skip to content

Use case 3: other HW

What typically differs from a BSP to another are:

  • versions and source code repositories of bootloader and kernel
  • device trees
  • defconfig (selection of drivers...)

This page gives guidelines for supporting Welma on a custom hardware board.

Step 1: Learn about your HW platform

First you need to know:

  • Which HW components and peripherals are on your board
  • What storage you may use for your product
  • How to do the cabling

Then you need complete build & boot procedures with the vendor's BSP (without Welma). This ensures that you have a working bootloader and Linux kernel with compatible drivers, device trees and defconfig.

If you were already provided with Yocto layers (for example provided by a SOM vendor), that build a bootable image, it is a good starting point. And you have to understand how the BSP Yocto layers of the vendor are organized.

Finally you need to know:

  • What the boot sequence of the board is (BL2, BL31, BL33,...)

  • How to boot your bootloader without flashing it (uuu, dfu-util, ...)

  • how to flash the Linux image

Step 2: Set up your Yocto BSP layer

  • Create a layer for your BSP (eg: meta-<bsp-name>):

    • Decide on a name for MACHINE
    • Create conf/layer.conf
    • Create conf/machine/$MACHINE.conf
    • Create conf/templates/$MACHINE/{local,bblayers}.conf.sample
  • Decide on the Yocto layers to use:

    • We generally recommend to remove dependencies on unnecessary layers that might be mentioned in the vendor's BSP layers (typically meta-freescale-*, meta-arm, meta-imx, ...). The reason is to make maintenance and upgrades easier. You generally do not need these extra layers. But if, for your project, you need a specific recipe of one of these layers, you can decide later either to use the whole layer or to write your own recipe directly in meta-<bsp-name>.

    • Integrate meta-welma

    • Create your manifest that references these layers and their exact references (tag or commit identifier).

    • Integrate these in bblayers.conf.sample, and so that all developers share the same reference.

  • Stick to the revisions of the vendor's BSP (u-boot, optee-os, tf-a, kernel):

    • SRC_URI, SRCREV, UBOOT_CONFIG, KERNEL_DEVICETREE, ...
  • Stick to Welma's default partition layout for now: boot, sysro, appro, sysrw.

  • Create your partition mapping in split/welma.part. See Partitioning. Eg:

part    boot    size=50M,update=ab,dev=/dev/mmcblk0p3:/dev/mmcblk0p4
part    sysro   size=400M,update=ab,dev=/dev/mmcblk0p5:/dev/mmcblk0p6
part    appro   size=15M,update=ab,dev=/dev/mmcblk0p7:/dev/mmcblk0p8
part    sysrw   size=400M,dev=/dev/mmcblk0p9
  • If using wic, use a custom WKS file that configures the partitions for the default Welma partition layout (boot, sysro, appro, sysrw). Make sure the indices of the partitions match those of split/welma.part.

  • At this point you should be able to build (bitbake welma-image-minimal-dev) and flash your image and run your bootloader. It will probably not boot further, as the vendor's bootloader does not know how to boot the Welma image.

Step 3: Have Welma boot to initramfs

In this step, you have to explain to U-Boot from which storage the Linux kernel should be loaded ie. modify the U-Boot boot sequence accordingly.

The Linux kernel of the Welma image is a FIT image named fitImage.

In the examples below, the kernel is loaded from emmc 0 / user area / partition 1

U-Boot:

  • Create the default environment file distro-bootcmd.env:
Example of minimal default environment for machine sm2s-imx8plus-mbep5
/* Machine-specific parameters */
bootmedia=mmc
bootdev=0  /* mmc device number */
fit_loadaddr=0x48000000
console=ttymxc1,115200

evaluate_bootflags=
    echo Placeholder for evaluate_bootflags;
    bootpart=1 /* partition where 'fitImage' is located */

set_swk1_in_bootargs=echo Placeholder for set_swk1_in_bootargs;

boot_welma=
    run evaluate_bootflags;
    load ${bootmedia} ${bootdev}:${bootpart} ${fit_loadaddr} fitImage;
    setenv bootargs "${bootargs} console=${console}";
    run set_swk1_in_bootargs;
    bootm ${fit_loadaddr};
    echo "Boot FAILED. Resetting...";
    reset

distro_bootcmd=run boot_welma;
  • Have this default environment file integrated in u-boot binary (CONFIG_ENV_SOURCE_FILE) and remove #define CFG_EXTRA_ENV_SETTINGS that may be defined in your board file.

Linux kernel:

  • In conf/machine/$MACHINE.conf, define parameters needed for buiding the Linux kernel and its FIT image:

    • KERNEL_DEVICETREE: include dtb and overlays
    • Find available memory regions in RAM and set load addresses for kernel, fdt, fdt overlays, ramdisk. These addresses will be used in the memory context of U-Boot. Eg:
      UBOOT_DTB_LOADADDRESS  = "0x50000000"
      UBOOT_DTBO_LOADADDRESS = "0x51000000"
      UBOOT_LOADADDRESS      = "0x40480000"
      UBOOT_RD_LOADADDRESS   = "0x43800000"
      
  • Kernel recipe: inherit kernel-uboot-deploy

Execute:

  • Build and install the bootloader and all partitions on your board.

  • Start your board.

You should now see your board boot until a kernel panic error, which will be fixed in the next step.

...
U-Boot ...
...
Hit any key to stop autoboot: ...
...
## Loading kernel from FIT Image at ...
...
## Loading ramdisk from FIT Image at ...
...
## Loading fdt from FIT Image at ...
...
Starting kernel ...
[    0.000000] Booting Linux on physical CPU 0x0000000000 ...
...
[    2.878750] Run /init as init process
[    2.915409] initramfs: Starting
[    3.022323] initramfs: ERROR: no active partition for (sysro / ...). Expect undefined behaviour.
[    3.005157] initramfs: ERROR: no active partition for (boot /boot ...). Expect undefined behaviour.
[    3.044028] initramfs: ERROR: no active partition for (appro /app ...). Expect undefined behaviour.
[    3.183040] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000100

Step 4: Support A/B partitions

Partition layout:

  • Allocate an extra partition for the bootflags.

    • Update split/welma.part accordingly if need be.
    • In conf/machine/$MACHINE.conf, define the BOOTFLAGS_ parameters, that indicate how userspace programs will locate the bootflags. Eg:
      BOOTFLAGS_PATH ?= "/dev/mmcblk0p2"
      BOOTFLAGS_OFFSET ?= "0x0"
      BOOTFLAGS_OFFSET_COPY ?= "0x200"
      
  • If using wic:

    • Add this bootflags partition in the WKS file.
    • Make sure that you have A/B partitions for boot, sysro, appro (2 adjacent copies of each)

U-Boot:

  • Add the command bootflags (in cmd/bootflags.c) and have it configured.

  • distro-bootcmd.env: add bootflags logic:

    • Recover if a previous bootflags writing was interrupted
    • Roll back if this is the second attempt to boot in test mode
    • Select the kernel FIT image partition to boot on
evaluate_bootflags=
    bootflags check; /* Recover from a previous interrupted writing */
    bootflags read;  /* loads bootflags_test_mode
                      *       bootflags_test_count
                      *       bootflags_first_active_partition
                      */

    if test $? != 0; then
        /* bootflags not present or corrupted */
        echo "Initialize bootflags recovery environment";
        bootflags set-recovery-env;
    fi;

    if test $bootflags_test_mode = 1; then
        echo "Test mode";
        if test $bootflags_test_count -ge 1; then
            /* cancel test and back to normal mode */
            echo "bootflags_test_count=$bootflags_test_count. Revert to normal mode.";
            bootflags abort-test;
            /* has also updated the values in memory (as bootflags read does) */
        fi;
    else
        echo "Normal mode";
    fi;

    bootpart=${bootflags_first_active_partition}; /* Select boot partition */

    if test $bootflags_test_mode = 1; then
        bootflags increment-test-count;
    fi;

Step 5: Minimal working functionalities

At this point you have to make sure:

  • The Linux commands reboot and reboot -f make your board reboot.
  • Your board has one ethernet interface configured with a static IP address. Example:
    # ip address
    ...
    2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
        link/ether 10:e7:7a:e1:93:9a brd ff:ff:ff:ff:ff:ff
        inet 169.254.0.1/16 brd 169.254.255.255 scope link eth0
           valid_lft forever preferred_lft forever
    [...]
        inet6 fe80::1/10 scope link
           valid_lft forever preferred_lft forever
    [...]
    
  • Your development image can be reached by SSH. Eg:

    $ ssh root@169.254.0.1
    Last login: Fri Jul 19 06:49:53 2024
    stm32mp15-disco-welma:~#
    
    For this, you may need to configure the network (routes) of your PC.

  • U-Boot can download an image via TFTP and install it on the mass memory of the board. This can be helpful for automatic testing of new images. Eg:

    STM32MP> setenv ipaddr 192.168.1.20
    STM32MP> setenv serverip 192.168.1.12
    
    STM32MP> tftp welma/demo-image-headless-dev-stm32mp15-disco-welma.wic.gz
    Using ethernet@5800a000 device
    TFTP from server 192.168.1.12; our IP address is 192.168.1.20
    Filename 'welma/demo-image-headless-dev-stm32mp15-disco-welma.wic.gz'.
    Load address: 0xc2000000
    Loading: #################################################################
             #################################################################
             ########
             4.6 MiB/s
    done
    Bytes transferred = 2024248 (1ee338 hex)
    
    STM32MP> mmcid=0
    STM32MP> gzwrite mmc $mmcid $loadaddr $filesize
    

  • The software update mechanism is working:

    • Upload welma-test/files/swu/swu-without-verity/appro1.swu to your target (eg: to /tmp)
    • On your target:
      • execute: updatectl install /tmp/appro1.swu
      • reboot
      • check that the file /app/README-1.txt is there
      • abort and roll back: updatectl abort
      • check that the device reboots and comes back to its original partitions (updatectl status)

Step 6: Watchdog

To ensure correct rollback in case of the update of a flawed kernel, you need to use a hardware watchdog.

If your board has more than one HW watchdog device, make sure to use the same watchdog device in U-Boot and Linux kernel & userspace.

U-Boot:

  • Start the watchdog timer before starting the kernel, with a timeout of 30 to 60 s (to give the kernel time to start)

It is also useful to have the wdt command (CMD_WDT=y), for testing (see https://docs.u-boot.org/en/latest/usage/cmd/wdt.html).

Linux kernel:

  • Activate the driver to support the watchdog device
  • Have the kernel ping the watchdog device until a userspace program takes over (for at most 4 min) and do not stop the watchdog timer when this userspace program closes /dev/watchdog:
WATCHDOG_HANDLE_BOOT_ENABLED=y
WATCHDOG_NOWAYOUT=y
WATCHDOG_OPEN_TIMEOUT=240

systemd:

  • Configure systemd to service the watchdog device (/dev/watchdog0).

Step 7: Finalization of basic support

  • Populate the license digest in recipes-welma/images/license-digest.bbappend:

    • WELMA_LIC_DIGEST_BOOT: add the recipes of the packages of the bootloader
  • Customize your partitions

  • Configure secure storage. This support should be provided the bootloader which must integrate and launch OP-TEE OS. In Linux, use xtest (from package optee-test) to check correct support.

  • Configure data provisioning

  • Add your project-specific packages in your image

Step 8: Support secure boot

  • In conf/templates/$MACHINE/local.conf.sample, set WELMA_SECURE_BOOT = "1"

Bootloader:

  • Activate (or write) the code for authenticating each stage until U-Boot
  • Have it signed in the Yocto build with development keys
  • Create the tools for signing out of Yocto
  • Document how to provision the needed public keys within manufactured products

U-Boot:

We only consider here the standard U-Boot mechanism for authenticating the kernel FIT image with RSA public keys. Some vendor's versions of U-Boot use other mechanisms (such as HAB on imx-based boards), and this is not covered in this document.

  • Provision SWK1 in U-Boot

    • do_compile:append(): have SWK1 injected into the U-Boot binary using the script inject-pubkey-uboot-dtb
  • Have U-Boot pass SWK1 to the kernel (needed when SWK2 is not used):

    • Add the command fdt get hexvalue (in cmd/fdt.c)
    • distro-bootcmd.env: add SWK1 public key to bootargs
set_swk1_in_bootargs=
    fdt addr ${fdtcontroladdr};
    fdt get hexvalue rsa_exponent /signature/key-SWK1 rsa,exponent;
    fdt get hexvalue rsa_modulus /signature/key-SWK1 rsa,modulus;
    setenv bootargs "${bootargs} swk1=rsa_exponent:0x${rsa_exponent};rsa_modulus:0x${rsa_modulus}"

Linux kernel:

  • Enable dm-verity: CONFIG_DM_VERITY=y

Step 9: Production bootloader

Production image should not have an interactive bootloader.

Make sure to apply a minimal u-boot configuration when IMAGE_FEATURES contains development.

This minimal configuration should at least include CONFIG_BOOTDELAY=-2 to boot without offering any command line.