
Reverse engineering the TI AM3358 boot ROM
Voilà probablement dix-huit mois que j'ai mis la main sur quelques cartes Beaglebone Black, sauvées de la benne. Malheureusement, les cartes ne fonctionnaient pas directement. En même temps, c'était la première fois que je travaillais avec l'une de ces cartes, ou avec un ordinateur à carte unique d'ailleurs, donc je n'étais pas sûr que le problème vienne de ce que je faisais, ou des cartes elles-mêmes (peut-être la raison pour laquelle elles étaient destinées à la benne en premier lieu). Il m'a fallu beaucoup de temps et d'efforts pour que ces cartes démarrent réellement, mais bon sang, j'y suis arrivé. Et voici ce que j'ai appris en chemin.
J'ai inclus quelques utilitaires dans ce dépôt, ainsi qu'un fichier xml exporté depuis Ghidra qui contient tous les symboles que j'ai obtenus jusqu'à présent grâce à la rétro-ingénierie. J'ai utilisé ce post pour exporter sans le firmware réel, afin d'éviter tout problème de droits d'auteur, par précaution. Si vous voulez déboguer la boot ROM vous-même, vous aurez déjà le JTAG branché, vous pouvez donc extraire la boot ROM (de 0x20000 à 0x2BFFF) par vous-même.
Pour charger les symboles :
0x20000 dans Options, et vous pouvez définir le nom du bloc sur bootrom.main(), ou le gestionnaire de démarrage MMC/carte SD.Pour commencer, je savais que ces cartes étaient des versions personnalisées de la beaglebone black standard, j'ai donc déterminé assez tôt que quelque chose pouvait manquer sur la carte elle-même, comme un identifiant de carte. Ce que j'ai vu en démarrant une carte SD standard formatée avec balenaEtcher, c'était tout simplement rien. Je m'attendais à ce que les LED de la carte commencent à clignoter, et à ce que le fait de brancher un câble UART vers USB me permette de voir le processus U-Boot. Cependant, l'UART était silencieux. Si je retirais la carte SD, elle émettait la lettre C en boucle, ce qui est un comportement attendu pour un démarrage UART/série. Elle essayait définitivement de démarrer, et la carte SD modifiait ce comportement, mais je n'avais pas plus de visibilité que cela. La plupart des dépannages sur le web prenaient la sortie d'U-Boot comme point de départ pour diagnostiquer les problèmes. Je suppose que je n'allais pas avoir ce luxe.
J'ai alors compris qu'il serait utile de brancher une sonde de débogage. Malheureusement, je n'avais pas de connecteur compatible avec l'empreinte existante, alors j'ai fabriqué le mien.
La carte beaglebone possède un connecteur portant la désignation P2 qui sort les connexions JTAG. J'y ai connecté des fils jusqu'à un connecteur femelle afin de pouvoir communiquer avec elle via mon J-Link.



En lançant Ozone (le débogueur Segger), j'ai configuré le J-Link et j'ai commencé par simplement essayer de trouver le point d'entrée. Je pensais qu'un reset-halt me placerait là où il fallait, ce qui m'a conduit à l'hypothèse (incorrecte) que le point d'entrée était 0x2148a, même si j'ai certainement remarqué que ce n'était pas cohérent. Plus tard, j'ai réalisé que les cartes AM335x ne s'entendent pas vraiment bien avec le reset-halt du J-Link, il y avait donc en réalité un délai de quelques centaines de cycles d'horloge, ce qui m'atterrissait quelque part à l'intérieur d'un gestionnaire de démarrage, de manière non déterministe. (J'ai finalement contourné ce problème en écrivant un fichier GEL pour le Code Composer Studio de TI, qui prend en charge le débogage J-Link - à la réinitialisation, le registre PC est défini sur le gestionnaire de réinitialisation, les registres sont effacés, et le mode d'instruction est forcé en ARM.)
À partir d'un fil de discussion sur les forums TI (AM335x : les employés de TI, où puis-je obtenir le code source/les symboles du ROM Bootloader ?), j'ai récupéré quelques symboles de débogage : SPI Initialize à 0x231e0, SPI ReadSectors à 0x23230, et 0x24bfa est une routine qui effectue une lecture UART. C'est une aide non négligeable, je suppose. J'ai remarqué que le démarrage échouait en aboutissant dans une boucle infinie à 0x402f0440, une boucle morte. Hmm, assez loin du reste de la boot ROM, cela doit être dans la RAM ou quelque chose comme ça. Il est probablement temps de se tourner vers le manuel de référence technique (TRM) !
Le chapitre 26 du TRM contient énormément d'informations sur le démarrage. Nous obtenons la vue suivante de la boot ROM :

Description:
L'architecture du Public ROM Code est illustrée dans la Figure 26-1. Elle est divisée en trois couches principales selon une approche descendante : haut niveau, pilotes et couche d'abstraction matérielle (HAL). Chaque couche communique avec une couche de niveau inférieur via une interface unifiée. La couche de haut niveau est chargée des tâches principales du Public ROM Code : configuration du chien de garde et des horloges, et routine principale de démarrage. La couche des pilotes implémente les protocoles logiques et de communication pour tout périphérique de démarrage conformément à la spécification de l'interface. Enfin, la HAL implémente le code de plus bas niveau pour interagir avec les IP de l'infrastructure matérielle. Les périphériques de démarrage finaux sont connectés aux pads d'E/S du dispositif.

