UBOOT
Ed eccoci qua, il vero cuore del bootloader.
U-BOOT è l’ ultimo stage della nostra bootchain
ed è responsabile dell’ inizializzazione
delle altre periferiche che useremo,
ma soprattuto è quello che prepara l’ ambiente
e poi passa l’ esecuzione al kernel linux.
E’ un progetto molto modulare
e con una valanga di funzionalità.
Più funzionalità incorporiamo
e più la dimensione del binario finale aumenta.
Questo può essere un problema se non abbiamo
chissà quanta memoria (RAM) disponibile.
Nel nostro caso ci va di lusso,
perchè il TFA ha già inizializzato la RAM DDR
e quindi non abbiamo problemi di spazio.
Una altra cosa interessante riguardo la repository
è l’utilizzo di Kconfig
per poter gestire la presenza e il valore
dei vari #define presenti dentro al codice.
Kconfig è un sistema molto utilizzato
nel kernel linux perchè permette
di gestire dipendenze anche complicate
tra molteplici #define.
Per configurarlo solitamente si utilizzano
degli applicativi grafici o da terminale come menuconfig.
Questi poi generano come output un file .config
che contiene tutte le definizioni necessarie al progetto.
In Yocto esistono .bbclass apposite per
poter interagire con tali applicativi.
Questo per dire che nel recipe UBOOT
non punteremo ad utilizzare variabili make
per configurare il progetto,
ma a configurare questo file .config.
Per configurare UBOOT ci basiamo su:
- il recipe originale u-boot_2024.01.bb
- la documentazione di UBOOT
- i sorgenti della repository UBOOT
dal recipe originale
Il recipe per UBOOT è particolarmente difficile,
perchè presenta molti meccanismi per poter
gestire la configurazione dell’ applicativo.
Vengono usate molteplici .bbclass
e una marea di variabili.
La maggior parte delle variabili bitbake disponibili
sono definite in uboot-config.bbclass.
23# Some versions of u-boot use .bin and others use .img. By default use .bin
24# but enable individual recipes to change this value.
25UBOOT_SUFFIX ??= "bin"
26UBOOT_BINARY ?= "u-boot.${UBOOT_SUFFIX}"
27UBOOT_BINARYNAME ?= "${@os.path.splitext(d.getVar("UBOOT_BINARY"))[0]}"
28UBOOT_IMAGE ?= "${UBOOT_BINARYNAME}-${MACHINE}-${UBOOT_VERSION}.${UBOOT_SUFFIX}"
29UBOOT_SYMLINK ?= "${UBOOT_BINARYNAME}-${MACHINE}.${UBOOT_SUFFIX}"
30UBOOT_MAKE_TARGET ?= "all"Una variabile interessante è UBOOT_MACHINE,
che viene usata come target del Makefile:
23uboot_configure () {
24 if [ -n "${UBOOT_MACHINE}" ]; then
25 oe_runmake -C ${S} O=${B} ${UBOOT_MACHINE}
26 else
27 oe_runmake -C ${S} O=${B} oldconfig
28 fi
29 merge_config.sh -m .config ${@" ".join(find_cfgs(d))}
30 cml1_do_configure
31}Notiamo anche la presenza di
merge_config.sh e l’utilizzo di cml1.
Non entriamo nei dettagli, ma
entrambe sono legate alla configurazione del progetto
tramite Kconfig.
dalla documentazione
La versione UBOOT utilizzata non supporta stm32mp2
e la documentazione tratta solo la serie stm32mp1,
quindi non abbiamo granchè.
Parliamo quinid un attimo del processo generico
di build di UBOOT.
Ogni board o configurazione desiderata
è definita da un file chiamato defconfig.
Configuriamo il progetto
passando tale file come target di make:
23make <defconfig_file>Facciamo partire la compilazione usando il target all
e definendo eventuali altre variabili make:
23make DEVICE_TREE=<name> alldai sorgenti
Spieghiamo un attimo come è organizzata la repo:
u-boot
├── Makefile
├── Kconfig
├── ...
├── configs
│ ├── ...
│ └── stm32mp25_defconfig
├── board
│ ├── ...
│ └── st
│ ├── ...
│ ├── common
│ │ ├── ...
│ │ └── Kconfig
│ └── stm32mp2
│ ├── ...
│ └── Kconfig
└── arch
├── Kconfig
├── x86
├── xtensa
├── riscv
├── ...
└── arm
├── Makefile
├── config.mk
├── Kconfig
├── dts
│ ├── ...
│ └── stm32mp257f-dk.dts
├── ...
└── mach-stm32mp
├── Makefile
├── Kconfig
├── Kconfig.13x
├── ...
├── Kconfig.25x
└── stm32mp2
├── ...
└── Makefile
I file Kconfig definiscono
i vari menù e scelte che possiamo fare
e ci permettono di dare una struttura gerarchica
ai classici #define.
Un Kconfig può includere un altro Kconfig.
I Makefile allora possono focalizzarsi
sulla compilazione dei file a seconda
di quali definizioni sono presenti.
Una particolarità del meccanismo,
è che i simboli che
nei file Kconfig vediamo come STM32MP25X,
li ritroviamo nel codice e nei Makefile con il prefisso CONFIG_,
quindi come CONFIG_STM32MP25X:
23config STM32MP25X
24 bool "Support STMicroelectronics STM32MP25x Soc"
25 select ARM64
26 select CLK_STM32MP25
27 select OF_BOARD
28 select OF_BOARD_SETUP
29 select PINCTRL_STM32
30 select STM32_RCC15obj-$(CONFIG_STM32MP25X) += stm32mp2/
16
17obj-$(CONFIG_STM32MP_TAMP_NVMEM) += nvram.o
18obj-$(CONFIG_MFD_STM32_TIMERS) += timers.o
19
20obj-$(CONFIG_STM32_ECDSA_VERIFY) += ecdsa_romapi.o
21ifndef CONFIG_SPL_BUILD
22obj-y += cmd_stm32prog/
23obj-$(CONFIG_CMD_STM32KEY) += cmd_stm32key.o
24obj-$(CONFIG_TFABOOT) += boot_params.o
25endif15#ifdef CONFIG_STM32MP25X
16#define STM32_UART9_BASE 0x402C0000
17#endifAlcune variabili Kconfig assumono un valore,
mentre altre sono definite come booleani
e vengono espanse in y oppure n.
Generalmente ogni Kconfig si occupa
di includere tutti i Kconfig da lui dipendenti,
anche se non necessari alla build attuale,
e i simboli specifici per un target saranno
condizionati dalla presenza o meno di altri simboli.
In questo modo vengono attivati i possibili sottomenù
per la famiglia una specifica famiglia, come stm32mp.
Nel nostro caso la catena di inclusione dei Kconfig è questa:
Kconfig
↓
arch/Kconfig
↓
arch/arm/Kconfig
↓
arch/arm/mach-stm32mp/Kconfig
↓ if ARCH_STM32MP
arch/arm/mach-stm32mp/Kconfig.25x
↓ if STM32MP25X
board/st/stm32mp2/Kconfig
↓ if TARGET_ST_STM32MP25X
board/st/common/KconfigNotiamo che alcune inclusioni sono condizionate dalla presenza di simboli come:
ARCH_STM32MPSTM32MP25XTARGET_ST_STM32MP25X
e non è necessario che questi vengano effettivamente utilizzati nel codice, ma possono rimanere delle variabili dedicate solo a Kconfig.
L’insieme delle definizioni e dei valori
di questi simboli rappresentano
la configurazione per un particolare target
e vengono solitamente chiamati defconfig (default config).
Passando stm32mp25_defconfig
come target di make
viene generato un file .config
a partire dal file configs/stm32mp25_defconfig.
Il file .config contiene la configurazione
attuale ed è quello che verrà
utilizzata per la compilazione.
Per apportare modifiche a questo
è consigliato utilizzare dei comandi specifici
come make menuconfig.
Infine ci basterà usare make all
per ottenere i file binari desiderati.
Esempio di file defconfig:
1CONFIG_ARM=y
2CONFIG_USE_ARCH_MEMCPY=y
3CONFIG_ARCH_STM32MP=y
4CONFIG_SYS_MALLOC_F_LEN=0x500000
5CONFIG_CUSTOM_SYS_INIT_SP_ADDR=0x90000000
6CONFIG_ENV_OFFSET=0x900000
7CONFIG_ENV_SECT_SIZE=0x40000
8CONFIG_DEFAULT_DEVICE_TREE="stm32mp257f-ev1"
9CONFIG_STM32MP25X=yAlcuni dei binari generati:
u-boot(binario in formato ELF per debugging su host)u-boot.bin(binario da flashare sul target)u-boot-nodtb.bin(binario senza devicetree da flashare)u-boot-dtb.bin(identico a u-boot.bin)u-boot.dtb(devicetree utilizzato)
recipe finale
Anche per uboot utilizziamo le repo di STM, quindi definiamo:
1SRC_URI = "git://github.com/STMicroelectronics/u-boot.git;protocol=https;nobranch=1"
2# SRCREV = "v2023.10-stm32mp-r2"
3SRCREV = "64beae9fed51f49b53f952d00d9918b9eb3b040b"
4
5PV := "2023.10-stm32mp"
6PR := "r2"Se il *_defconfig per la nostra board
è già presente nella repo, ci basta quindi
definire UBOOT_MACHINE = "stm32mp25_defconfig".
Già dal estratto di file che abbiamo qua sopra,
notiamo che il device tree definito come default non è
quello per la scheda stm32mp257f-dk
ma per la stm32mp257f-ev1.
Uboot permette di cambiare rapidamente
la scelta del device tree utilizzando
la variabile Make DEVICE_TREE,
evitando quindi di dover cambiare il defconfig.
Per il nostro recipe possiamo quindi utilizzare:
7UBOOT_MACHINE = "stm32mp25_defconfig"
8
9# override CONFIG_DEFAULT_DEVICE_TREE
10EXTRA_OEMAKE:append = " \
11 DEVICE_TREE=stm32mp257f-dk \
12 "Se abbiamo necessità di apportare modifiche
alle definizioni possiamo invece creare una copia
del defconfig, modificarla,
posizionarla all’interno della cartella del recipe
e utilizzare SRC_URI per aggiungere questo file
alla nostra repo.
Ad esempio:
7# add a new defconfig and device-tree
8SRC_URI += " \
9 file://configs/custom_defconfig;subdir=git \
10 file://arch/arm/dts/custom_board_v1.dts;subdir=git \
11 "
12
13# the defconfig must reference the new device-tree
14UBOOT_MACHINE = "custom_defconfig"Inoltre, TFA richiede
il binario di uboot senza devicetree (u-boot-nodtb.bin)
e il binario del solo devicetree (u-boot.dtb),
per poterli includere nel FIP.
La ricetta originale di uboot prevede
delle variabili per copiare determinati
binari, però lo trovo tutto molto arzigogolato
quindi rimpiazziamo completamente
le funzioni di install e deploy
e dichiariamo direttamente quello che ci interessa
ottenere come risultato da questo recipe:
1do_install(){
2 install -d ${D}/boot/
3 install -m 644 ${B}/u-boot-nodtb.bin ${D}/boot/
4 install -m 644 ${B}/u-boot.dtb ${D}/boot/
5}
6
7do_deploy(){
8 # install -D -m 644 ${B}/u-boot-nodtb.bin ${DEPLOYDIR}/
9 # install -D -m 644 ${B}/u-boot.dtb ${DEPLOYDIR}/
10}Et voilà.