KERNEL

Continuiamo la nostra avventura affrontando l’ostacolo più grosso. Il kernel è dove succede la vera magia. Il problema principale è destreggiarsi tra i principali strumenti che lo rendono così configurabile: il device-tree, i driver e i kconfig. Le distribuzioni Yocto solitamente usano un fork kernel Linux originale, integrandoci degli ulteriori meccanismi di configurazione. Al momento però il kernel linux-yocto non include la logica e la definizioni necessarie per pilotare le schede della famiglia stm32mp2. Potremmo cambiare SRC_URI per puntare alla repo di stm come abbiamo fatto per gli altri recipe, ma incontreremmo tutta una serie di errori di Yocto in quanto nei recipe che si occupano del kernel sono implementati tutta una serie di controlli sulla validità del SRC_URI. La scelta più conveniente è quindi di creare un nuovo recipe e includere la logica di base per il kernel fornita da Yocto.

recipe iniziale

Possiamo creare un file .bb da zero oppure utilizzare recipetool per creare un .bb con già un punto di partenza decente:

recipetool create -o /workdir/layers/yoctolearn/meta-stm32mp2/recipes-kernel/linux/linux-stm.bb https://github.com/STMicroelectronics/linux.git

è sufficiente fornire il path del recipe e url della repo che vogliamo usare.

recipes-kernel/linux/linux-stm.bb
 1SUMMARY = "An example kernel recipe that uses the linux-yocto and oe-core"
 2# linux-yocto-custom.bb:
 3#
 4#   kernel classes to apply a subset of yocto kernel management to git
 5#   managed kernel repositories.
 6#
 7#   To use linux-yocto-custom in your layer, copy this recipe (optionally
 8#   rename it as well) and modify it appropriately for your machine. i.e.:
 9#
10#     COMPATIBLE_MACHINE:yourmachine = "yourmachine"
11#
12#   You must also provide a Linux kernel configuration. The most direct
13#   ...
14#   example feature addition (for kernel v4.x only):
15#            SRC_URI += "file://feature.scc"
16#
17
18inherit kernel
19require recipes-kernel/linux/linux-yocto.inc
20
21SRC_URI = "git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git;protocol=git;nocheckout=1;name=machine"
22
23LINUX_VERSION ?= "5.0.0-rc4"
24LINUX_VERSION_EXTENSION:append = "-custom"
25
26SRCREV_machine= "62967898789dc1f09a06e59fa85ae2c5ca4dc2da"
27
28PV = "${LINUX_VERSION}+git"
29
30COMPATIBLE_MACHINE = "stm32mp257f-dk"
31# WARNING: the following LICENSE and LIC_FILES_CHKSUM values are best guesses - it is
32# your responsibility to verify that the values are complete and correct.
33#
34# The following license files were not able to be identified and are
35# ...
36# instead of &. If there is any doubt, check the accompanying documentation
37# to determine which situation is applicable.
38LICENSE = "LGPL-2.0-only & Unknown"
39LIC_FILES_CHKSUM = "file://COPYING;md5=bbea815ee2795b2f4230826c0c6b8814 \
40                    file://Documentation/networking/device_drivers/qlogic/LICENSE.qla3xxx;md5=ae5c998421760d17dd805a57f798a82b \
41                    file://Documentation/networking/device_drivers/qlogic/LICENSE.qlcnic;
42                    ...
43                    file://tools/usb/usbip/COPYING;md5=1f6f1c0be32491a0c8d2915607a28f36"

Vediamo che bitbake capisce che si tratta del kernel analizzando i contenuti della repo che stiamo usando e aggiunge delle variabili rilevanti al nostro recipe. COMPATIBLE_MACHINE = "stm32mp257f-dk" ci va bene, però SRC_URI fa riferimento alla versione mainline di linux e a noi non va bene. Utile che abbia inserito anche i dati della licenza, però non possiamo usarli perchè si riferiscono ai file presenti nella repo sbagliata.

