Manifest of the Advantech Linux TSU Yocto project

The goal of this project is to release opensource Linux source code that runs on Advantech Hardware

Download the Yocto Linux for ECU-150-A1 and setup environment variables

For downloading the source code and setting up the environment, follow the instructions below:

foo@bar:~/yocto$ repo init -u https://github.com/saurontech/Advantech-LinuxTSU-manifest.git -b main -m ecu-150a1-6.6.36-2.1.0.xml
foo@bar:~/yocto$ repo sync
foo@bar:~/yocto$ MACHINE=imx8mq-ecu150a1 DISTRO=fsl-imx-xwayland source ./imx-setup-release.sh -b build
foo@bar:~/yocto/build$ bitbake-layers add-layer ../sources/meta-ecu-150a1/

After the commands, not only was the Yocto project for ECU-150-A1 downloaded, the operating console were also setup to operate bitbaker. Please also notice, that after the commands, your current position has been changed to the build directory! If, in the future, to operate bitbake from another console; in that spacific console, use the command:

foo@bar:~/yocto$ source ./setup-environment build
foo@bar:~/yocto/build$

Build Yocto

Important notice! Edit local.conf based on your host resource.

Building Yocto, with the default configure, is very memory consuming. At least 32 GBytes of RAM will be needed.
With insufficient RAM, the building process will fail. Therefore, limiting the maximum parallel processes allowed, migth be a good idea. One may do so by adding the following parameters to "build/config/local.conf"

PARALLEL_MAKE = "-j 2"
BB_NUMBER_THREADS = "2"

In a console that has been setup properly, use the following command to build Linux and the Yocto rootfs

foo@bar:~/yocto/build$ bitbake core-image-minimal
  • The Linux kernel will be located at : ./build/tmp/deploy/images/imx8mq-ecu150a1/Image
  • The dtb will be located at: ./build/tmp/deploy/images/imx8mq-ecu150a1/fsl-imx8mq-ecu150a1.dtb
  • The rootfs will be located at: ./build/tmp/deploy/images/imx8mq-ecu150a1/core-image-minimal-imx8mq-ecu150a1.rootfs.tar.gz

To build only the Linux kernel, use the following command instead:

foo@bar:~/yocto/build$ bitbake linux-imx

Programmable LEDs and Watchdog Timer

The Programmable LEDs and Watch Dog Timer are accessed via GPIO; therefore, it should be accessed via the standard libGPIOD API.

root@imx8mq-ecu150a1:~/$ gpioinfo
...
gpiochip3 - 32 lines:
        ...
        line   11:    "WDT_EN"       unused   input  active-high
        ...
        line   20:       "WDT"       unused   input  active-high
gpiochip4 - 32 lines:
        line   0:      unnamed       unused   input  active-high
        line   1:      unnamed       unused   input  active-high
        line   2:      unnamed       unused   input  active-high
        line   3:        "PL1"       unused   input  active-high
        line   4:        "PL2"       unused   input  active-high
        line   5:        "PL3"       unused   input  active-high
...
root@imx8mq-ecu150a1:~/$ gpioset -t0 PL1=1	//switch on Programmable LED 1
root@imx8mq-ecu150a1:~/$ gpioset -t0 PL1=0        //switch off Programmable LED 1

The following bash-script gives an idea on how the watchdog timer could be enabled and updated. Run the script and terminate it with control-c and the WDT will reset the system.

#!/bin/bash
i=0
# clear the wdt a few times before enabling it.
# It has left alone for too long,
# enabling it right away will reset the system immediately.
for j in {0..5}
do
	gpioset -t0 WDT=$i
	if [ $i -eq 0 ]; then 
		i=1
	else
		i=0
	fi
	sleep 0.3
done
# enable the wdt
gpioset -t0 WDT_EN=0
while true
do
	gpioset -t0 WDT=$i
	if [ $i -eq 0 ]; then 
		i=1
	else
		i=0
	fi
	sleep 0.3
done

Create SDK for Yocto

foo@bar:~/yocto/build$ bitbake -c populate_sdk core-image-minimal
foo@bar:~/yocto/build$ sudo sh ./tmp/deploy/sdk/fsl-imx-xwayland-glibc-x86_64-core-image-minimal-armv8a-imx8mq-ecu150a1-toolchain-6.6-scarthgap.sh
NXP i.MX Release Distro SDK installer version 6.6-scarthgap
===========================================================
Enter target directory for SDK (default: /opt/fsl-imx-xwayland/6.6-scarthgap): ~/my_sdk
You are about to install the SDK to "/home/foo/test/my_sdk". Proceed [Y/n]? y
Extracting SDK...................................................................................................................................................................................................................done
Setting it up...done
SDK has been successfully set up and is ready to be used.
Each time you wish to use the SDK in a new shell session, you need to source the environment setup script e.g.
 $ . /home/foo/my_sdk/environment-setup-armv8a-poky-linux
