TFA

Il Trusted Firmware Arm viene caricato da ROM code all’ interno della SYSRAM (pochi kilobytes) e si deve occupare di inizializzare:

  • RAM DDR
  • secure world monitor
  • SSBL (second stage bootloader)

La questione secure world monitor riguarda OPTEE e altri componenti di sicurezza. Per il momento la saltiamo e ne riparliamo in un articolo a parte.

Il TFA deve quindi fare un bel po’ di cose e operare con una quantità di RAM molto ristretta. La filosofia di base è quindi quella di preparargli una collezioni di file binari contenenti i componenti che dovrà poi caricare in RAM DDR (svariati megabytes). Questa collezione prende il nome di FIP (Firmware Image Package ). Inoltre, TFA si aspetta di trovare tale pacchetto all’interno di una partizione in flash con GUID type e nome preciso.

Il recipe trusted-firmware-a_2.10.4.bb del layer meta-arm utilizza la repository ufficiale del progetto, ma questa non contiene le implementazioni per i processori STM32MP2. Utilizziamo quindi il nostro .bbappend per fare un override della repository e utilizziamo quella di ST:

trusted-firmware-a_%.bbappend
1## STM TFA REPO
2PV := "2.10-stm32mp"
3PR := "r2"
4SRCREV = "v2.10-stm32mp-r2"
5SRC_URI = "git://github.com/STMicroelectronics/arm-trusted-firmware.git;protocol=https;branch=v2.10-stm32mp"

per sistemare i nomi dei package che verranno usati da Yocto, facciamo anche un override di PV e PR. Per capire come procedere, possiamo consultare:

Diciamo che sono in ordine di difficoltà e che alla fine della fiera, chiunque può ipotizzare ciò che vuole, ma la verità è scritta nei sorgenti. Il capirli però richiede un po’ di tempo.

dal recipe originale

Nel recipe la cosa che ci interessa di più è capire cosa succede all’ interno del task bitbake do_compile per vedere che applicativo e che variabili vengono utilizzate per compilare. Vediamo che viene utilizzato make e le variabili vengono passate tramite EXTRA_OEMAKE. Alcune variabili interessanti sono:

  • TFA_PLATFORM
  • TFA_DEBUG
  • TFA_BUILD_TARGET
  • TFA_INSTALL_TARGET

dove TFA_PLATFORM guida direttamente il parametro PLAT del Makefile, mentre TFA_BUILD_TARGET e TFA_INSTALL_TARGET vengono usate come insieme di stringhe che definiscono i target del Makefile e ci si itera dentro.

Queste vengono utilizzate per pilotare le variabili associate a make che poi si occuperà della compilazione del progetto:

trusted-firmware-a.inc
103# Add platform parameter
104EXTRA_OEMAKE += "BUILD_BASE=${B} PLAT=${TFA_PLATFORM}"
trusted-firmware-a.inc
155do_compile() {
156    # This is still needed to have the native tools executing properly by
157    # setting the RPATH
158    sed -i '/^LDOPTS/ s,$, \$\{BUILD_LDFLAGS},' ${S}/tools/fiptool/Makefile
159    sed -i '/^INCLUDE_PATHS/ s,$, \$\{BUILD_CFLAGS},' ${S}/tools/fiptool/Makefile
160    sed -i '/^LIB/ s,$, \$\{BUILD_LDFLAGS},' ${S}/tools/cert_create/Makefile
161
162    # Currently there are races if you build all the targets at once in parallel
163    for T in ${TFA_BUILD_TARGET}; do
164        oe_runmake -C ${S} $T
165    done
166}
trusted-firmware-a.inc
169do_install() {
170    install -d -m 755 ${D}${FIRMWARE_DIR}
171
172    for atfbin in ${TFA_INSTALL_TARGET}; do
173        processed="0"
174        ...
175    done

dalla documentazione

Leggendo la documentazione del TFA troviamo una sezione relativa al porting per stm32mp2 che spiega quali variabili make definire. Innanzitutto dobbiamo selezionare la variante di MPU stm32mp2 corretta definendo una tra queste:

  • STM32MP21
  • STM32MP23
  • STM32MP25

Selezionare il tipo di DDR definendo una tra queste:

  • STM32MP_DDR3_TYPE
  • STM32MP_DDR4_TYPE
  • STM32MP_LPDDR4_TYPE

Definire STM32MP_DDR_FIP_IO_STORAGE per includere il binario di training della DDR all’ interno del FIP. Definire STM32MP_SDMMC se vogliamo che il FIP venga caricato da sdcard. All’ interno della repo possiamo quindi definire tali variabili e utilizzare make per generare il binario del TFA e anche il binario del FIP nel seguente modo:

TFA
1export CROSS_COMPILE=aarch64-none-elf-
2export PLAT=stm32mp2
3export STM32MP_DDR4_TYPE=1
4export SPD=opteed
5export DTB_FILE_NAME=stm32mp257f-dk.dtb
6export STM32MP_SDMMC=1
7export STM32MP_DDR_FIP_IO_STORAGE=1
8
9make
FIP
 1export CROSS_COMPILE=aarch64-none-elf-
 2export PLAT=stm32mp2
 3export STM32MP_DDR4_TYPE=1
 4export SPD=opteed
 5export DTB_FILE_NAME=stm32mp257f-ev1.dtb
 6export BL33=<u-boot_directory>/u-boot-nodtb.bin 
 7export BL33_CFG=<u-boot_directory>/u-boot.dtb 
 8export BL32=<optee_directory>/tee-header_v2.bin 
 9export BL32_EXTRA1=<optee_directory>/tee-pager_v2.bin
10STM32MP_DDR_FW_PATH=drivers/st/ddr/phy/firmware/bin
11
12make fip

Questo è utile nel caso volessimo compilare manualmente il TFA, bypassando Yocto oppure utilizzando devshell sul recipe. Soprattutto, però, ci aiuta a capire dove focalizzare la nostra attenzione per riuscire a far funzionare sto benedetto TFA. Da notare inoltre che, siccome il TFA ha il compito di inizializzare UBOOT e OPTEE, è necessario disporre dei risultati della compilazione di questi altri due progetti per poterli inglobare all’ interno del FIP. Quindi dovremmo capire prima come fare almeno un setup grezzo di questi, ma per il momento amen.

dai sorgenti

Passando poi all’ analisi dei sorgenti, notiamo un Makefile principale che orchestra la build e che richiama un platform.mk specifico per la piattaforma definita da PLAT. Nel nostro caso PLAT=stm32mp2 e i makefiles che ci interessano sono:

  • Makefile
  • plat/st/stm32mp2/platform.mk
  • plat/st/common/common.mk
tfa directory layout
arm-trusted-firmware
├── Makefile
├── plat 
│    ├── arm
│    ├── common
│    ├── rpi
│    ├── ...
│    └── st
│         ├── common
│         │    ├── ...
│         │    └── common.mk
│         ├── stm32mp1
│         │    └── ...
│         └── stm32mp2
│              ├── ...
│              └── platform.mk
│    
├── drivers
├── fdts
└── ...

in questi troviamo variabili che ci possono tornare utili come:

arm-trusted-firmware/plat/st/common/common.mk
 7RESET_TO_BL2                :=  1
 8
 9STM32MP_EARLY_CONSOLE		?=  0
10STM32MP_RECONFIGURE_CONSOLE	?=  0
11STM32MP_UART_BAUDRATE		?=  115200
arm-trusted-firmware/plat/st/common/common.mk
32# Boot devices
33STM32MP_EMMC			?=	0
34STM32MP_SDMMC			?=	0
35STM32MP_RAW_NAND		?=	0
36STM32MP_SPI_NAND		?=	0
37STM32MP_SPI_NOR			?=	0
38
39# Put both BL2 and FIP in eMMC boot partition
40STM32MP_EMMC_BOOT		?=	0
41
42# Serial boot devices
43STM32MP_UART_PROGRAMMER		?=	0
44STM32MP_USB_PROGRAMMER		?=	0
arm-trusted-firmware/plat/st/stm32mp2/platform.mk
47# Default Device tree
48DTB_FILE_NAME			?=	stm32mp257f-ev1.dtb
49
50STM32MP21			?=	0
51STM32MP23			?=	0
52STM32MP25			?=	0
53
54STM32MP2X			:=	1
arm-trusted-firmware/plat/st/stm32mp2/platform.mk
137# DDR types
138STM32MP_DDR3_TYPE		?=	0
139STM32MP_DDR4_TYPE		?=	0
140STM32MP_LPDDR4_TYPE		?=	0
arm-trusted-firmware/plat/st/stm32mp2/platform.mk
32# Default Device tree
33DTB_FILE_NAME			?=	stm32mp257f-ev1.dtb
34
35STM32MP25			:=	1

e, ancora più interessante, i target make:

arm-trusted-firmware/plat/st/common/common_rules.mk
67tf-a-%.bin: tf-a-%.elf
68	${Q}${OC} -O binary $< $@
69	@echo
70	@echo "Built $@ successfully"
71	@echo
72
73tf-a-%.stm32: tf-a-%.bin ${STM32_DEPS}
74	@echo
75	@echo "Generate $@"
76	$(eval LOADADDR = $(shell cat $(@:.stm32=.map) | grep '^RAM' | awk '{print $$2}'))
77	$(eval ENTRY = $(shell cat $(@:.stm32=.map) | grep "__BL2_IMAGE_START" | awk '{print $$1}'))
78	${Q}${STM32IMAGE} -s $< -d $@ \
79		-l $(LOADADDR) -e ${ENTRY} \
80		-v ${STM32_TF_VERSION} \
81		-m ${STM32_HEADER_VERSION_MAJOR} \
82		-n ${STM32_HEADER_VERSION_MINOR} \
83		-b ${STM32_HEADER_BL2_BINARY_TYPE}
84	@echo

Quindi, dopo aver analizzato la repo del TFA in lurgo e in largo, possiamo aggiungere al nostro .bbappend le istruzioni per generare i binari che ci interessano. Dobbiamo dire a make di fare il build dei target:

  • tf-a-%.stm32
  • fip

dai makefile si vede che % prende il valore DTB_FILE_NAME senza l’ estensione .dtb. Possiamo usare il target all, ma rimane necessario esplicitare comunque il target fip. Nella cartella di build verranno generati i file:

  • tf-a-%.stm32
  • fip.bin

e questi sono i binari di TFA e FIP che ci servono. Nel nostro .bbappend queste operazioni sono pilotate dalle variabili TFA_BUILD_TARGET e TFA_INSTALL_TARGET perciò possiamo usare:

trusted-firmware-a_%.bbappend
 7TFA_BUILD_TARGET = "all fip"
 8
 9TFA_INSTALL_TARGET = "\
10    tf-a-stm32mp257f-dk.stm32 \
11    fip.bin \
12    "

Per configurare la compilazione come descritto nella documentazione TFA invece possiamo agire direttamente su OEMAKE:

trusted-firmware-a_%.bbappend
 7EXTRA_OEMAKE:append = " \
 8    PLAT=stm32mp2 \
 9    STM32MP_SDMMC=1 \
10    STM32MP_LPDDR4_TYPE=1 \
11    STM32MP_DDR_FIP_IO_STORAGE=1 \
12    SPD=opteed \
13    STM32MP25=1 \
14    DTB_FILE_NAME=stm32mp257f-dk.dtb \
15    "

E per definire la dipendenza tra la generazione del FIP, la compilazione di UBOOT e OPTEE e il successivo uso di questi binari:

trusted-firmware-a_.bbappend
 7DEPENDS:append = " optee-os"
 8EXTRA_OEMAKE:append = " \
 9    BL32=${STAGING_DIR_TARGET}${nonarch_base_libdir}/firmware/tee-header_v2.bin \
10    BL32_EXTRA1=${STAGING_DIR_TARGET}${nonarch_base_libdir}/firmware/tee-pager_v2.bin \
11    "
12
13
14DEPENDS:append = " u-boot"
15do_compile[depends] += "u-boot:do_deploy"
16EXTRA_OEMAKE:append = " \
17    BL33=${STAGING_DIR_TARGET}/boot/u-boot-nodtb.bin \
18    BL33_CFG=${STAGING_DIR_TARGET}/boot/u-boot.dtb \
19    "

recipe finale

Queste sono a grandi linee le istruzioni per poter configurare TFA, ma si può fare di meglio. Ad esempio al momento stiamo forzando il boot da sdcard e non stiamo dando l’opzione di poterlo fare da emmc o da usb. Stiamo utilizzando il devicetree disponibile nella repo, ma nel caso volessimo gestire una board leggermente diversa dobbiamo capire come modificare il devicetree. Stiamo usando molti nomi “hardcoded” quando potremmo gestire molti casi in maniera più parametrica. Insomma si possono fare un sacco di migliorie, ma a noi interessava capire il concetto. Lasciamo qua il nostro .bbappend grezzo, ma funzionale:

trusted-firmware-a_.bbappend
 7## STM TFA REPO
 8PV := "2.10-stm32mp"
 9PR := "r2"
10SRCREV = "v2.10-stm32mp-r2"
11SRC_URI = "git://github.com/STMicroelectronics/arm-trusted-firmware.git;protocol=https;branch=v2.10-stm32mp"
12
13TFA_PLATFORM = "stm32mp2"
14TFA_BUILD_TARGET = "all fip"
15TFA_INSTALL_TARGET = "\
16    tf-a-stm32mp257f-dk.stm32 \
17    fip.bin \
18    "
19
20EXTRA_OEMAKE:append = " \
21    PLAT=stm32mp2 \
22    STM32MP_SDMMC=1 \
23    STM32MP_LPDDR4_TYPE=1 \
24    STM32MP_DDR_FIP_IO_STORAGE=1 \
25    SPD=opteed \
26    STM32MP25=1 \
27    DTB_FILE_NAME=stm32mp257f-dk.dtb \
28    "
29
30DEPENDS:append = " optee-os"
31EXTRA_OEMAKE:append = " \
32    BL32=${STAGING_DIR_TARGET}${nonarch_base_libdir}/firmware/tee-header_v2.bin \
33    BL32_EXTRA1=${STAGING_DIR_TARGET}${nonarch_base_libdir}/firmware/tee-pager_v2.bin \
34    "
35
36
37DEPENDS:append = " u-boot"
38do_compile[depends] += "u-boot:do_deploy"
39EXTRA_OEMAKE:append = " \
40    BL33=${STAGING_DIR_TARGET}/boot/u-boot-nodtb.bin \
41    BL33_CFG=${STAGING_DIR_TARGET}/boot/u-boot.dtb \
42    "