In caso di dubbi sulle variabili ricordiamoci che esiste il glossario, controllando che sia relativo alla versione Yocto che stiamo usando. Yocto fornisce anche una lista di variabili importanti per kernel.

Modifichiamo quindi il recipe:

recipes-kernel/linux/linux-stm.bb
SRC_URI = "git://github.com/STMicroelectronics/linux.git;branch=v6.6-stm32mp;protocol=https"

## tag: v6.6-stm32mp-r2
SRCREV = "f01241fbba4d879fa770685629b49d42e904e43c"

PV := "v6.6-stm32mp"
PR := "r2"

LINUX_VERSION = "6.6"
LINUX_VERSION_EXTENSION:append = "-stm32"

provando a buildare vedremo una serie di errori:

bitbake
ERROR: Nothing PROVIDES 'linux-stm'
linux-stm was skipped: Set PREFERRED_PROVIDER_virtual/kernel to linux-stm to enable it

quindi in machine.conf inseriamo

stm32mp257f-dk.conf
PREFERRED_PROVIDER_virtual/kernel = "linux-stm"

poi

bitbake
ERROR: linux-stm-tut-v6.6-stm32mp-r2 do_kernel_metadata: Could not locate BSP definition for stm32mp257f-dk/standard and no defconfig was provided

quindi nel recipe inseriamo

recipes-kernel/linux/linux-stm.bb
KBUILD_DEFCONFIG = "defconfig"

poi

bitbake
ERROR: linux-stm-tut-v6.6-stm32mp-r2 do_kernel_version_sanity_check: Package Version (v6.6-stm32mp) does not match of kernel being built (6.6.78). Please update the PV variable to match the kernel source or set KERNEL_VERSION_SANITY_SKIP="1" in your recipe.

che possiamo risolvere definendo

PV := "6.6.78"

oppure disabilitando l’errore:

KERNEL_VERSION_SANITY_SKIP = "1"

poi

bitbake
| make[1]: *** No rule to make target 'zImage'.  Stop.

il kernel viene compilato in un file compresso chiamato zImage, ma per architettura arm64 questo non va bene e bisogna usare la variante non compressa Image

recipes-kernel/linux/linux-stm.bb
KERNEL_IMAGETYPE = "Image"

poi dopo la prima build vedremo un errore relativo alla licenza e bitbake ci fornirà il valore del checksum da utilizzare:

bitbake
ERROR: linux-stm-6.6.78+git-r2 do_populate_lic: QA Issue: linux-stm: The LIC_FILES_CHKSUM does not match for file://COPYING;md5=d7810fab7487fb0aad327b76f1be7cd7
linux-stm-tut: The new md5 checksum is 6bc538ed5bd9a7fc9398086aedcd7e46

quindi

recipes-kernel/linux/linux-stm.bb
LIC_FILES_CHKSUM = "file://COPYING;md5=6bc538ed5bd9a7fc9398086aedcd7e46"

definendo anche il devicetree da utilizzare, possiamo utilizzare la classe yocto-kernel per compilare il dts in un dtb:

recipes-kernel/linux/linux-stm.bb
## by default dt are searched inside "arch/<ARCH>/boot/dts/" dir
## KERNEL_DEVICETREE is a relative path from this dir
## and must end with .dtb extension
KERNEL_DEVICETREE = "st/stm32mp257f-dk.dtb"

otteniamo così un recipe funzionante:

recipes-kernel/linux/linux-stm.bb
SUMMARY = "Linux STM32MP Kernel"
SECTION = "kernel"
LICENSE = "GPL-2.0-only"
LIC_FILES_CHKSUM = "file://COPYING;md5=6bc538ed5bd9a7fc9398086aedcd7e46"

## inherits kernel and kernel-yocto
require recipes-kernel/linux/linux-yocto.inc

S = "${WORKDIR}/git"
UNPACKDIR = "${S}"


## ================================================================
## STM FORK
## ================================================================
SRC_URI = "git://github.com/STMicroelectronics/linux.git;branch=v6.6-stm32mp;protocol=https"