foo@bar:~/yocto/build$ make modules_prepare -C $SDKTARGETSYSROOT/usr/src/kernel

Build out-of-tree kernel modules

The following example shows how to build a out-of-tree kernel module with the Yocto SDK.
We use the Advantech USB-4604B, a USB to serial converter, as an example.

Notice that this spacific drivers makefile uses $(KERNELDIR) to represent the position of the kernel header files; therefore, we use export KERNELDIR=$SDKTARGETSYSROOT/usr/src/kernel before we build the driver with make.

foo@bar:~/example$ git clone https://github.com/saurontech/USB-4604-BE-linux-driver.git
foo@bar:~/example$ cd ./USB-4604-BE-linux-driver/driver
foo@bar:~/example/USB-4604-BE-linux-driver/driver$ . /home/foo/my_sdk/environment-setup-armv8a-poky-linux
foo@bar:~/example/USB-4604-BE-linux-driver$ cat ./Makefile
obj-m := adv_usb_serial.o
adv_usb_serial-objs := xr_usb_serial_common.o

KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD       := $(shell pwd)

EXTRA_CFLAGS	:= -DDEBUG=0

all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD)

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions vtty *.symvers *.order *.a *.mod
foo@bar:~/example/USB-4604-BE-linux-driver/driver$ export KERNELDIR=$SDKTARGETSYSROOT/usr/src/kernel
foo@bar:~/example/USB-4604-BE-linux-driver$ make

Kernel Development

To modify/develop the in-tree kernel source code, the devtool command provided by Yocto is a good base to start. The following command will prepare a kernel source tree, which is managed by git, under "build/workspace/sources/linux-imx".

foo@bar:~/yocto/build$ devtool modify linux-imx
foo@bar:~/yocto/build$ ls ./workspace/sources/
linux-imx

To test the modified source code, build the modified kernel with the following command.

foo@bar:~/yocto/build$ devtool build linux-imx

To modify the default configure, use the following command.

foo@bar:~/yocto/build$ devtool menuconfig  linux-imx

During the process, use "git add, git commit" to source-control the development. To save all the changes controled by git into a new layer, use the following commands

foo@bar:~/yocto/build$ bitbake-layers create-layer ../source/meta-mylayer
foo@bar:~/yocto/build$ devtools update-recipe -a ../source/meta-mylayer linux-imx

To finish the process, use the following procedure to clean the current workspace and add the newly created layer to the yocto project.

foo@bar:~/yocto/build$ devtool reset linux-imx
foo@bar:~/yocto/build$ bitbake-layers add-layer ../source/meta-mylayer/
foo@bar:~/yocto/build$ bitbake linux-imx

Build Debian/Ubuntu based rootfs

follow the instructions below to build the rootfs with debootstrap, qemu, and chroot

foo@bar:~/work$ sudo apt-get install qemu-user-static debootstrap debian-archive-keyring
foo@bar:~/work$ nano ch-rootfs.sh
foo@bar:~/work$ chmod +x ./ch-rootfs.sh

the content of ch-rootfs.sh is listed as below:

#!/bin/bash
#
function mnt() {
 echo "MOUNTING"
 sudo mount -t proc /proc ${2}proc
 sudo mount -t sysfs /sys ${2}sys
 sudo mount -o bind /dev ${2}dev
 sudo mount -o bind /dev/pts ${2}dev/pts
 sudo chroot ${2}
}
function umnt() {
 echo "UNMOUNTING"
 sudo umount ${2}proc
 sudo umount ${2}sys
 sudo umount ${2}dev/pts
 sudo umount ${2}dev
}

function pack() {
 echo "Packing rootfs to rootfs.tar.gz ...."
 sudo rm -f ../rootfs.tar.gz
 echo '=== tar rootfs start ==='
 cd $2 && sudo tar zcvf ../rootfs.tar.gz *
 echo '=== tar rootfs finish ==='
}

if [ "$1" == "-m" ] && [ -n "$2" ] ;
then
 mnt $1 $2
 umnt $1 $2
elif [ "$1" == "-u" ] && [ -n "$2" ];
then
 umnt $1 $2
elif [ "$1" == "-z" ] && [ -n "$2" ];
then
 pack $1 $2
else
 echo ""
 echo "Either 1'st, 2'nd or both parameters were missing"
 echo ""
 echo "1'st parameter can be one of these: -m(mount) OR -u(umount) or -z(pack)"
 echo "2'nd parameter is the full path of rootfs directory(with trailing '/')"
 echo ""
 echo "For example: ./ch-rootfs.sh -m /media/sdcard/"
 echo ""
 echo 1st parameter : ${1}
 echo 2nd parameter : ${2}
fi

The following commands will slightly differe between Debian 12 and Ubuntu 24.04; therefore, we seperate the instuctions into two subsections.
Choose the instructions based on your target distro. The "deb" files mentioned below could be found in "yocto/build/tmp/deploy/deb/all/".

Debian 12