La Figure 26-2 illustre le flux de haut niveau de la procédure de démarrage du Public ROM Code. Sur ce dispositif, le Public ROM Code démarre à la fin du démarrage sécurisé (effectué par le Secure ROM Code). Le ROM Code effectue ensuite la configuration et l'initialisation de la plateforme dans le cadre de la procédure de démarrage publique. La liste des périphériques de démarrage est créée en fonction des broches SYSBOOT. Un périphérique de démarrage peut être un périphérique de démarrage mémoire (mémoire flash soudée ou périphérique de démarrage temporaire comme une carte mémoire) ou une interface périphérique connectée à un hôte. La boucle principale de la procédure de démarrage parcourt la liste des périphériques de démarrage et tente de rechercher une image à partir du périphérique de démarrage actuellement sélectionné. Cette boucle est quittée si une image de démarrage valide est trouvée et exécutée avec succès, ou à l'expiration du chien de garde. La procédure d'authentification de l'image est effectuée avant l'exécution de l'image sur un dispositif HS. L'échec de la procédure d'authentification entraîne une branchement vers une « boucle morte » dans le Secure ROM (en attendant une réinitialisation du chien de garde).
Carte mémoire ! Vecteurs d'exceptions ! Organigrammes ! Énormément d'informations dans cette section. Ma tâche vient de devenir beaucoup plus facile.
À ce stade, j'ai utilisé la sonde JTAG pour télécharger le firmware dans deux ou trois fichiers différents, et j'ai commencé à charger les choses dans Ghidra. Il ne semblait y avoir aucun fichier SVD ni d'autres mappages de registres disponibles dans un format pratique, ce qui est vraiment dommage, car cela signifie que je dois définir manuellement les régions mémoire, les registres et tout le reste. C'était un processus fastidieux, mais après un certain temps, j'avais un script python que je pouvais utiliser pour charger les symboles dans Ghidra pour l'AM3358. Un souci de moins !
Il semble que les fichiers dont je dispose puissent être mappés comme suit :
Il est intéressant de noter que la boucle infinie à 0x402f_0440 se trouve au début de l'« image téléchargée » dans la SRAM interne, tandis que les vecteurs d'exceptions sont stockés ailleurs. C'est peut-être un indice important pour la suite...
À la réinitialisation, la boot ROM privée gère les aspects de sécurité et bifurque vers 0x2 0000, qui contient les vecteurs de réinitialisation. La première instruction est un branchement vers 0x2 08d0, qui doit être le point d'entrée. Ce n'est pas une instruction BX, donc, vraisemblablement, nous sommes toujours en mode ARM à ce moment-là.
C'est le premier code exécuté, ce qui signifie que ce n'est pas vraiment une « fonction » avec des paramètres, mais plutôt un script de démarrage généré par compilation. Le premier bloc de base :```arm ldr r4,[->Peripherals::CM_PER] mov r0,#0x2c ldr r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL mov r6,#0x2 str r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL mov r0,#0x2c poll: ldr r6,[r4,r0]=>CM_PER.CM_PER_OCMCRAM_CLKCTRL cmp r6,#0x2 bne poll
Ce bloc définit l'horloge de la RAM OCMC sur activée :
1. Définir `CM_PER_OCMCRAM_CLKCTRL=0x2`
2. Vérifier si le registre a été défini ; si ce n'est pas le cas, continuer à interroger
Le registre `CM_PER_OCMCRAM_CLKCTRL` utilise les bits 0 et 1 pour le champ `MODULEMODE` ; le définir `=0x2` active l'horloge de la RAM OCMC.
Le bloc de base suivant :```arm
ldr r0,[PTR_control_status]
ldr r0,[r0,#0x0]=>control_status
and r0,r0,#0x700
mov r0,r0, lsr #0x8
cmp r0,#0x3
bne skip
ldr r0,[PTR_control_status]
ldr r0,[r0,#0x0]=>control_status
cpy r6,r0
and r0,r0,#0x1f
cmp r0,#0x1f
bleq GPMIC_init
skip: ...
Ce bloc fait ce qui suit :
(control_status & 0x700) >> 8 == 0x3, sauter si ce n'est pas le cascontrol_status & 0x1f == 0x1f, si c'est le cas, appeler la fonction GPMC_init après avoir chargé control_status dans r6Le bloc suivant configure le coprocesseur :```arm msr cpsr_c,#0xd3 ldr r4,[->Exceptions::ROM_RESET_VECTOR] mcr p15,0x0,r4,cr12,cr0,0x0 bl LAB_00020934 bl LAB_00020938 bl LAB_0002093c bl LAB_00020940 bl LAB_00020944 bl LAB_00020948 bl LAB_0002094c bl LAB_00020950 mrc p15,0x0,r0,cr1,cr0,0x0 orr r0,r0,#0x800 mcr p15,0x0,r0,cr1,cr0,0x0 b LAB_000207f0
Opérations dans ce bloc :
1. Charger `11010011b` dans le champ de contrôle du CPSR (`I=1`,`F=1`,`T=0`,`MODE=10011`)
1. `I` est la désactivation des interruptions, `F` est la désactivation des interruptions rapides (donc `I=F=1` signifie que les interruptions sont désactivées)
2. `T` est le mode Thumb, mis à `0`
3. `MODE=10011` configure le mode processeur en mode Superviseur ([ref](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/ARM-processor-modes?lang=en#CIHGHDGI))
4. Voir [ici](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-core-registers/Program-Status-Registers--PSRs-) pour plus d'informations
2. Charger l'adresse du vecteur de réinitialisation ROM
3. Accéder au [coprocesseur 15](https://developer.arm.com/documentation/den0013/d/ARM-Processor-Modes-and-Registers/Registers/Coprocessor-15) (coprocesseur de contrôle système), au registre des extensions de sécurité `c12`, et charger le vecteur de réinitialisation ROM dans le `VBAR` (registre d'adresse de base des vecteurs)

4. On dirait des sauts `nop` ? Pourquoi `bl` plutôt que `b` ?
5. Activer la prédiction de branchement (mettre le bit 11 du registre de contrôle système `SCTLR`)

*Registres `c1` du CP15 (registres de contrôle système) dans l'implémentation VMSA*
Description du registre `SCTLR` :
> Le SCTLR fournit le contrôle de plus haut niveau du système, y compris de son système mémoire.
> Ce registre fait partie du groupe fonctionnel des registres de contrôle de la mémoire virtuelle.
Voir la page B4-1687 du TRM. Le bit 11 est le bit d'*activation de la prédiction de branchement* ; le définir active signifie que la [prédiction de branchement](https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/Common-Memory-System-Architecture-Features/Caches/Branch-predictors) est activée.
6. Appeler une fonction (via un branchement vers une instruction d'appel)
### Fonction à `0x20894` (`__main`)
Cette fonction est atteinte par branchement depuis une autre routine précoce. Je pense qu'elle initialise la pile, et éventuellement les temporisateurs ou le chien de garde, avant d'appeler `FUN_0002889c` (qui se révélera être `main()` !).```arm
ldr sp,[->RESERVED_EXCEPTION_BRANCH] ; 0x4030ce00
blx load_stack_1
ldr r12,[DWORD_1]
add r12,r12,pc
tst r12,#0x1
adrne lr,0x208bd
cpyeq lr,pc
bx r12 ;=>init_timers_maybe
adr r12,0x208bd
bx r12 ;=>LAB_000208bc
000208bc bl FUN_0002889c
000208c0 ddw 0x109
000208c4 addr RESERVED_EXCEPTION_BRANCH
...
RESERVED_EXCEPTION_BRANCH:
ldr pc=>LAB_00020090,[PTR_LAB_4030ce20]
; 20090 is a dead loop
Les opérations ici sont :
r0,r1,r2,r3,r4,lr sur la pile (pile publique dans la carte mémoire)
load_stack_1 branche vers une fonction vide bx lr, puis dépile r0,r1,r2,r3,r4,pc de la pile (remet essentiellement ces données dans les registres et place ce que contient lr dans pc pour revenir)pc + 0x109 est impair ; s'il est pair, charger 0x208bd dans lr, sinon copier pc dans lrFUN_0002889cCela correspond à la fonction __main() mentionnée dans l'organigramme de démarrage :

Cela ferait de la fonction suivante la fonction main.
Comme indiqué en haut de la figure 26-8, le CPU saute vers le vecteur de réinitialisation du code ROM public une fois qu'il a terminé l'initialisation du démarrage sécurisé. Une fois en mode public, au démarrage du système, le CPU effectue l'initialisation côté public et la configuration de la pile (initialisation C générée automatiquement par le compilateur ou « chargement dispersé »). Ensuite, il configure le chien de garde 1 (réglé sur trois minutes), effectue la configuration des horloges système. Enfin, il saute vers la routine de démarrage.
0x209b0)Lorsque main est appelée, le registre SP pointe vers 0x4030ce00. C'est ici que commence la pile, et elle descend vers 0x4030 b800 ; et comme nous pointons vers l'adresse 0x4030 cdf0 après avoir poussé 4 registres (une différence de 16 octets ou 4 mots), nous utilisons une pile pleine descendante, comme dans AAPCS. Autrement dit, SP pointe vers le mot le plus récent de la pile et descend.
Voici la fonction main() décompilée :```c
int main()
{
uint local_10;
uint local_c;
local_c = 0; local_10 = 0; check_stack_prm(&local_10); update_coldreset_tracing_vector(local_10); update_current_tracing_vector(1); main_clock_init(6,0); watchdog_softreset(); watchdog_write_disable_seq_data2(); set_watchdog(300000); if ((local_10 & 1) != 0) { update_current_tracing_vector(2); local_c = local_c & 0xffff | 1; } timer_func_1(); clock_init_func_4(&local_c); run_booting_loop(&local_c,local_10 & 0xff); return 0; }
Most interesting for my purposes is the `run_booting_loop` function at `0x20a10`.
### Notes sur X-Loader
Après avoir parcouru le processus de démarrage et atteint cette partie avec des chaînes comme "ISSW" et "CHSETTINGS" et "X-LOADER", j'ai commencé à chercher d'autres endroits où ces chaînes pourraient apparaître dans des contextes liés à U-Boot. Je suis tombé sur [ce fil](https://forum.xda-developers.com/t/discussion-on-the-boot-loader-cracked.1378886/) de personnes qui font de la rétro-ingénierie ou craquent le firmware du Nook, et la [source de x-loader](https://github.com/joelagnel/x-loader/blob/f3c74bc9b01dac58e553393d6ec1041353f2f1f7/scripts/signGP.c) contient des références à des éléments comme `CHSETTINGS`. En cherchant bien, "ISSW" [semble](https://github.com/u-boot/u-boot/blob/master/doc/README.ti-secure) faire référence au démarrage depuis des périphériques non-mémoire.
Rappelons, d'après la documentation d'initialisation, le code de haut niveau :

À noter :
- RNDIS
- FAR
- XMODEM
- BOOTP
- TFTP
- DFT
Il est peut-être temps de réessayer le débogage en direct. Essayer de rétro-concevoir toutes ces structures serait probablement pénible étant donné la grande quantité de données que je ne comprends pas...
Hourra ! Le débogage en direct fonctionne quand on définit manuellement le PC et le SP à l'aide des vecteurs que j'ai trouvés :

À partir de la source et des vecteurs de trace, j'ai pu établir la correspondance entre les différentes options de démarrage et le numéro de périphérique qui leur est attribué. Cela s'est avéré très utile par la suite, car j'ai dû faire la distinction entre MMC0 (8) et MMCSD1 (9) lors de la définition de points d'arrêt dans le gestionnaire de démarrage SD/MMC.
| Type | Périphérique | ID du périphérique |
| ---------- | ----------------- | ------------------ |
| Mémoire | XIP (MUX2) | 1 |
| Mémoire | XIP w/WAIT (MUX2) | 2 |
| Mémoire | XIP (MUX1) | 3 |
| Mémoire | XIP w/WAIT (MUX1) | 4 |
| Mémoire | NAND | 5 |
| Mémoire | MMCSD1 | 7, 9 (eMMC) |
| Mémoire | NAND_I2C | 10 |
| Mémoire | MMC0 | 8, 12 (SD) |
| Périphérique | UART0 | 16 |
| Périphérique | USB | 20 |
| Périphérique | GPGMAC0 | 22 |
Pour plus d'informations sur la façon dont le processeur démarre, consultez le TRM et [cette réponse sur Stack Exchange](https://stackoverflow.com/a/31252989/8565545). Pour résumer :
1. La ROM de démarrage a identifié le fichier MLO (Mmc LOader) sur la carte SD et l'a copié dans la SRAM
2. Il s'agit du chargeur de programme secondaire, un chargeur d'amorçage plus petit qui initialise la RAM complète et y copie le binaire U-Boot complet pour l'exécuter
3. Une fois que le binaire U-Boot s'exécute, nous (ou plutôt U-Boot) démarrons enfin le noyau
### `run_booting_loop()`
C'est la boucle de démarrage principale. Elle s'exécute indéfiniment, ou jusqu'à ce que l'exécution bifurque vers un autre chargeur d'amorçage qui serait chargé dans la RAM.
Début de la procédure, sans mises à jour des vecteurs de trace :
- Consulter le type de périphérique
- Si le type de périphérique est 5 (périphérique sécurisé), effectuer une autre initialisation
- Exécuter `build_boot_list(int,buffer[],data[],int)`
- `buffer[]` est initialisé à `0xff` et `data[]` inclut le type de périphérique (probablement)```c
void run_booting_loop(uint32_t *r0_config,undefined4 param_2,undefined4 param_3,
undefined4 default_list)
{
int iVar1;
uint j;
uint i;
int device_type;
byte alt_list [12];
undefined4 boot_status;
byte boot_list [8];
uint8_t local_buffer [8];
update_current_tracing_vector(3);
/* Device type is 3 */
lookup_device_type(&device_type);
if ((device_type == AM335X_HIGH_SECURITY) && (iVar1 = return_zero_4(), iVar1 != 0)) {
init_something_1_small(&STATIC_DATA_1);
}
/* param1 = 1
param2 = 4030 ebc4
param3 = 4030 ebb4 */
build_boot_list(*(ushort *)r0_config,boot_list,alt_list,default_list);
do {
i = 0;
local_buffer[0] = 0xff;
local_buffer[1] = 0xff;
local_buffer[2] = 0xff;
local_buffer[3] = 0xff;
do {
if (boot_list[i] - 1 < 12) {
update_current_tracing_vector(4);
/* No return unless there is an error */
boot_device_1(r0_config,boot_list[i],local_buffer);
}
else if (boot_list[i] - 65 < 8) {
update_current_tracing_vector(5);
watchdog_write_disable_seq_data2();
boot_status = 0xffffffff;
boot_device_2((uint32_t)r0_config,boot_list[i],&boot_status,local_buffer);
watchdog_write_enable_seq_data2();
if (boot_status != 0xffffffff) {
local_buffer[0] = (undefined)boot_status;
local_buffer[1] = boot_status._1_1_;
local_buffer[2] = boot_status._2_1_;
local_buffer[3] = boot_status._3_1_;
if ((boot_status & 0xffff00ff) == 0xf0030006) {
update_current_tracing_vector(9);
boot_list[i + 1] = (byte)(boot_status >> 8);
}
else if (boot_status != 0xf0030002) {
update_current_tracing_vector(8);
j = 0;
do {
if (63 < boot_list[j]) {
boot_list[j] = 0;
}
j = j + 1 & 0xff;
} while (j < 8);
}
}
}
i = i + 1 & 0xff;
} while (i < 8);
update_current_tracing_vector(6);
} while( true );
}
J'ai découvert qu'il y avait une fonction à 0x23d7a que j'ai nommée boot_into_SRAM(), qui était la dernière fonction appelée avant de bifurquer vers la SRAM, puis de là, d'entrer dans le gestionnaire d'exceptions. Auparavant, j'avais enregistré un état différent de la RAM, mais en déboguant en direct une fois de plus (maintenant plus d'un an plus tard, en juillet 2024), j'ai compris ce qui devait se passer. La carte lisait avec succès des données depuis la carte SD, et exécutait du code qu'elle avait chargé depuis la carte ! Pour vérifier cela, j'ai dû trouver dans la SRAM des octets identiques à ceux présents sur la carte SD. Il s'avère que dans am335x-evm-linux-sdk-bin-.../board-support/prebuilt-images/ se trouve un fichier binaire nommé u-boot-spl.bin-am335x-evm, et le code de ce binaire correspond à ce qui apparaît dans la SRAM. Nous avons atteint le SPL d'uboot !
Nous avons réussi à démarrer dans la SRAM ; maintenant je m'interroge sur le terminal UART, qui devrait afficher des informations sur uboot. La connexion matérielle est ci-dessous.

En connectant l'appareil avec CuteCom, 115200 @ 8-N-1, sans carte SD insérée, il émet simplement C de façon répétée.

Mais lorsque la carte SD est insérée, l'UART ne sort rien. Ni messages, ni caractères. Le défaut doit-il survenir trop tôt dans le processus de démarrage ? Mais maintenant que je sais aussi quel code il exécute (et que j'ai sa source), je devrais pouvoir construire des symboles de débogage pour celui-ci et lancer une vraie session de débogage. Cela pourrait ne pas être trivial, je dois m'assurer de compiler le code de la même manière ; il me faudra peut-être du temps pour apprendre exactement ce que le SDK a chargé sur ma carte SD et comment le compiler.
Étant donné que U-Boot devrait envoyer du texte sur l'UART, et que je ne vois rien, je suppose que nous attrapons une exception quelque part dans le SPL.
À ce stade, j'ai passé de très nombreuses heures à faire de la rétro-ingénierie et à nettoyer le code source décompilé de la ROM de démarrage dans Ghidra, en examinant les structures et comment chaque membre de données était utilisé entre les fonctions, parfois de manière imbriquée, ce qui m'a causé toutes sortes de ravages. Pendant que cela restait en arrière-plan, je me suis dit qu'il était aussi temps de commencer à déboguer et à compiler mon propre code. Après tout, nous sommes dans la RAM ; pourquoi ne pas charger les symboles du SPL et voir ce qui se passe ?
Vous pouvez déboguer avec Ozone, le débogueur Segger pour J-Link. Vous pouvez également utiliser Code Composer Studio (CCS) de TI, ou peut-être aussi sa version « légère » de style VSCode, CCS Theia. J'ai réussi à compiler tout le SDK en suivant cette vidéo : Sitara Linux Board Porting Series: Module 6. Il y a trois composants à compiler :
J'ai suivi la vidéo du Module 7 de la série ci-dessus et j'ai réussi à faire fonctionner les choses, avec quelques remarques :
s_init() n'est plus présentAvoir des symboles ? Magnifique. Maintenant je peux voir ce qui se passe lors de l'exécution, en commençant par un gestionnaire de réinitialisation reset(), et je peux voir où nous aboutissons avec notre exception. Pour la retrouver, j'ai placé un point d'arrêt au gestionnaire d'exceptions à 0x402f 0440, et vérifié le registre de liaison, qui stockait encore l'adresse de la fonction la plus récente. Il s'est avéré que c'était l'adresse 0x402f 76ce, même si cela ne semble pas cohérent. Quelle est la cause de l'erreur ?
Remarque : pour le débogage, en suivant la vidéo du Module 7, exécutez jusqu'à 0x402f 0400, puis effectuez la partie Load Memory(). Cela doit être fait à chaque redémarrage.
Nous entrons dans la fonction device_probe() (0x402f 74c4), puis dans quelques autres fonctions ? Ensuite, depuis do_setup_dpll() avec une branche à 0x402f 07fc, nous n'en sortons pas, alors continuons dans cette direction. En avançant pas à pas, nous revenons à _main() dans crt0.S, situé à 0x402f 14e0. Il semble que nous sortions peut-être de board_init_f(), pour passer à spl_relocate_stack_gd(). Cet appel ne se termine pas. Nous atteignons dm_fixup_for_gd_move(). Celle-ci contient une instruction qui échoue, à 0x402f 76c2. Je pense que c'est ça : elle essaie d'accéder à 0x81ff ff20. Apparemment sans succès. J'ai l'intuition qu'il y a un problème avec la configuration de la SDRAM. J'ai retrouvé tous les fils de discussion sur les forums TI concernant des problèmes similaires, et j'en ai trouvé une demi-douzaine contenant quelques indices pour m'aider. J'en ai conclu que cela était probablement lié soit (a) au réglage EMIF, soit (b) au nivellement logiciel (software leveling).
Ma carte est celle présentée ci-dessous.

La mémoire est de Micron, tandis que le schéma de la BeagleBone Black que je possède (rév. C3) utilise de la mémoire DDR3 de Kingston, plus précisément la D2516EC4BXGGB. La DDR3 est U12, nous pouvons utiliser la page de décodage des marquages Micron pour identifier la référence :
Pour être sûr que le composant est vivant, j'ai commencé par simplement vérifier que l'alimentation était fournie. La fiche technique spécifie qu'elle doit être de 1,5 V +/- 0,075 V. Je mesure 1,506 V aux bornes de R6 sur la face inférieure de la carte. Nous avons deux points de test, TP1 et TP2.
Au cas où cela pourrait être utile, voici quelques points de test.
Le circuit mémoire est décrit en détail sur la page de conception matérielle.
Vérifions la ligne de validation d'horloge (clock enable). On peut vérifier les deux côtés de R96 ; un côté doit être mis à la masse, l'autre doit être maintenu au niveau haut.
Confirmé, 1,5 V sur CKE.
L'étape suivante consiste à vérifier le signal d'horloge. J'ai fait ce que j'ai pu ici, en utilisant le tinySA avec l'antenne connectée pointant à peu près en direction de la puce RAM. En faisant ce genre de « reniflage », je suis assez sûr que l'horloge est présente, du moins assez pour le moment.
Ensuite, il est temps d'examiner l'interface avec la mémoire externe. Un concept qui revient souvent est celui du fichier GEL. C'est un langage interprété développé par Texas Instruments pour Code Composer Studio ; l'abréviation signifie General Extension Language.
Un fichier GEL est inclus dans l'outil de configuration de la mémoire DDR.
Très bien ! J'ai suivi la procédure de réglage (du mieux que j'ai pu) et j'ai réussi à trouver des valeurs optimales pour le fichier GEL.```
The Slave Ratio Search Program Values are...
PARAMETER MAX | MIN | OPTIMUM | RANGE
DATA_PHY_RD_DQS_SLAVE_RATIO 0x071 | 0x005 | 0x03b | 0x06c DATA_PHY_FIFO_WE_SLAVE_RATIO 0x1b3 | 0x046 | 0x0fc | 0x16d DATA_PHY_WR_DQS_SLAVE_RATIO 0x0f7 | 0x01a | 0x088 | 0x0dd DATA_PHY_WR_DATA_SLAVE_RATIO 0x137 | 0x05a | 0x0c8 | 0x0dd
En essayant d'ajuster les paramètres RAM dans le Memory Browser... Ouah ! Ça marche !
Donc la mémoire semble certainement fonctionner, mais le SPL échoue toujours, donc il se passe peut-être quelque chose ici concernant la façon dont le SPL essaie d'initialiser la SDRAM ? Ah, oui, il y a plus dans la procédure de réglage, bien sûr ! Il faut en fait mettre à jour le SPL...
Nous nous rapprochons maintenant. Le fichier `board.c` initialise la DDR en vérifiant quel type de carte nous avons. Mais pour cette carte, toutes les fonctions (`board_is_evm_sk()`, `board_is_icev2()`, `board_is_bone_lt()`, etc) renvoient false, donc elle utilise par défaut `config_ddr(266, ...)` où 266 est la fréquence d'horloge en MHz, et il *devrait* être de 400 MHz. Cela serait certainement un problème.
`board_is_bone_lt` devrait être contournée pour toujours renvoyer true. J'ai fait cela, et j'avance un peu plus loin, mais quelque chose me chiffonne. Charger le nouveau fichier MLO sur la carte SD ne fonctionne pas, alors que charger le programme directement fonctionne parfaitement. Que se passe-t-il ici ? Je peux dire que le code chargé dans la SRAM n'est pas le même code que celui que j'ai compilé. En fait, j'ai même formaté la carte SD, et un SPL par défaut semble se charger dans la SRAM de toute façon ! J'ai vérifié que le processus de démarrage n'essaie pas de continuer lorsqu'on utilise une autre carte SD. Par conséquent, le bootloader cherche définitivement la partition de démarrage sur la carte SD, puis il déplace l'exécution vers la SRAM, mais il n'a pas encore copié les données ? Ugh. D'où cela vient-il ??
Ce problème m'a causé pas mal de soucis. J'ai supprimé toutes les partitions, mis à zéro le MBR, mis à zéro la partition de démarrage, et essayé différentes cartes SD, et seule ma carte était encore capable de démarrer, donc il devait forcément y avoir *des* données de démarrage encore dessus, stockées quelque part. Finalement, j'ai pu mettre fin à cette folie en mettant à zéro la carte *entière*.
J'ai aussi appris à ce sujet les **vecteurs de trace** accessibles lors du dépannage de la ROM de démarrage. Cela s'est également révélé extrêmement utile pour le reverse engineering, car je savais d'où venaient tous les appels de trace, et je pouvais alors assigner des noms de fonctions etc. en fonction de ces appels. J'ai créé une feuille de calcul pour interpréter ces vecteurs de trace, et je l'ai utilisée pour comprendre rapidement comment la procédure de démarrage changeait lorsque je modifiais les paramètres de la carte. Effectivement, il prétendait trouver les CHSETTINGS encore et encore pendant que j'essayais de formater et reformater la carte SD, avant que je ne mette désespérément le tout à zéro.
Pour essayer de lire la carte comme le ferait le processeur TI, vous pouvez utiliser `dd`. Utilisez une taille de bloc = 512, et spécifiez le premier secteur (essayez d'utiliser GParted pour vérifier lequel) en sautant les `n` premiers. Exemple : le premier secteur est 2048, le périphérique est `sda`, nous ne lirons que le premier secteur :```
sudo dd if=/dev/sda1 of=/home/sam/sector2048 bs=512 skip=2048 count=1
Je m'en suis servi pour télécharger des images du MBR et du début de la partition de démarrage directement depuis la carte SD, deux éléments qui se révéleraient utiles plus tard.
Mes efforts de rétro-ingénierie dans Ghidra m'avaient conduit aux fonctions de gestion du démarrage de la carte SD, et je pouvais désormais voir des fonctions envoyant des commandes SD à la carte, et je pouvais suivre pas à pas l'exécution et voir ce que la carte répondait. Je pensais que je regardais au bon endroit et que la carte renvoyait des zéros. J'ai appris plus tard que j'étais peut-être en train d'exécuter pas à pas le gestionnaire eMMC (le même gestionnaire mais avec un identifiant de périphérique différent), ou que quelque chose d'autre clochait, car la carte SD n'avait rien d'anormal. Néanmoins, je me suis dit qu'il était temps d'apprendre comment ces cartes fonctionnent.
J'étais curieux de voir pourquoi la carte ne cessait de renvoyer des zéros à chaque requête de bloc. De toute évidence, les fonctions de la carte et le logiciel savent lire depuis celle-ci, car ils l'ont déjà fait par le passé. Mais il était tout de même temps de tout câbler et d'y jeter un œil avec un analyseur logique. Un peu de microsoudure, le maintien des fils en 30awg avec de l'époxy à polymérisation UV, et le clip de mon Saleae, et voilà quelque chose qui fonctionne.

J'ai utilisé cet analyseur pour analyser les données. J'ai d'abord essayé sans carte insérée.

Pour les premières commandes, la fréquence d'horloge est de 120 kHz. Évidemment, la carte ne répond pas (elle n'est pas là).``` CMD0, arg=\0 GO_IDLE_STATE CMD8, arg=\x01\xAA SEND_EXT_CSD CMD55, arg=\0 APP_CMD CMD1, arg=\0 SEND_OP_COND ... CMD0, arg=\0 GO_IDLE_STATE CMD8, arg=\x01\xAA SEND_EXT_CSD CMD55, arg=\0 APP_CMD CMD1, arg=\0 SEND_OP_COND
Quand la carte est réellement insérée, la fréquence passe à environ 6 MHz après la configuration.

Après avoir surmonté quelques plantages dus à l'utilisation du mode SD (astuce : même pour cette carte SD, vous devriez utiliser le mode MMC) et avoir pu vérifier que la carte fournissait des données raisonnables, j'ai mis cela de côté parce que j'ai redoublé d'efforts pour comprendre le gestionnaire de démarrage de la carte SD et j'ai réalisé que les données correctes *étaient* bien lues, et que c'étaient les mêmes données que celles que j'avais obtenues en faisant manuellement un `dd` de la carte ! Bon, c'était un détour amusant et cela m'a aidé à être confiant que la carte fonctionnait.
### Trouver le problème
Les données proviennent de l'adresse `0x4030c928` (une variable de pile, tableau de 512 octets) après l'exécution de la branche à `0x25c2e` pour l'adresse `0x0000` et le périphérique `8` (voir les données statiques à `0x4030d00c`). En entrant dans la méthode que j'ai appelée `MBR_detection`, le programme vérifie les octets magiques `0xaa55`. Il charge d'abord les deux seconds octets `0xaa`, puis le premier `0xaa`.```
r0 = data[0x1ff]
r1 = data[0x1fe]
orr r0,r1,r0,lsl #8
sub r1,r0,#0xaa00
subs r1,#0x55
bne <return FAIL>
Ceci réussit. La vérification suivante échoue, cependant :``` r0 = data[0xc] => 0 r1 = data[0xb] => 0 orr r0,r1,r0, lsl #8 cmp r0,#0x200 bne
Pseudocode du décompilateur :```c
if (
data[0x1fe] != 0x55aa ||
data[0xb] != 0x200 ||
(data[0xd] != 1 && // bit 0
data[0xd] != 2 && // bit 1
data[0xd] != 4 && // bit 2
data[0xd] != 8 && // bit 3
data[0xd] != 0x10 && // bit 4
data[0xd] != 0x20 && // bit 5
data[0xd] != 0x40 && // bit 6
data[0xd] != 0x80) // bit 7
)
{
return 1;
}
Cette fonction vérifie si l'octet à 0xb = 11 est égal à 0x200, et vérifie si l'octet à 0xd est égal à une valeur sur un seul bit. Les deux conditions doivent être satisfaites, sinon elle renvoie un échec.
Après que la fonction de détection renvoie un 1, le gestionnaire de démarrage essaie ensuite de le lire comme un MBR et charge le décalage de la première partition pour voir si celle-ci est une partition amorçable. Voici la procédure:```C
// Call block read function
// mmc_block_read_something(boot_device *dev,blk_read_struct blk)
ret = ((code *)blockread_struct->block_read_func)(blockread_struct->device_ptr,&block_read_info);
if (ret != 0) {
return 1;
}
// Check if device doesn't use MBR
ret = MBR_check_bootable_partition((partition_struct *)block_data,blockread_struct);
if (ret != 0) {
// It uses MBR, try each partition in the partition entries for a bootable
// partition
ret = MBR_check_entries(block_data,blockread_struct);
if (ret != 0) {
return 1;
}
ret = MBR_parse_entries(block_data,&blockread_struct->part_entry);
if (ret != 0) {
return 1;
}
// Get bootable partition offset
block_read_info = (blk_read_struct *)(blockread_struct->part_entry).first_sect_pos;
uStack_220 = 1;
pbStack_21c = block_data;
// Call block read function
// mmc_block_read_something(boot_device *dev,blk_read_struct blk)
ret = ((code *)blockread_struct->block_read_func)
(blockread_struct->device_ptr,&block_read_info);
if (ret != 0) {
return 1;
}
// Try and verify bootable partition again
ret = MBR_check_bootable_partition((partition_struct *)block_data,blockread_struct);
if (ret != 0) {
return 1;
}
}
Je passe donc maintenant au point où il lit les données à `0x800` et j'obtiens le dump correct. Mais la méthode de détection du MBR renvoie toujours 1, même avec les bonnes données (et il y a quelques couches d'indirection que j'ai dû suivre, grrr), c'est donc là que doit se trouver le problème.
Dernière ligne droite. La première lecture mémoire depuis la carte SD est le MBR, qui contient jusqu'à quatre entrées de table de partitions, voir les tableaux 26-20, 26-21 du TRM. L'entrée de la table de partitions pour la partition de démarrage indique que la partition contient `0x40000` secteurs. Mais le système de fichiers de la partition (voir tableau 26-23 du TRM) n'en mentionne que `0x3fff8`, pour une raison quelconque, et la ROM de démarrage le détecte et échoue.
À titre d'expérience, j'ai sauté le branchement qui posait problème (ce qui pourrait mal tourner... croisons les doigts...) et le programme a bien continué, même si je ne suis pas sûr de l'endroit où j'ai atterri, on dirait du charabia. Mais en ignorant cela, l'appareil démarre réellement ! En plaçant un point d'arrêt à `0x402f0400` (début de l'image chargée), tout se passe correctement. Peut-être le moment de brancher l'UART ? L'UART, c'est bien !
Quant au problème de carte SD ? J'ai posé une [question sur Unix SE](https://unix.stackexchange.com/questions/781715/why-do-the-mbr-partition-entry-and-partition-filesystem-disagree-on-the-number-o/781755), mais je n'ai pas eu beaucoup d'aide sur le sujet (même si j'ai quand même obtenu de bonnes infos). De là : j'ai enfin corrigé le problème ! En examinant les commandes `mkfs` pour construire le système de fichiers FAT16, j'ai remarqué qu'[une autre référence](https://blog.billvanleeuwen.ca/porting-u-boot-onto-the-beaglebone) utilise l'option -a, qui désactive l'alignement. C'était la clé. En ajoutant cette option et en recompilant, les nombres de secteurs concordent (`0x40000`) et le système démarre. J'imagine que la ROM de démarrage ne prend pas en charge ce type d'alignement.
J'obtiens maintenant les messages ci-dessous en boucle de démarrage, avec la carte insérée. Hourra ! Il ne reste plus qu'à comprendre pourquoi le noyau ne démarre pas, et ce sera parfait ! Tout ce travail pourrait enfin porter ses fruits avec quelques cartes beaglebone utilisables. Est-ce que ça en valait la peine ? À qui de juger.```
U-Boot SPL 2021.01-00001-gc59bf25a382-dirty (Jul 24 2024 - 20:38:49 -0400)
Trying to boot from MMC1
U-Boot 2021.01-00001-gc59bf25a382-dirty (Jul 28 2024 - 20:36:46 -0400)
CPU : AM335X-GP rev 2.1
Model: TI AM335x BeagleBone Black
DRAM: 512 MiB
WDT: Started with servicing (60s timeout)
NAND: 0 MiB
MMC: OMAP SD/MMC: 0, OMAP SD/MMC: 1
Loading Environment from FAT... *** Warning - bad CRC, using default environment
<ethaddr> not set. Validating first E-fuse MAC
Net: eth2: ethernet@4a100000, eth3: usb_ether
Hit any key to stop autoboot: 2 <0x08><0x08><0x08> 1 <0x08><0x08><0x08> 0
WARNING: Could not determine device tree to use
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
Failed to load 'boot.scr'
Failed to load 'uEnv.txt'
switch to partitions #0, OK
mmc0 is current device
Scanning mmc 0:1...
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
<0x1b>7<0x1b>[r<0x1b>[999;999H<0x1b>[6n<0x1b>8Scanning disk [email protected]...
Scanning disk [email protected]...
** Unrecognized filesystem type **
Found 4 disks
No EFI system partition
BootOrder not defined
EFI boot manager: Cannot load any image
switch to partitions #0, OK
mmc0 is current device
SD/MMC found on device 0
4997632 bytes read in 353 ms (13.5 MiB/s)
Failed to load '/boot/undefined'
Starting kernel ...
Donc, il y a un problème avec le device tree (« WARNING: Could not determine device tree to use »). C'est ma première rencontre avec le noyau et le démarrage du noyau, donc je n'ai aucune idée de ce que cela signifie.
« Démarrer le noyau » implique ce qui suit, d'après ce que je comprends :
Un aspect important du processus de démarrage du noyau est le device tree. Il est stocké dans le fichier .dtb (binaire de device tree ; à comparer avec les fichiers source .dts du device tree) pour la carte.
Mon problème maintenant est qu'U-Boot ne charge pas le device tree de la carte, puisqu'il n'y a aucun message reading /am335x-boneblack.dtb dans le journal. Au lieu de cela, on obtient WARNING: Could not determine device tree to use. C'est donc une bonne preuve ! Je suppose que c'est dû à l'identifiant de carte EEPROM manquant.
Quelques détails sur la manière dont il obtient la carte se trouvent dans ce fil de discussion sur les forums TI.
Alors, comment U-Boot sait-il comment se configurer et démarrer correctement ? Dans le code source d'U-Boot que nous avons compilé, il y a un dossier appelé configs/ qui stocke les fichiers defconfig pour diverses cartes. Ces fichiers définissent les divers paramètres de configuration d'U-Boot, y compris la commande de démarrage, qui pourrait ressembler à ceci :```
if test ${boot_fit} -eq 1;
then run update_to_fit;
fi;
run findfdt;
run init_console;
run envboot;
run finduuid;
run distro_bootcmd
Nous définissons quelle configuration utiliser lorsque nous exécutons la cible `make <boardname>_config`. La fonction `findfdt` est utilisée pour identifier la carte que nous utilisons, et configure correctement le device tree. Elle ressemble à ceci (définie dans `am335x_evm.h`) :```
"findfdt="\
"if test $board_name = A335BONE; then " \
"setenv fdtfile am335x-bone.dtb; fi; " \
"if test $board_name = A335BNLT; then " \
"setenv fdtfile am335x-boneblack.dtb; fi; " \
"if test $board_name = A335PBGL; then " \
"setenv fdtfile am335x-pocketbeagle.dtb; fi; " \
"if test $board_name = BBBW; then " \
"setenv fdtfile am335x-boneblack-wireless.dtb; fi; " \
"if test $board_name = BBG1; then " \
"setenv fdtfile am335x-bonegreen.dtb; fi; " \
"if test $board_name = BBGW; then " \
"setenv fdtfile am335x-bonegreen-wireless.dtb; fi; " \
"if test $board_name = BBBL; then " \
"setenv fdtfile am335x-boneblue.dtb; fi; " \
"if test $board_name = BBEN; then " \
"setenv fdtfile am335x-sancloud-bbe.dtb; fi; " \
"if test $board_name = A33515BB; then " \
"setenv fdtfile am335x-evm.dtb; fi; " \
"if test $board_name = A335X_SK; then " \
"setenv fdtfile am335x-evmsk.dtb; fi; " \
"if test $board_name = A335_ICE && test $ice_mii = rmii; then " \
"setenv fdtfile am335x-icev2.dtb; fi; " \
"if test $board_name = A335_ICE && test $ice_mii = mii; then " \
"setenv fdtfile am335x-icev2-prueth.dtb; fi; " \
"if test $fdtfile = undefined; then " \
"echo WARNING: Could not determine device tree to use; fi; \0" \
Si nous voulons le comportement par défaut, nous pouvons simplement changer la variable board_name, non ? Eh bien peut-être pas, ou du moins je ne sais pas où est le meilleur endroit pour la changer. Mais même si le réglage de board_name n'a pas fonctionné en soi, j'ai aussi mis à jour le fichier .dtb par défaut et cela a fonctionné !```
_____ _____ _ _
| _ |___ ___ ___ ___ | _ |___ ___ ||__ | |
| | _| .'| . | . | | | | . | | | -| _| _|
|||| |__,| || || || ||| ||||
|| |___|
Arago Project http://arago-project.org am335x-evm ttyS0
Arago 2021.09 am335x-evm ttyS0
am335x-evm login: root
root@am335x-evm:~#
At long last, we're at a terminal. My junk boards are alive!
### Fixing the Missing EEPROM ID
As a last step, I'll write in the correct board ID to the EEPROM, which is easy to do from within Linux user space. From the SPL source, the various board IDs are:
- `A335BONE` - Beaglebone board
- `A335BNLT` - Beaglebone Black board
- `A335PBGL`
- `A335X_SK`
- `A33515BB`
- `A335_ICE`
And there's also an optional board revision. Based on the schematic, the EEPROM is on I2C0, and the chip itself (24LC32A on my schematic, although it's labeled '256Kx8) provides the I2C device address of `0x50` (binary `b1010` followed by `000` chip address, since the 5 pin package doesn't have additional address pins). One last point of note: the WP pin is pulled HIGH with a 10k pull-up, so write protect is enabled by default; it needs to be tied low before any writing can occur, or else it will acknowledge but simply not write anything.
The EEPROM can be accessed through the kernel at `/sys/bus/i2c/devices/0-0050`, within which there is a file called `eeprom`. Therefore, with the WP pin pulled LOW (tie TP4 on the top near the DC jack to ground) a few `echo` calls is all that's needed. The Beaglebone Black System Reference Manual has the format. I adapted this [from here](https://groups.google.com/g/beagleboard/c/di5O5JCl4yw).```sh
root@am335x-evm:~# cat fix_eeprom.sh
#!/bin/bash
# Fix board ID EEPROM
EEPROM_FILE=/tmp/eeprom.tmp
EEPROM=/sys/bus/i2c/devices/0-0050/eeprom
# header bytes
echo -ne "\xaa\x55\x33\xee" > ${EEPROM_FILE}
# Board ID
echo -n "A335BNLT" >> ${EEPROM_FILE}
# serial number (I left this basically as the template)
echo -n "000C24wwBBoxxxx" >> ${EEPROM_FILE}
dd if=${EEPROM_FILE} of=${EEPROM}
En utilisant less pour confirmer, et nous ne devrions avoir aucun problème à exécuter une carte SD par défaut à partir de maintenant. Les modifications apportées au SPL et à la configuration U-Boot peuvent être annulées, une fois que toutes les cartes ont leurs EEPROMs écrites.
| Région | Adresse de début | Longueur |
|---|
| Boot ROM (Public) | 0x4002_0000 | 0xBFFF |
| Boot ROM (Public, alias) | 0x0002_0000 | 0xBFFF |
| SRAM interne | 0x402F_0400 | 0xFC00 |
| L3 OCM0 | 0x4030_0000 | 0x10000 |
| Point de test | Connexion | Feuille de schéma | Côté de la carte |
|---|
| TP1 | DGND | 2 (D1) | Dessus |
| TP2 | VDD_MPUON (VDD_MPU_MON) | 5 (C4) | Dessus |
| TP3 | TESTOUT | 5 (B2) | Dessus |
| TP4 | Board ID WP | 11 (B1) | Dessus |