## tag: v6.6-stm32mp-r2
SRCREV = "f01241fbba4d879fa770685629b49d42e904e43c"
#SRCREV = "${AUTOREV}"

PV := "6.6.78"
PR := "r2"

## repo branch to use
KBRANCH = "v6.6-stm32mp"

LINUX_VERSION = "6.6.78"
LINUX_VERSION_EXTENSION = "-stm32mp"


## ================================================================
## KERNEL CONFIG
## ================================================================
## arm64 doesnt use "zImage"
KERNEL_IMAGETYPE = "Image"

## by default defconfig is searched inside "arch/<ARCH>/configs/" dir
## KBUILD_DEFCONFIG is a relative path from this dir
KBUILD_DEFCONFIG = "defconfig"
##
## or can be supplied via SRC_URI
#FILESEXTRAPATHS:prepend := "${THISDIR}/defconfig:"
#SRC_URI += "file://defconfig;subdir=git"



## ================================================================
## MACHINE
## ================================================================
## by default dt are searched inside "arch/<ARCH>/boot/dts/" dir
## KERNEL_DEVICETREE is a relative path from this dir
## and must end with .dtb extension
KERNEL_DEVICETREE = "st/stm32mp257f-dk.dtb"

e che produce i seguenti file che ci serviranno per fare il boot:

deploy dir
tmp/deploy/images/stm32mp257f-dk
├── Image 
├── stm32mp257f-dk.dtb 
└── ...

primo boot

adesso procediamo con il boot, quindi per prima cosa dobbiamo posizionare i file Image e dtb all’interno della sdcard. Non esistono vincoli su posizione e label della partizione, ma basta che questa abbia un filesystem riconoscibile da uboot e da linux. Creiamo quindi una partizione formattata con ext4:

flasher.sh
DEVICE="${1:-}"
IMAGE="${2:-}"
DTB="${3:-}"

# Linux filesystem data (ext2/ext4/fat file system)
# TYPE CODE GUID = 0FC63DAF-8483-4772-8E79-3D69D8477DE4

PART=10
sudo sgdisk \
    -n ${PART}:0:+100M \
    -c ${PART}:bootfs \
    -t ${PART}:0FC63DAF-8483-4772-8E79-3D69D8477DE4 \
    -u ${PART}:R \
    "$DEVICE"   

sudo mkfs.ext4 -L "bootfs" "${DEVICE}${PART}"

# CONTENTS
MNTPOINT=/tmp/bootfs
sudo mkdir -p "$MNTPOINT"
sudo mount "${DEVICE}${PART}" "$MNTPOINT"
sudo install -m 644 "$IMAGE"  "$MNTPOINT/Image"
sudo install -m 644 "$DTB"    "$MNTPOINT/dtb"
sudo sync
sudo umount "$MNTPOINT"

adesso colleghiamo la scheda e avviamo da sdcard. Arrivati alla console uboot esaminiamo la sdcard:

u-boot console
STM32MP> mmc list
STM32 SD/MMC: 0 (SD)
STM32 SD/MMC: 1 (eMMC)

STM32MP> mmc part
Partition Map for MMC device 0  --   Partition Type: EFI
Part    Start LBA       End LBA         Name
        Attributes
        Type GUID
        Partition GUID
  1     0x00000800      0x00000bff      "fsbla"
        attrs:  0x0000000000000000
        type:   8da63339-0007-60c0-c436-083ac8230908
                (8da63339-0007-60c0-c436-083ac8230908)
        guid:   3181429f-6c93-4500-bd4d-264f0af4fd4d
  2     0x00001000      0x00002fff      "fip"
        attrs:  0x0000000000000000
        type:   19d5df83-11b0-457b-be2c-7559c13142a5
                (19d5df83-11b0-457b-be2c-7559c13142a5)
        guid:   4db28b33-fe10-4c2b-93be-4b79de8be706
 10     0x00003000      0x00034fff      "bootfs"
        attrs:  0x0000000000000000
        type:   0fc63daf-8483-4772-8e79-3d69d8477de4
                (linux)
        guid:   243f52c4-209b-4cf3-a136-147882bcb87f