foo@bar:~/work$ sudo debootstrap --arch arm64 bookworm my_rootfs http://deb.debian.org/debian
foo@bar:~/work$ sudo tar xvf ./modules-imx8mq-ecu150a1.tgz -C ./my_rootfs/usr/
foo@bar:~/work$ cp firmware-imx-sdma-imx7d*.deb ./my_rootfs/tmp/
foo@bar:~/work$ cp linux-firmware-rtl*.deb ./my_rootfs/tmp/
foo@bar:~/work$ ./ch-rootfs.sh -m ./my_rootfs/
imx@bar:~/$ apt-get update
imx@bar:~/$ apt-get install sudo ssh net-tools iputils-ping rsyslog bash-completion htop resolvconf dialog gpiod vim locales netplan.io systemd-timesyncd systemd-resolved
imx@bar:~/$ dpkg-reconfigure locales
imx@bar:~/$ cd /tmp  && dpkg -i *.deb

Ubuntu 24.04

foo@bar:~/work$ sudo debootstrap --arch arm64 noble my_rootfs http://tw.archive.ubuntu.com/ubuntu/
foo@bar:~/work$ ./ch-rootfs.sh -m ./my_rootfs/
foo@bar:~/work$ sudo tar xvf ./modules-imx8mq-ecu150a1.tgz -C ./my_rootfs/usr/
foo@bar:~/work$ cp firmware-imx-sdma-imx7d*.deb ./my_rootfs/tmp/
foo@bar:~/work$ cp linux-firmware-rtl*.deb ./my_rootfs/tmp/
foo@bar:~/work$ ./ch-rootfs.sh -m ./my_rootfs/
root@imx:~/$ apt-get update
root@imx:~/$ apt install sudo ssh net-tools iputils-ping rsyslog bash-completion htop vim nano netplan.io software-properties-common
root@imx:~/$ add-apt-repository universe
root@imx:~/$ cd /tmp  && dpkg -i *.deb

The difference between Debian 12/Ubuntu 24.04 stops here.
The following instructions can be shared between two target distros.

root@imx:~/$ echo 'imx8mq-ecu150a1' > /etc/hostname
root@imx:~/$ cat <<EOF >> /etc/udev/rules.d/localextra.rules
# Microchip Technology USB2740 Hub
KERNEL=="rtc1", SYMLINK+="rtc"

KERNEL=="ttymxc2", GROUP="dialout", MODE="0664", SYMLINK+="ttyAP0"
KERNEL=="ttymxc3", GROUP="dialout", MODE="0664", SYMLINK+="ttyAP1"
KERNEL=="fram0", GROUP="dialout", MODE="0664", SYMLINK+="fram"
EOF
root@imx:~/$ systemctl enable systemd-networkd.service
root@imx:~/$ systemctl enable systemd-resolved.service
root@imx:~/$ systemctl enable systemd-timesyncd.service
root@imx:~/$ nano /etc/profile.d/custom.sh
root@imx:~/$ exit
foo@bar:~/work$

The constent of custom.sh is listed as below:

# Set the prompt for bash and ash (no other shells known to be in use here)
[ -z "$PS1" ] || PS1='\u@\h:\w\$ '

# Use the EDITOR not being set as a trigger to call resize later on
FIRSTTIMESETUP=0
if [ -z "$EDITOR" ] ; then
    FIRSTTIMESETUP=1
fi

if [ -t 0 -a $# -eq 0 ]; then
    if [ ! -x /usr/bin/resize ] ; then
        if [ -n "$BASH_VERSION" ] ; then
            # Optimized resize funciton for bash
resize() {
    local x y
    IFS='[;' read -t 2 -p $(printf '\e7\e[r\e[999;999H\e[6n\e8') -sd R _ y x _
    [ -n "$y" ] && \
    echo -e "COLUMNS=$x;\nLINES=$y;\nexport COLUMNS LINES;" && \
    stty cols $x rows $y
}
        else
# Portable resize function for ash/bash/dash/ksh
# with subshell to avoid local variables
resize() {
    (o=$(stty -g)
    stty -echo raw min 0 time 2
    printf '\0337\033[r\033[999;999H\033[6n\0338'
    if echo R | read -d R x 2> /dev/null; then
        IFS='[;R' read -t 2 -d R -r z y x _
    else
        IFS='[;R' read -r _ y x _
    fi
    stty "$o"
    [ -z "$y" ] && y=${z##*[}&&x=${y##*;}&&y=${y%%;*}
    [ -n "$y" ] && \
    echo "COLUMNS=$x;"&&echo "LINES=$y;"&&echo "export COLUMNS LINES;"&& \
    stty cols $x rows $y)
}
        fi
    fi
    # only do this for /dev/tty[A-z] which are typically
    # serial ports
    if [ $FIRSTTIMESETUP -eq 1 -a ${SHLVL:-1} -eq 1 ] ; then
        case $(tty 2>/dev/null) in
            /dev/tty[A-z]*) resize >/dev/null;;
        esac
    fi
fi