Iniziamo

Capiamo intanto cosa abbiamo tra le mani.

Come esempio utilizzerò la devboard stm32mp257f-dk, la più cazzuta della serie MP2.

Prima della serie MP2 (uscita nel 2024 ) esisteva la MP1 (uscita nel 2019 ), ed entrambe sono basate su una architettura a microprocessore.

La MP2 dispone di una architettura a 64bit invece che 32bit, molte più periferiche, come quelle dedicate alla crittografia, una gpu integrata, npu per accelerare lavori di AI, …, e una maggiore velocità.

Per andare leggermente più nel dettaglio, la MP2 si basa sul Cortex-A35, mentre la MP1 sul Cortex-A7.

Per gli interessati, vi invito a cercare maggiori informazioni a riguardo per conto vostro. Procediamo.

Architettura

I micro processori della famiglia STM32MP2 sono tutti basati su architettura ARM a 64bit. Nel mondo Linux e delle toolchain in generale, questa architettura è identificata con i nomi:

  • ARM64
  • aarch64
  • armv8

Siccome ARM nasce come architettura a 32bit, di default si assume che il termine “arm” si riferisca a un 32bit, mentre “arm64” si riferisca a un 64bit.

Per completezza, l’architettura a 32bit è solitamente identificata coi nomi:

  • ARM
  • ARM32
  • aarch32
  • armv7

Queste informazioni ci torneranno utili per capire che toolchain utilizzare.

Bootloader

Lo scopo principale del bootloader è inizializzare tutte le periferiche necessarie (RAM, UART, USB controller, watchdogs, MMU, …) e poi passare la palla al kernel Linux, che prenderà in mano il controllo di queste.

Vedremo in realtà che, almeno per questo del soc, alcune periferiche critiche per la sicurezza non saranno gestite da Linux, ma rimarranno in possesso ad un ulteriore componente con il solo scopo di gestirne l’uso sicuro.

Da questo documento di stm capiamo che il micro dispone di un ROM code (quindi non modificabile), che poi necessita di altri componenti per gestire l’inizializzazione del sistema. Questi altri componenti si occupano di

  • inizializzare la RAM esterna (di tipo DDR)
  • inizializzare il contesto di sicurezza del micro (quali zone di memoria sono riservate)
  • inizializzare varie periferiche
  • trovare il kernel linux su disco e avviarlo coi parametri corretti

Non esiste quindi un singolo bootloader, ma piuttosto una bootchain formata da:

  • TFA (Trusted Firmware Arm)
  • OPTEE (Open Portable Trusted Execution Environment)
  • UBOOT (classico bootloader per embedded)

Inoltre, da notare come TFA e UBOOT siano dei componenti che eseguono un determinato task e poi lasciano spazio allo stadio successivo (un po’ come i diversi stadi di un razzo), mentre OPTEE è un componente che rimane vivo fino alla morte del sistema. Quest’ ultimo è definito quindi come componente “runtime”.

A dire il vero, il TFA a sua volta è diviso in due stadi, di cui l’ultimo rimane a runtime.

Generalmente questi componenti vengono indicati con dei nomi generici a seconda dello stadio a cui appartengono (piccola tangente, penso che da adesso utilizzerò il termine stage invece che stadio, perchè lo trovo al quanto orribile).

La differenza principale tra uno stage e un altro è dettata dal dove questo viene eseguito, ovvero se direttamente in ROM (quindi senza necessità di avere una RAM), se dalla SYSRAM (la RAM interna del soc) o se da una RAM (la classica RAM esterna al soc). Inoltre per brevità si usa il termine “bl” per indicare “bootloader”, quindi:

  • bl1 –> bootloader in ROM code (non modificabile)
  • bl2 –> bootloader in SYSRAM (pochi KB disponibili)
  • bl3 –> bootloader in RAM (massima libertà)

Perchè tutto questo casino? Il motivo è che a differenza delle schede PC, in cui il ROM code è un bootloader BIOS o UEFI ed è capace di riconoscere autonamente tutte le periferiche necessarie, nei sistemi embedded, in cui le risorse sono sempre scarse, il bootloader deve configurare il sistema mano a mano. Il ROM code è piccolissimo per tenere i costi del chip bassi, ma tramite il BL2 abbiamo un pochino di flessibilità in più, che ci permette di inizializzare una RAM esterna (di cui il processore al momento non sa NULLA) e poi su questa possiamo fare girare un bootloader un po’ più capiente e capace, con il compito di inizializzare il resto delle periferiche di sistema.

Queste ultime infatti non sono fisse, ma appunto variano a seconda della board su cui il processore viene montato. Il compito principale di un embedded engineer è proprio quello di configurare il software per poter interagire con un hardware custom.

Vedremo che uno degli strumenti più comodi e diffusi è il Device Tree , dove andremo a descrivere il nostro hardware al processore. Questo è un lavoro complesso che richiede una grande conoscenza dei registri interni del processore e di tutte le periferiche ad esso collegate.

Solitamente il nostro vendor ci fornirà un device tree da cui partire, quindi non sarà necessario andare a smadonnare più di tanto. Teniamo conto però che pure il nostro soc ha bisogno di un device tree, perchè sebbene noi lo vediamo come un unico chip, in realtà questo è l’insieme di un processore (il core vero e proprio) e di molte molte periferiche (sysram, uart, usb controller, watchdogs, …).

C’è da uscirne pazzi, sì.

Linux

Quando saremo in grado di arrivare a una shell linux, gran parte del lavoro sarà fatta e resterà da configurare il caricamento dei moduli kernel necessari e l’installazione degli applicativi di cui abbiamo bisogno.

Prima pausa

Il setup del bootloader, del kernel e degli applicativi su Linux possiamo farlo anche al di fuori di Yocto, se ci troviamo meglio (che è anche un ottimo esercizio per capire meglio le difficoltà del processo), La quasi totalità di questo processo la faremo direttamente in Yocto.