la sdcard è nello slot mmc 0 e la partizione bootfs ha indice 10 che in esadecimale corrisponde a A. Siccome uboot utilizza il formato esadecimale per tutti i numeri nei comandi, facciamo un po’ di attenzione. Verifichiamo la presenza di un filesystem nella partizione A e esploriamone i contenuti:

u-boot console
STM32MP> fstype mmc 0:A                                        
ext4      

STM32MP> ls mmc 0:A /                                          
<DIR>       1024 .                                             
<DIR>       1024 ..                                            
        25053192 Image                                         
           88749 dtb  

STM32MP> size mmc 0:A /Image                                   
STM32MP> print filesize                                        
filesize=17e4808           

STM32MP> size mmc 0:A /dtb                                     
STM32MP> print filesize                                        
filesize=15aad 

il comando size assegna alla variabile filesize il valore esadecimale in byte della dimensione del file. Ok, quindi tutto a posto e procediamo col boot. Prima dobbiamo caricare kernel e dtb da disco a RAM e poi fornire a booti gli address in ram di questi binari. Per decidere gli indirizzi possiamo utilizzare le variabili standard di uboot kernel_addr_r e fdt_addr_r come indirizzi di memoria. Una altra variabile comoda è bootargs che definisce gli argomenti da passare al kernel. Vabbe dai proviamo:

u-boot console
STM32MP> load mmc 0:A ${kernel_addr_r} Image  
17770504 bytes read in 943 ms (18 MiB/s)

STM32MP> load mmc 0:A ${fdt_addr_r} dtb 
117096 bytes read in 215 ms (531.3 KiB/s)

STM32MP> setenv bootargs "earlycon ignore_loglevel"

STM32MP> booti ${kernel_addr_r} - ${fdt_addr_r}

utilizzando dei bootargs che indichino al kernel di stampare il log, all’avvio vedremo la console riempirsi di messaggi:

kernel log
## Flattened Device Tree blob at 90000000
   Booting using the fdt blob at 0x90000000
Working FDT set to 90000000
   Loading Device Tree to 000000008ffe0000, end 000000008ffff967 ... OK
Working FDT set to 8ffe0000

Starting kernel ...
[    0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd040]
[    0.000000] Linux version 6.6.78-stm32mp (oe-user@oe-host) (aarch64-oe-linux-gcc (GCC) 13.4.0, GNU ld (GNU Binutils) 2.42.0.20240723) #1 SMP PREEMPT Wed Jun 25 12:03:32 UTC 2025
[    0.000000] KASLR disabled due to lack of seed
[    0.000000] Machine model: STMicroelectronics STM32MP257F-DK Discovery Board
[    0.000000] Reserved memory: created CMA memory pool at 0x00000000f2200000, size 128 MiB

il log finisce quando avviene lo switch da bootconsole a console:

kernel log
[    0.012702] printk: console [tty0] enabled
[    0.016675] printk: bootconsole [stm32] disabled

quindi ai bootargs aggiungiamo la definizione della console seriale che viene inizializzata da linux:

u-boot console
setenv bootargs "console=ttySTM0,115200 earlycon ignore_loglevel"

questo lo possiamo trovare dai documenti STM oppure dal fatto che nel devicetree viene definito stdout-path = "serial0:115200n8" dove serial0 usa il driver st,stm32h7-uart che registra le console seriali in linux con il prefisso ttySTM. La spiegazione è complicata, ma per il momento fidiamoci e poi la vedremo in dettaglio. A conferma di questo, possiamo trovare questo esatto prefisso nei driver definiti nel kernel:

drivers/tty/serial/stm32-usart.h
#define STM32_SERIAL_NAME "ttySTM"

riavviando la scheda, ridefinendo i bootargs e ripetendo il processo, arriviamo ad un kernel panic perchè non è presente un root filesystem:

kernel log
[    2.918219] /dev/root: Can't open blockdev
[    2.918269] VFS: Cannot open root device "" or unknown-block(0,0): error -6
[    2.923726] Please append a correct "root=" boot option; here are the available partitions:
[    2.932190] List of all bdev filesystems:
[    2.936125]  ext3
[    2.936129]  ext4
[    2.938055]  ext2
[    2.939973]  vfat
[    2.941898]
[    2.945330] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)
[    2.953683] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 6.6.78-stm32mp #1
[    2.960328] Hardware name: STMicroelectronics STM32MP257F-DK Discovery Board (DT)
[    2.967875] Call trace:
[    2.970296]  dump_backtrace+0xa0/0x104
[    2.974036]  show_stack+0x14/0x1c
[    2.977367]  dump_stack_lvl+0x44/0x58
[    2.981101]  dump_stack+0x14/0x1c
[    2.984431]  panic+0x2f0/0x34c
[    2.987461]  mount_root_generic+0x228/0x2f4
[    2.991598]  mount_root+0x194/0x25c
[    2.995130]  prepare_namespace+0x54/0x26c
[    2.999164]  kernel_init_freeable+0x228/0x280
[    3.003499]  kernel_init+0x20/0x1d4
[    3.007033]  ret_from_fork+0x10/0x20
[    3.010665] SMP: stopping secondary CPUs
[    3.014597] Kernel Offset: disabled
[    3.018122] CPU features: 0x0,00000000,00000000,0000401b
[    3.023459] Memory Limit: none
[    3.026487] ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0) ]---

Bello. Almeno il kernel qualcosa fa dai.

rootfs in ram / initramfs

lo step finale sarà quello di arrivare ad un rootfs salvato in una partizione su disco, ma per il momento utilizziamo un initramfs, ovvero un root-filesystem che vive su disco, ma viene caricato in ram e poi non salvato. Questo perchè non abbiamo ancora configurato il kernel e dobbiamo prima verificare di riuscire ad interagire correttamente con l’ hardware. In questo modo, invece, sfruttiamo il fatto che uboot è già in grado di lavorare con la sdcard e quindi sarà lui ad effettuare l’operazione di caricare initramfs in ram.

Per generare un initramfs possiamo utilizzare il recipe di Yocto core-image-minimal-initramfs:

terminal
bitbake core-image-minimal-initramfs

nel deploy troveremo il initramfs compresso:

deploy dir
tmp/deploy/images/stm32mp257f-dk
├── core-image-minimal-initramfs-stm32mp257f-dk.cpio.gz 
└── ...

che per poter essere decompresso e usato come filesystem necessita che il kernel sia compilato con delle opzioni specifiche. Questo lo vedremo dopo, quindi per il momento aggiungiamo in local.conf la definizione del tipo di initramfs che vogliamo:

local.conf
INITRAMFS_FSTYPES = "cpio"

ribuildando il recipe otterremo:

deploy dir
tmp/deploy/images/stm32mp257f-dk
├── core-image-minimal-initramfs-stm32mp257f-dk.cpio 
└── ...

copiamo il file nella stessa partizione bootfs che avevamo già creato e da uboot eseguiamo:

u-boot console
STM32MP> load mmc 0:A ${kernel_addr_r} Image
25053192 bytes read in 1248 ms (19.1 MiB/s)

STM32MP> load mmc 0:A ${fdt_addr_r} dtb
117096 bytes read in 215 ms (531.3 KiB/s)

STM32MP> load mmc 0:A ${ramdisk_addr_r} initramfs.cpio
32108544 bytes read in 1550 ms (19.8 MiB/s)

STM32MP> setenv bootargs "console=ttySTM0,115200 earlycon ignore_loglevel"

STM32MP> booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r}

arriveremo a un punto tipo:

kernel log
[    1.386586] ------------[ cut here ]------------
[    1.390816] WARNING: CPU: 1 PID: 1 at /drivers/firmware/arm_scmi/driver.c:3008 scmi_driver_init+0x0/0xc
[    1.400260] Modules linked in:
[    1.403324] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 6.6.78-stm32mp #1
[    1.409968] Hardware name: STMicroelectronics STM32MP257F-DK Discovery Board (DT)
[    1.417490] pstate: 60000005 (nZCv daif -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[    1.424486] pc : scmi_driver_init+0x0/0xc
[    1.428508] lr : do_one_initcall+0x68/0x180
[    1.432704] sp : ffff0000800c7dc0
[    1.436028] x29: ffff0000800c7dc0 x28: 0000000000000000 x27: ffff800081066068
[    1.443198] x26: ffff800080fe00a0 x25: ffff800080f6edd0 x24: ffff800080fc6c90
[    1.450367] x23: ffff800081066078 x22: 0000000000000000 x21: ffff000080098000
[    1.457534] x20: ffff8000817ec000 x19: ffff80008102720c x18: ffff0000800c7cf0
[    1.464702] x17: ffff80008172e9e0 x16: 0000000035eab635 x15: 0000000009f6f035
[    1.471869] x14: 000000007b1a73ba x13: 0000000026ecf358 x12: 00000000335b73b7
[    1.479036] x11: 000000008c1ee96f x10: 00000000b92e1044 x9 : 0000000000000000
[    1.486204] x8 : ffff0000800c7dc0 x7 : 0000000000000000 x6 : 000000000000003f
[    1.493375] x5 : 0000000000000040 x4 : ffffffffffffffd0 x3 : 0000000000000280
[    1.500541] x2 : 0000000000000000 x1 : 0000000000000000 x0 : 0000000000000000
[    1.507711] Call trace:
[    1.510162]  scmi_driver_init+0x0/0xc
[    1.513837]  kernel_init_freeable+0x1c0/0x280
[    1.518212]  kernel_init+0x20/0x1d0
[    1.521713]  ret_from_fork+0x10/0x20
[    1.525302] ---[ end trace 0000000000000000 ]---
...
[    1.584679] registered taskstats version 1
[    6.676016] request_module: modprobe binfmt-464c cannot be processed, kmod busy with 50 threads for more than 5 seconds now
[   11.796022] request_module: modprobe binfmt-464c cannot be processed, kmod busy with 50 threads for more than 5 seconds now
[   16.916008] request_module: modprobe binfmt-464c cannot be processed, kmod busy with 50 threads for more than 5 seconds now
[   22.036012] request_module: modprobe binfmt-464c cannot be processed, kmod busy with 50 threads for more than 5 seconds now
...
[   22.102891] Warning: unable to open an initial console.
[   22.110430] Freeing unused kernel memory: 5312K
[   22.112768] Run /init as init process
[   22.116315]   with arguments:
[   22.119275]     /init
[   22.121555]   with environment:
[   22.124701]     HOME=/
[   22.127052]     TERM=linux
[   22.129989] Failed to execute /init (error -2)
...
[   27.412013] request_module: modprobe binfmt-464c cannot be processed, kmod busy with 50 threads for more than 5 seconds now
[   27.415350] Kernel panic - not syncing: No working init found.  Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance.
...
E/TC:1   Panic 'Watchdog' at /usr/src/debug/optee-os/4.0.0-stm32mp/core/drivers/stm32_iwdg.c:218 <stm32_iwdg_it_handler>
E/TC:1   TEE load address @ 0x82000000
E/TC:1   Call stack:
E/TC:1    0x82007ef0
E/TC:1    0x82043428
E/TC:1    0x8201c110
E/TC:1    0x82040f54
E/TC:1    0x820141e0

Da qui vediamo una serie di errori:

  • /drivers/firmware/arm_scmi/driver.c non riesce a fare init
  • modprobe binfmt-464c cannot be processed quindi il kernel module binfmt-464c non viene caricato
  • No working init found quindi il kernel non riesce a eseguire init del initramfs
  • Panic 'Watchdog' quindi il watchdog protetto da OPTEE scade perchè il kernel non lo riavvia mai

Inoltre anche nel log di avvio di uboot vediamo dei riferimenti a questo watchdog:

kernel log
WDT:   Started watchdog with servicing every 1000ms (32s timeout)

Adesso iniziano i grandi cazzi. Meglio dedicargli un articolo intero.