Dans Un contrôleur de mémoire plus ou moins fonctionnel a été réalisé, en fait une enveloppe autour de l'IP Core de Quartus, servant de passerelle vers TileLink. Aujourd'hui, dans la rubrique « Portage de RocketChip sur une carte chinoise peu connue avec Cyclone », vous verrez une console fonctionnelle. Le processus a pris un peu de temps : je pensais lancer rapidement Linux et avancer, mais cela ne s'est pas passé comme prévu. Dans cette partie, je propose de jeter un œil au processus de démarrage de U-Boot, BBL, et aux tentatives timides d'initialisation du noyau Linux. Mais il y a une console — celle de U-Boot, et elle est assez avancée, avec beaucoup de ce que vous attendez d'une console complète.
Au niveau matériel, une carte SD sera ajoutée, connectée via l'interface SPI, ainsi qu'un UART. Dans la partie logicielle, le BootROM sera remplacé par xip sur sdboot et, en fait, les étapes de chargement suivantes seront ajoutées (sur la carte SD).
Aménagement de la partie matérielle
Ainsi, la tâche consiste à passer à un « grand » cœur et à connecter l'UART (de Raspberry) et l'adaptateur SD (une petite carte de Catalex avec six broches : GND, VCC, MISO, MOSI, SCK, CS).
En principe, tout était assez simple. Mais avant d'en avoir conscience, j'ai été un peu ballotté d'un côté à l'autre : après la dernière fois, j'ai décidé qu'il fallait encore simplement mélanger dans Système quelque chose comme HasPeripheryUART (et dans l'implémentation en conséquence), tout comme pour la carte SD — et tout serait prêt. Ensuite, j'ai décidé de voir comment cela était implémenté dans un design « sérieux ». Alors, qu'avons-nous d'important ici ? Arty ne semble pas convenir — il reste le monstre unleahshed.DevKitConfigs. Et soudain, j'ai découvert qu'il y avait des superpositions partout, ajoutées via des paramètres par clés. Je devine que cela doit être très flexible et configurable, mais j'aurais besoin de quelque chose pour commencer à faire fonctionner… N'avez-vous pas quelque chose de similaire, mais plus simple et moins raffiné ? C'est alors que je suis tombé sur vera.iofpga.FPGAChip pour FPGA Microsemi et j'ai tout de suite pris des citations pour essayer de faire ma propre implémentation par analogie, étant donné que presque toute la « plomberie de la carte mère » se trouve dans un seul fichier.
Il s'est avéré qu'il suffisait simplement d'ajouter dans System.scala les lignes
class System(implicit p: Parameters) extends RocketSubsystem
...
with HasPeripherySPI
with HasPeripheryUART
...
{
val tlclock = new FixedClockResource("tlclk", p(DevKitFPGAFrequencyKey))
...
}
class SystemModule[+L <: System](_outer: L)
extends RocketSubsystemModuleImp(_outer)
...
with HasPeripheryUARTModuleImp
with HasPeripheryGPIOModuleImp
...Ligne dans le corps de la classe Système ajoute des informations sur la fréquence à laquelle cette partie de notre SoC fonctionne, dans le fichier dts. D'après ce que je comprends, le DTS/DTB est une sorte d'analogue statique de la technologie plug-and-play pour les appareils embarqués : l'arborescence de description dts est compilée en un fichier binaire dtb et est transmise par le chargeur au noyau, afin qu'il puisse configurer correctement le matériel. Ce qui est intéressant, c'est que sans la ligne avec tlclock , tout synthétise parfaitement, mais il ne sera pas possible de compiler BootROM (rappelez-vous, maintenant ce sera déjà sdboot) — lors du processus de compilation, il analyse le fichier dts et crée un en-tête avec la macro TL_CLK, grâce à laquelle il pourra configurer correctement les diviseurs de fréquence pour les interfaces externes.
Il faudra également corriger un peu le routage :
Platform.scala :
class PlatformIO(implicit val p: Parameters) extends Bundle {
...
// UART
io.uart_tx := sys.uart(0).txd
sys.uart(0).rxd := RegNext(RegNext(io.uart_rx))
// Carte SD
io.sd_cs := sys.spi(0).cs(0)
io.sd_sck := sys.spi(0).sck
io.sd_mosi := sys.spi(0).dq(0).o
sys.spi(0).dq(0).i := false.B
sys.spi(0).dq(1).i := RegNext(RegNext(io.sd_miso))
sys.spi(0).dq(2).i := false.B
sys.spi(0).dq(3).i := false.B
}À vrai dire, les chaînes de registre ont été ajoutées simplement par analogie avec certains autres endroits du code d'origine. Ils doivent probablement protéger contre . Peut-être que dans certaines blocs, il y a déjà une certaine protection, mais au départ, je veux réussir au moins à "un niveau de qualité". La question qui m'intéresse davantage est pourquoi MISO et MOSI sont sur des dq? Ответа я пока так и не нашёл, но, похоже, остальной код рассчитывает именно на такое подключение.
Physiquement, j'ai simplement assigné les sorties de conception aux contacts libres sur la broche et déplacé le jumper de sélection de tension à 3.3V.
Adaptateur SD
Vue de dessus :

Vue de dessous :

Débogage de la partie logicielle : outils
Commençons par parler des outils de débogage disponibles et de leurs limitations.
Minicom
Tout d'abord, nous avons besoin de manière à lire ce que le chargeur et le noyau affichent. Pour cela, sur Linux (dans ce cas, sur ce qui est sur RaspberryPi), nous aurons besoin du programme Minicom. En général, n'importe quel programme fonctionnant avec le port série fera l'affaire.
Veuillez noter qu'au démarrage, le nom de dispositif du port doit être indiqué comme -D /dev/ttyS0 — après l'option -D. Et la principale information : pour quitter, utilisez Ctrl-A, X. J'ai en fait eu un cas où cette combinaison n'a pas fonctionné — dans ce cas, vous pouvez simplement dire à partir d'une session SSH voisine killall -KILL minicom.
Il existe une autre particularité. Sur le Raspberry Pi, il y a deux UART, et les deux ports peuvent déjà être configurés pour quelque chose : l'un pour Bluetooth, et l'autre étant utilisé par défaut pour afficher la console du noyau. Heureusement, ce comportement peut être reconfiguré. .
Réécriture de la mémoire.
Lors du débogage, pour vérifier une hypothèse, il m'est parfois arrivé de devoir charger le bootloader (désolé) dans la mémoire vive directement depuis l'hôte. Peut-être que cela peut être fait directement depuis GDB, mais j'ai finalement opté pour la solution simple : j'ai copié le fichier nécessaire sur le Raspberry, redirigé le port 4444 (telnet depuis OpenOCD) via SSH et utilisé la commande load_image.Quand vous l'exécutez, on dirait que tout est bloqué, mais en réalité «il ne dort pas, il clignote simplement lentement»: il charge le fichier, mais le fait à une vitesse de quelques kilo-octets par seconde.
Particularités de la mise en place de breakpoints.
Probablement, beaucoup n'y ont jamais pensé lors du débogage de programmes ordinaires, mais les breakpoints ne sont pas toujours matériels. Parfois, mettre un breakpoint consiste à écrire temporairement une instruction spéciale à l'endroit souhaité directement dans le code machine.Par exemple, cela fonctionnait ainsi avec la commande standard b dans GDB. Voici ce qui en découle :
- on ne peut pas mettre de point d'arrêt à l'intérieur du BootROM, car la ROM
- on peut mettre un point d'arrêt sur le code chargé dans la mémoire vive à partir de la carte SD, mais il faut attendre qu'il soit chargé. Sinon, ce n'est pas nous qui réécrivons un morceau de code, mais le bootloader qui réécrit notre breakpoint.
Je suis sûr qu'on peut explicitement demander à utiliser des points d'arrêt matériels, mais ils sont de toute façon en nombre limité.
Substitution rapide du BootROM.
Au début du débogage, il y a souvent l'envie de corriger le BootROM et d'essayer encore une fois. Mais il y a un problème : le BootROM fait partie du design chargé dans le FPGA, et sa synthèse prend quelques minutes (et cela après la compilation presque instantanée de l'image BootROM à partir de C et d'assembleur…). Heureusement, en réalité tout beaucoup plus rapidement: la séquence d'actions est la suivante :
- régénérer bootrom.mif (je suis passé au MIF au lieu de HEX, car j'avais toujours des problèmes avec HEX, et MIF est le format natif d'Alter).
- Dans Quartus, dire
Traitement -> Mettre à jour le fichier d'initialisation de la mémoire - dans l'assembleur (dans la colonne gauche des tâches), ordonner de recommencer.
Tout cela prend quelques dizaines de secondes.
Préparation de la carte SD.
Ici, tout est relativement simple, mais il faut faire preuve de patience et disposer d'environ 14 Go d'espace disque :
git clone https://github.com/sifive/freedom-u-sdk
git submodule update --recursive --init
makeAprès cela, il faut insérer une carte SD vierge, c'est-à-dire ne contenant rien d'utile, et exécuter
sudo make DISK=/dev/sdX format-boot-loader… où sdX — est l'appareil assigné à la carte. ATTENTION : les données sur la carte seront supprimées, réécrites et en général ! Il est peu probable qu'il soit judicieux de faire toute la compilation sous sudo, car alors tous les artefacts de compilation appartiendront à root, et il faudra faire la compilation sous sudo en permanence.
Au final, on obtient une carte partitionnée en GPT avec quatre partitions, dont l'une est FAT avec uEnv.txt et une image de démarrage au format FIT (il contient plusieurs sous-images, chacune avec sa propre adresse de démarrage), l'autre partition est vierge, elle est supposée être formatée en Ext4 pour Linux. Les deux autres partitions sont mystérieuses: l'une héberge U-Boot (son décalage, autant que je comprends, est gravé dans le BootROM), sur l'autre, il semble que résident ses variables d'environnement, mais je ne les utilise pas pour l'instant.
Niveau un, BootROM
La sagesse populaire dit : « S'il y a des danses avec un tambour en programmation, alors en électronique, il y a aussi des danses avec un extincteur ». Ce n'est même pas que j'ai failli brûler la carte en pensant que « GND, c'est le même niveau bas » (apparemment, une résistance n'aurait pas fait de mal…) C'est plutôt que si les mains ne viennent pas d'où il faut, l'électronique ne cesse de faire des surprises : en soudant le connecteur sur la carte, je n'ai jamais réussi à souder correctement les contacts — dans les vidéos, on montre comment la soudure se répand toute seule sur toute la connexion, il suffit de poser le fer, chez moi, elle ne faisait que 'bricoler' n'importe comment. Eh bien, peut-être que la soudure n'était pas adaptée à la température du fer, peut-être autre chose… Bref, voyant que j'avais déjà une dizaine de contacts, j'ai abandonné et commencé le débogage. Et là, cela a commencé mystérieux: j'ai connecté RX/TX de l'UART, chargeant le firmware — il écrit
INIT
CMD0
ERROREh bien, tout cela est logique — je n'ai pas connecté le module de la carte SD. Je corrige la situation, charge le firmware… Et le silence… Quoi que j'ai pensé, le problème était simple : une des broches du module devait être connectée à VCC. Dans mon cas, le module supportait 5V pour l'alimentation, donc sans trop réfléchir, j'ai connecté le fil venant du module de l'autre côté de la carte. Au final, le connecteur mal soudé s'est déformé et je viens de perdre le contact UART. facepalm.jpg En gros, « une tête vide n'apporte aucun repos aux pieds », et des mains maladroites à la tête…
Finalement, j'ai vu dans Minicom ce que j'attendais depuis longtemps
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING /De plus, l'indicateur de chargement bouge. Cela me rappelle mes années scolaires et le démarrage lent de MinuetOS à partir d'une disquette. À part que le lecteur de disquette ne grince pas.
Le problème est qu'après le message BOOT, il ne se passe rien. Il est donc temps de se connecter via OpenOCD à Raspberry, à lui GDB sur l'hôte, et de voir de quoi il s'agit.
Tout d'abord, la connexion avec GDB a immédiatement montré que $pc (compteur de programme, adresse de l'instruction actuelle) s'envole vers 0x0 — il est probable que cela se produise après une erreur multiple. Donc, juste après avoir affiché le message BOOT ajoutons une boucle infinie. Cela le retardera un moment…
diff --git a/bootrom/sdboot/sd.c b/bootrom/sdboot/sd.c
index c6b5ede..bca1b7f 100644
--- a/bootrom/sdboot/sd.c
+++ b/bootrom/sdboot/sd.c
@@ -224,6 +224,8 @@ int main(void)
kputs("BOOT");
+ while(*(volatile char *)0x10000){}
+
__asm__ __volatile__ ("fence.i" : : : "memory");
return 0;
}Ce code astucieux est utilisé « pour la fiabilité » : j'ai entendu quelque part que, apparemment, une boucle infinie est un comportement indéfini, et ici, le compilateur ne devrait pas le deviner (Je rappelle que par 0x10000 se trouve le BootROM).

On pourrait penser, qu'attendre d'autre — du dur du embarqué, quel code source ici. Mais en fait, l'auteur a débogué le code C… Tricks-trac-truqu.
(gdb) file builds/zeowaa-e115/sdboot.elf
Un programme est déjà en cours de débogage.
Êtes-vous sûr de vouloir changer de fichier ? (y ou n) y
Lecture des symboles depuis builds/zeowaa-e115/sdboot.elf...fait.
Il faut juste charger non un fichier MIF ou binaire, mais la version originale au format ELF.
Maintenant, on peut essayer de deviner l'adresse où l'exécution va se poursuivre (c'est une autre raison pour laquelle le compilateur n'aurait pas dû deviner que la boucle est infinie). La commande
set variable $pc=0xADDRpermet de changer la valeur du registre à la volée (dans ce cas — l'adresse de l'instruction actuelle). Avec elle, on peut aussi changer les valeurs enregistrées en mémoire (et les registres mappés en mémoire).
En fin de compte, je suis arrivé à la conclusion (je ne suis pas sûr que ce soit la bonne), que nous avons « une image de carte SD d'un autre système », et qu'il faut passer non au tout début des données chargées, mais à 0x89800 des octets plus loin :
diff --git a/bootrom/sdboot/head.S b/bootrom/sdboot/head.S
index 14fa740..2a6c944 100644
--- a/bootrom/sdboot/head.S
+++ b/bootrom/sdboot/head.S
@@ -13,7 +13,7 @@ _prog_start:
smp_resume(s1, s2)
csrr a0, mhartid
la a1, dtb
- li s1, PAYLOAD_DEST
+ li s1, (PAYLOAD_DEST + 0x89800)
jr s1
.section .rodataPeut-être que cela a également été influencé par le fait que n'ayant pas à disposition une carte inutile de 4 Go, j'ai pris celle de 2 Go et, par tâtonnement, remplacé dans le Makefile. DEMO_END=11718750 sur DEMO_END=3078900 (ne cherchez pas de sens dans la signification spécifique — il n'y en a pas, simplement maintenant l'image tient sur la carte).
Niveau deux, U-Boot
Maintenant, nous sommes toujours « en chute », mais nous nous retrouvons déjà à l'adresse 0x0000000080089a84. Ici, je dois admettre : en réalité, l'exposé ne se fait pas « avec tous les arrêts », mais s'écrit partiellement déjà « après coup », donc ici j'ai déjà réussi à ajouter le bon fichier dtb de notre SoC, à corriger dans les paramètres. HiFive_U-Boot variable CONFIG_SYS_TEXT_BASE=0x80089800 (au lieu de 0x08000000), pour que l'adresse de chargement corresponde à la réalité. Nous chargeons maintenant déjà la carte du niveau suivant d'une autre image :
(gdb) file ..\/freedom-u-sdk\/work\/HiFive_U-Boot\/u-boot
(gdb) tui enEt nous voyons :
│304 * │
│305 * entrée de piège │
│306 *\/ │
│307 trap_entry : │
│308 addi sp, sp, -32*REGBYTES │
>│309 SREG x1, 1*REGBYTES(sp) │
│310 SREG x2, 2*REGBYTES(sp) │
│311 SREG x3, 3*REGBYTES(sp) │Et nous sautons entre les lignes 308 et 309. Et ce n'est pas surprenant, étant donné que dans $sp se trouve la valeur 0xfffffffe31cdc0a0. Malheureusement, elle « s'échappe » constamment à cause de la ligne 307. Donc essayons de placer un point d'arrêt sur trap_entry, puis de revenir à 0x80089800 (point d'entrée U-Boot), et espérons qu'il ne nécessite pas un bon réglage des registres avant de sauter… Cela semble fonctionner :
(gdb) b trap_entry
Breakpoint 1 at 0x80089a80: file \/hdd\/trosinenko\/fpga\/freedom-u-sdk\/HiFive_U-Boot\/arch\/riscv\/cpu\/HiFive\/start.S, line 308.
(gdb) set variable $pc=0x80089800
(gdb) c
Continuing.
Breakpoint 1, trap_entry () at \/hdd\/trosinenko\/fpga\/freedom-u-sdk\/HiFive_U-Boot\/arch\/riscv\/cpu\/HiFive\/start.S:308
(gdb) p\/x $sp
$4 = 0x81cf950Pas un très bon pointeur de pile, je dirais : il indique complètement à côté de la RAM (à moins que nous n'ayons pas encore de traduction d'adresses, mais espérons une solution simple).
Essayons de remplacer le pointeur par 0x881cf950. En fin de compte, nous en venons à ce que handle_trap est appelé encore et encore, tandis que nous plongeons dans _exit_trap avec l'argument epc=2148315240 (en valeur décimale) :
(gdb) x/10i 2148315240
0x800cb068 : lbu a4,0(a5)
0x800cb06c : bnez a4,0x800cb078
0x800cb070 : sub a0,a5,a0
0x800cb074 : ret
0x800cb078 : addi a5,a5,1
0x800cb07c : j 0x800cb064
0x800cb080 : addi sp,sp,-32
0x800cb084 : sd s0,16(sp)
0x800cb088 : sd ra,24(sp)
0x800cb08c : li s0,0Mettons un point d'arrêt sur strnlen, continuons et voyons :
(gdb) bt
#0 strnlen (s=s@entry=0x10060000 "", count=18446744073709551615) à lib/string.c:283
#1 0x00000000800cc14c dans string (buf=buf@entry=0x881cbd4c "", end=end@entry=0x881cc15c "", s=0x10060000 "", field_width=, precision=, flags=) à lib/vsprintf.c:265
#2 0x00000000800cc63c dans vsnprintf_internal (buf=buf@entry=0x881cbd38 "code d'exception : 5 , ", size=size@entry=1060, fmt=0x800d446e "s , epc x , ra lxn", fmt@entry=0x800d4458 "code d'exception : %d , %s , epc x , ra lxn", args=0x881cc1a0,
args@entry=0x881cc188) à lib/vsprintf.c:619
#3 0x00000000800cca54 dans vsnprintf (buf=buf@entry=0x881cbd38 "code d'exception : 5 , ", size=size@entry=1060, fmt=fmt@entry=0x800d4458 "code d'exception : %d , %s , epc x , ra lxn", args=args@entry=0x881cc188) à lib/vsprintf.c:710
#4 0x00000000800cca68 dans vscnprintf (buf=buf@entry=0x881cbd38 "code d'exception : 5 , ", size=size@entry=1060, fmt=fmt@entry=0x800d4458 "code d'exception : %d , %s , epc x , ra lxn", args=args@entry=0x881cc188) à lib/vsprintf.c:717
#5 0x00000000800ccb50 dans printf (fmt=fmt@entry=0x800d4458 "code d'exception : %d , %s , epc x , ra lxn") à lib/vsprintf.c:792
#6 0x000000008008a9f0 dans _exit_trap (regs=, epc=2148315240, code=) à arch/riscv/lib/interrupts.c:92
#7 handle_trap (mcause=, epc=, regs=) à arch/riscv/lib/interrupts.c:55
#8 0x0000000080089b10 dans trap_entry () à /hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot/arch/riscv/cpu/HiFive/start.S:343
La trace s'est arrêtée : le cadre n'a pas enregistré le PCIl semble que _exit_trap veut afficher des informations de débogage sur l'exception survenue, mais il ne peut pas. Donc, quelque chose ne s'affiche pas dans le code source. set directories ../freedom-u-sdk/HiFive_U-Boot/ Oh ! Maintenant ça s'affiche !
Eh bien, relançons et voyons dans la trace de pile la raison du problème initial qui a causé la première erreur (mcause == 5). Si j'ai bien compris ce qui est écrit à la page 37, cette exception signifie erreur d'accès au chargement. La raison semble être que ici
arch/riscv/cpu/HiFive/start.S :
call_board_init_f:
li t0, -16
li t1, CONFIG_SYS_INIT_SP_ADDR
and sp, t1, t0 /* forcer un alignement à 16 octets */
#ifdef CONFIG_DEBUG_UART
jal debug_uart_init
#endif
call_board_init_f_0:
mv a0, sp
jal board_init_f_alloc_reserve
mv sp, a0
jal board_init_f_init_reserve
mv a0, zero /* a0 <-- boot_flags = 0 */
la t5, board_init_f
jr t5 /* saut vers board_init_f() */
$sp a cette valeur incorrecte, et une erreur se produit à l'intérieur de board_init_f_init_reserve . Il semble que ce soit le coupable : la variable avec un nom sans équivoque CONFIG_SYS_INIT_SP_ADDR. Elle est définie dans le fichier HiFive_U-Boot/include/configs/HiFive-U540.h. À un moment donné, j'ai même pensé que peut-être il serait plus facile d'ajuster légèrement le processeur plutôt que de modifier le chargeur pour le processeur. Mais j'ai ensuite réalisé que cela ressemblait plus à un artefact d'une configuration inachevée pour un autre type de mémoire, et que l'on pouvait essayer de faire comme suit :#if 0- des réglages non terminés pour une autre configuration de mémoire, et on peut essayer de faire comme suit :
diff --git a/include/configs/HiFive-U540.h b/include/configs/HiFive-U540.h
index ca89383..245542c 100644
--- a/include/configs/HiFive-U540.h
+++ b/include/configs/HiFive-U540.h
@@ -65,12 +65,9 @@
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_0
#endif
#if 1
-/*#define CONFIG_NR_DRAM_BANKS 1*/
+#define CONFIG_NR_DRAM_BANKS 1
#define PHYS_SDRAM_0 0x80000000 /* Banque SDRAM #1 */
-#define PHYS_SDRAM_1
- (PHYS_SDRAM_0 + PHYS_SDRAM_0_SIZE) /* Banque SDRAM #2 */
-#define PHYS_SDRAM_0_SIZE 0x80000000 /* 2 Go */
-#define PHYS_SDRAM_1_SIZE 0x10000000 /* 256 Mo */
+#define PHYS_SDRAM_0_SIZE 0x40000000 /* 1 Go */
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_0
#endif
/*
@@ -81,7 +78,7 @@
#define CONSOLE_ARG "console=ttyS0,115200 "
/* Initialiser le pointeur de pile */
-#define CONFIG_SYS_INIT_SP_ADDR (0x08000000 + 0x001D0000 -
+#define CONFIG_SYS_INIT_SP_ADDR (0x80000000 + 0x001D0000 -
GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_LOAD_ADDR 0xa0000000 /* en haut de la SDRAM */À un moment donné, le nombre de béquilles a atteint un seuil critique. Après quelques réflexions, j'en suis venu à la nécessité de réaliser un port correct pour ma carte. Pour cela, il faut copier et adapter un certain nombre de fichiers à notre configuration.
Eh bien, approximativement, voilà un petit échantillon
trosinenko@trosinenko-pc:/hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot$ git show --name-status
commit 39cd67d59c16ac87b46b51ac1fb58f16f1eb1048 (HEAD -> zeowaa-1gb)
Author: Anatoly Trosinenko
Date: Tue Jul 2 17:13:16 2019 +0300
Support initial pour la carte Zeowaa A-E115FB
M arch/riscv/Kconfig
A arch/riscv/cpu/zeowaa-1gb/Makefile
A arch/riscv/cpu/zeowaa-1gb/cpu.c
A arch/riscv/cpu/zeowaa-1gb/start.S
A arch/riscv/cpu/zeowaa-1gb/timer.c
A arch/riscv/cpu/zeowaa-1gb/u-boot.lds
M arch/riscv/dts/Makefile
A arch/riscv/dts/zeowaa-1gb.dts
A board/Zeowaa/zeowaa-1gb/Kconfig
A board/Zeowaa/zeowaa-1gb/MAINTAINERS
A board/Zeowaa/zeowaa-1gb/Makefile
A board/Zeowaa/zeowaa-1gb/Zeowaa-A-E115FB.c
A configs/zeowaa-1gb_defconfig
A include/configs/zeowaa-1gb.hLes détails peuvent être consultés dans .
Il s'est avéré que sur cette carte SiFive, les registres de certains périphériques ont des adresses différentes. Il s'est également avéré que U-Boot est configuré par le même mécanisme Kconfig que celui que l'on connaît du noyau Linux — par exemple, on peut donner l'ordre make menuconfig, et une interface texte conviviale apparaîtra avec la description des paramètres. ? En gros, en combinant les descriptions de deux cartes pour créer celle d'une troisième, et en supprimant les réglages exagérés de PLL (je suppose que cela a un rapport avec la gestion depuis l'ordinateur hôte via PCIe, mais je n'en suis pas sûr), j'ai obtenu un firmware qui, par temps favorable sur Mars, m'affichait via UART le hash du commit dont il était construit, ainsi que la quantité de DRAM (mais cette information, c'est moi qui l'ai définie dans l'en-tête).
Il est dommage qu'après cela, la carte cessait généralement de répondre via le JTAG du processeur, et le démarrage depuis la carte SD — eh bien, dans ma configuration, ce n'est pas rapide. D'un autre côté, parfois le BootROM signalait que ERREUR, le démarrage a échoué, et U-Boot apparaissait immédiatement. C'est alors que j'ai réalisé : apparemment, après le redémarrage, le bitstream dans le FPGA ne rafraîchit pas la mémoire, il n'a pas le temps de « se désynchroniser », etc. En gros, on peut simplement, dès l'apparition du message LOADING / se connecter avec le débogueur et donner la commande set variable $pc=0x80089800, contournant ainsi ce long démarrage (évidemment, en supposant qu'il ait échoué assez tôt la dernière fois et qu'il n'ait pas réussi à charger quelque chose par-dessus le code original).
D'ailleurs, est-ce normal que le processeur se fige complètement, et que le débogueur JTAG ne puisse se connecter avec les messages
Error: unable to halt hart 0
Error: dmcontrol=0x80000001
Error: dmstatus =0x00030c82Attendez une minute ! Je l'ai déjà vu ! Quelque chose de similaire se produit lors d'un deadlock TileLink, et je n'ai pas vraiment confiance en l'auteur du contrôleur de mémoire - il l'a écrit lui-même... Étrangement, après la première reconstruction réussie du processeur après la modification du contrôleur, j'ai vu :
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING
BOOT
U-Boot 2018.09-g39cd67d-dirty (03 juillet 2019 - 13:50:33 +0300)
DRAM: 1 GiB
MMC:
BEFORE LOAD ENVBEFORE FDTCONTROLADDRBEFORE LOADADDRIn: serial
Out: serial
Err: serial
Appuyez sur n'importe quelle touche pour arrêter le démarrage automatique : 3Cette étrange ligne avant In: serial Ne vous en faites pas — c'est moi qui essayais de comprendre sur un processeur gelé si cela fonctionnait correctement avec l'environnement. Que signifie « Ça fait déjà dix minutes que ça reste bloqué » ? Au moins, il a réussi à se relocaliser et à passer au menu de démarrage ! Petite parenthèse : bien que U-Boot se charge parmi les premiers 2^24 octets de la carte SD, une fois lancé, il se copie ailleurs à l'adresse soit écrite dans l'en-tête de configuration, soit simplement dans les adresses supérieures de la mémoire vive, effectue la relocalisation des symboles ELF et cède le contrôle à cet endroit. Donc, il semble que ce niveau ait été franchi et que, en prime, nous avons un processeur qui ne reste pas bloqué après cela.
Alors, pourquoi le minuteur ne fonctionne pas ? Il semble que l'horloge, pour une raison quelconque, ne tourne pas du tout…
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000Et si on faisait tourner les aiguilles manuellement ?
(gdb) set variable *0x0200bff8=310000000
(gdb) cAlors :
Appuyez sur n'importe quelle touche pour arrêter l'autoboot : 0
MMC_SPI : 0 à 0:1 hz 20000000 mode 0Conclusion : l'horloge ne fonctionne pas. Peut-être que cela explique également pourquoi l'entrée du clavier ne fonctionne pas :
HiFive_U-Boot/cmd/bootmenu.c :
static void bootmenu_loop(struct bootmenu_data *menu,
enum bootmenu_key *key, int *esc)
{
int c;
while (!tstc()) {
WATCHDOG_RESET();
mdelay(10);
}
c = getc();
switch (*esc) {
case 0:
/* Premier caractère de la séquence d'échappement ANSI 'e' */
if (c == 'e') {
*esc = 1;
*key = KEY_NONE;
}
break;
case 1:
/* Deuxième caractère de ANSI '[' */
if (c == '[') {
...Le problème était que j'avais un peu trop complexifié les choses : j'avais ajouté dans le config du processeur la clé :
case DTSTimebase => BigInt(0)… en me basant sur le fait qu'il était dit dans le commentaire « si vous ne savez pas, laissez 0 ». Et bien WithNBigCores il était justement fixé à 1 MHz (comme d'ailleurs mentionné dans le config de U-Boot). Mais je suis, enfin, minutieux et pointilleux : ici je ne sais pas, là 25 MHz ! Au final, rien ne fonctionne. J'ai retiré mes « améliorations » et…
Appuyez sur n'importe quelle touche pour arrêter l'autoboot : 0
MMC_SPI : 0 à 0:1 hz 20000000 mode 0
## Type de table de partition inconnu 0
libfdt fdt_path_offset() retourné FDT_ERR_NOTFOUND
** Pas de table de partition - mmc 0 **
## Info : taille des données d'entrée = 34 = 0x22
Exécution de uEnv.txt boot2...
## Erreur : "boot2" non défini
HiFive-Unleashed #On peut même entrer des commandes ! Par exemple, après avoir un peu fouillé, on peut finalement deviner d'entrer mmc_spi 1 10000000 0; mmc part, réduisant la fréquence SPI de 20 MHz à 10 MHz. Pourquoi ? Eh bien, dans la configuration, la fréquence maximale était définie à 20 MHz, et elle y figure encore. Mais, si j'ai bien compris, les interfaces, du moins ici, fonctionnent ainsi : le code divise la fréquence du bloc matériel (chez moi — partout 25 MHz) par la fréquence cible, et définit cette valeur comme diviseur dans le registre de contrôle approprié. Le problème est que si pour un UART à 115200 Hz, cela pourrait convenir, en divisant directement 25000000 par 20000000, on obtient 1, c'est-à-dire que cela fonctionnera à 25 MHz. Cela peut être acceptable, mais si des limites sont définies, c'est qu'il y a une raison (mais ce n'est pas certain)... En gros, il est plus facile de mettre en place cela et d’avancer — loin et, hélas, pour longtemps. 25 MHz — ce n'est pas un Core i9.
Sortie de la console
HiFive-Unleashed # env edit mmcsetup
edit: mmc_spi 1 10000000 0; mmc part
HiFive-Unleashed # boot
MMC_SPI: 1 à 0:1 hz 10000000 mode 0
Carte des partitions pour l'appareil MMC 0 -- Type de partition: EFI
Part LBA de départ LBA de fin Nom
Attributs
Type GUID
Partition GUID
1 0x00000800 0x0000ffde "Vfat Boot"
attrs: 0x0000000000000000
type: ebd0a0a2-b9e5-4433-87c0-68b6b72699c7
type: data
guid: 76bd71fd-1694-4ff3-8197-bfa81699c2fb
2 0x00040800 0x002efaf4 "root"
attrs: 0x0000000000000000
type: 0fc63daf-8483-4772-8e79-3d69d8477de4
type: linux
guid: 9f3adcc5-440c-4772-b7b7-283124f38bf3
3 0x0000044c 0x000007e4 "uboot"
attrs: 0x0000000000000000
type: 5b193300-fc78-40cd-8002-e86c45580b47
guid: bb349257-0694-4e0f-9932-c801b4d76fa3
4 0x00000400 0x0000044b "uboot-env"
attrs: 0x0000000000000000
type: a09354ac-cd63-11e8-9aff-70b3d592f0fa
guid: 4db442d0-2109-435f-b858-be69629e7dbf
libfdt fdt_path_offset() a retourné FDT_ERR_NOTFOUND
2376 octets lus en 0 ms
Exécution de uEnv.txt boot2...
15332118 octets lus en 0 ms
## Chargement du noyau depuis l'image FIT à 90000000 ...
Utilisation de la configuration 'config-1'
Essai du sous-élément de noyau 'bbl'
Description: BBL/SBI/riscv-pk
Type: Image de noyau
Compression: non compressé
Début des données: 0x900000d4
Taille des données: 74266 Octets = 72.5 KiB
Architecture: RISC-V
OS: Linux
Adresse de chargement: 0x80000000
Point d'entrée: 0x80000000
Algo de hachage: sha256
Valeur de hachage: 28972571467c4ad0cf08a81d9cf92b9dffc5a7cb2e0cd12fdbb3216cf1f19cbd
Vérification de l'intégrité du hachage ... sha256+ OK
## Chargement de fdt depuis l'image FIT à 90000000 ...
Utilisation de la configuration 'config-1'
Essai du sous-élément fdt 'fdt'
Description: indisponible
Type: Flat Device Tree
Compression: non compressé
Début des données: 0x90e9d31c
Taille des données: 6911 Octets = 6.7 KiB
Architecture: RISC-V
Adresse de chargement: 0x81f00000
Algo de hachage: sha256
Valeur de hachage: 10b0244a5a9205357772ea1c4e135a4f882409262176d8c7191238cff65bb3a8
Vérification de l'intégrité du hachage ... sha256+ OK
Chargement de fdt de 0x90e9d31c à 0x81f00000
Démarrage à l'aide du blob fdt à 0x81f00000
## Chargement des éléments chargeables depuis l'image FIT à 90000000 ...
Essai du sous-élément chargés 'noyau'
Description: noyau Linux
Type: Image de noyau
Compression: non compressé
Début des données: 0x900123e8
Taille des données: 10781356 Octets = 10.3 MiB
Architecture: RISC-V
OS: Linux
Adresse de chargement: 0x80200000
Point d'entrée: indisponible
Algo de hachage: sha256
Valeur de hachage: 72a9847164f4efb2ac9bae736f86efe7e3772ab1f01ae275e427e2a5389c84f0
Vérification de l'intégrité du hachage ... sha256+ OK
Chargement des éléments chargeables depuis 0x900123e8 à 0x80200000
## Chargement des éléments chargeables depuis l'image FIT à 90000000 ...
Essai du sous-élément chargés 'ramdisk'
Description: buildroot initramfs
Type: Image RAMDisk
Compression: compressé gzip
Début des données: 0x90a5a780
Taille des données: 4467411 Octets = 4.3 MiB
Architecture: RISC-V
OS: Linux
Adresse de chargement: 0x82000000
Point d'entrée: indisponible
Algo de hachage: sha256
Valeur de hachage: 883dfd33ca047e3ac10d5667ffdef7b8005cac58b95055c2c2beda44bec49bd0
Vérification de l'intégrité du hachage ... sha256+ OK
Chargement des éléments chargeables depuis 0x90a5a780 à 0x82000000D'accord, nous sommes passés à un nouveau niveau, mais cela bloque toujours. Parfois, il génère même des exceptions. Vous pouvez voir mcause en interceptant le code à l'adresse indiquée. $pc et après si se retrouver sur trap_entry. Le gestionnaire U-Boot ne peut afficher que pour mcause = 0..4, préparez-vous à entrer dans une boucle sur un chargement incorrect. Je suis allé dans la configuration, j'ai commencé à regarder ce que j'avais changé et je me suis rappelé : c'est inscrit là-bas, conf/rvboot-fit.txt il est écrit :
fitfile=image.fit
# en dessous doit correspondre à ce qui est dans FIT (ugha)Eh bien, mettons tous les fichiers en conformité, remplaçons la ligne de commande du noyau à peu près de cette manière, car il y a des soupçons que SIF0 — cela signifie une sortie quelque part sur le PCIe :
-bootargs=console=ttySIF0,921600 debug
+bootargs=console=ttyS0,125200 debugEt, tant qu'on y est, changeons l'algorithme de hachage de SHA-256 à MD5 : je n'ai pas besoin de cryptographie (surtout pendant le débogage), cela prend énormément de temps, et pour la détection des erreurs d'intégrité au chargement, MD5 est largement suffisant. Alors, au final ? Nous passons le niveau précédent beaucoup plus rapidement (grâce à un hachage plus simple), et le suivant s'est ouvert :
...
Vérification de l'intégrité du hachage ... md5+ OK
Chargement des fichiers à partir de 0x90a5a758 jusqu'à 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
chosen {
linux,initrd-end = ;
linux,initrd-start = ;
riscv,kernel-end = ;
riscv,kernel-start = ;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
};
libfdt fdt_path_offset() a retourné FDT_ERR_NOTFOUND
chosen {
linux,initrd-end = ;
linux,initrd-start = ;
riscv,kernel-end = ;
riscv,kernel-start = ;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
};
Chargement de l'image du noyau ... OK
Démarrage du noyau dans
3Sauf que l'horloge ne tourne pas…
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000Oups, il semble que corriger l'heure était un placebo, bien que cela m'ait semblé utile à l'époque. Non, il faut bien le réparer, mais commençons par ajuster les aiguilles manuellement et voyons ce que ça donne :
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=1000000
(gdb) c
Continuing.
^C
Le programme a reçu le signal SIGINT, Interruption.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=2000000
(gdb) c
Continuing.
^C
Le programme a reçu le signal SIGINT, Interruption.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=3000000
(gdb) c
Continuing.En attendant…
Chargement de l'image du noyau ... OK
Démarrage du noyau dans
3
2
1
0
## Démarrage de l'application à 0x80000000 ...Non, je vais automatiser le réglage de l'horloge — sinon, peut-être qu'il va penser à calibrer le minuteur !
Et l'adresse de l'instruction actuelle pointait quant à elle vers
0000000080001c20 :
80001c20: 1141 addi sp,sp,-16
80001c22: e022 sd s0,0(sp)
80001c24: 842a mv s0,a0
80001c26: 00005517 auipc a0,0x5
80001c2a: 0ca50513 addi a0,a0,202 # 80006cf0
80001c2e: e406 sd ra,8(sp)
80001c30: f7fff0ef jal ra,80001bae
80001c34: 8522 mv a0,s0
80001c36: 267000ef jal ra,8000269c
80001c3a: 00010797 auipc a5,0x10
80001c3e: 41e78793 addi a5,a5,1054 # 80012058
80001c42: 639c ld a5,0(a5)
80001c44: c399 beqz a5,80001c4a
80001c46: 72c000ef jal ra,80002372
80001c4a: 45a1 li a1,8
80001c4c: 4501 li a0,0
80001c4e: dc7ff0ef jal ra,80001a14
80001c52: 10500073 wfi
80001c56: bff5 j 80001c52dans le chargeur de démarrage Berkeley Boot Loader. Personnellement, je suis perplexe par la mention htif — host interface, utilisé pour le démarrage tethered du noyau (c’est-à-dire en coopération avec l’ARM hôte), je pensais que c'était en mode standalone. Cependant, si vous trouvez cette fonction dans le code source, il est clair que ce n’est pas si grave :
void poweroff(uint16_t code)
{
printm("Power offrn");
finisher_exit(code);
if (htif) {
htif_poweroff();
} else {
send_ipi_many(0, IPI_HALT);
while (1) { asm volatile ("wfin"); }
}
}Quête : lancez les horloges
La recherche des registres dans CLINT nous mène à
val io = IO(new Bundle {
val rtcTick = Bool(INPUT)
})
val time = RegInit(UInt(0, width = timeWidth))
when (io.rtcTick) { time := time + UInt(1) }Qui se connecte au RTC, ou dans le mystérieux MockAON, au sujet duquel j'avais initialement pensé : « Qu'est-ce que c'est ici ? Peu clair ? Débranchons ! » Puisque je ne comprends toujours pas quelle magie d'horloge s'y déroule, je vais simplement réimplémenter cette logique dans System.scala:
val rtcDivider = RegInit(0.asUInt(16.W)) // par précaution je vais supporter jusqu'à 16 GHz, je suis optimiste :)
val mhzInt = p(DevKitFPGAFrequencyKey).toInt
// Supposons que la fréquence soit un nombre entier en mégahertz
rtcDivider := Mux(rtcDivider === (mhzInt - 1).U, 0.U, rtcDivider + 1.U)
outer.clintOpt.foreach { clint =>
clint.module.io.rtcTick := rtcDivider === 0.U
}En parcourant le noyau Linux
L'histoire ici est déjà trop longue et un peu monotone, donc je vais la survoler :
BBL supposait la présence du FDT à l'adresse 0xF0000000, mais je l'avais déjà corrigé ! Eh bien, cherchons encore... Trouvé dans HiFive_U-Boot/arch/riscv/lib/boot.c, remplacé par 0x81F00000, spécifié dans la configuration du démarrage de U-Boot.
Puis, BBL s'est plaint qu'il n'y avait pas assez de mémoire. Mon chemin m'a conduit à la fonction mem_prop, ce qui dans riscv-pk/machine/fdt.c: de là, j'ai appris qu'il fallait marquer le nœud fdt ram comme device_type = "memory" — ensuite, peut-être qu'il faudra ajuster le générateur de processeur, mais pour l'instant je vais juste le remplir à la main — de toute façon, j'ai transféré ce fichier manuellement.
J'ai maintenant reçu un message (fournit au format structuré, avec des retours de chariot) :
This is bbl's dummy_payload. To boot a real kernel, reconfigure bbl
with the flag --with-payload=PATH, then rebuild bbl. Alternatively,
bbl can be used in firmware-only mode by adding device-tree nodes
for an external payload and use QEMU's -bios and -kernel options.Il semble que les options soient indiquées comme il se doit riscv,kernel-start et riscv,kernel-end dans le DTB, mais des zéros sont analysés. Débogage query_chosen a montré que BBL essaie d'analyser une adresse 32 bits, mais se retrouve avec une paire <0x0 0xADDR>, et la première valeur semble être les bits de poids faible. J'ai ajouté dans la section chosen
chosen {
#address-cells = ;
#size-cells = ;
...
}et ajusté la génération des valeurs : ne pas ajouter 0x0 comme premier élément.
Ces 100500 étapes simples permettront de voir facilement comment le pingouin tombe :
Texte caché
Vérification de l'intégrité du hachage ... md5+ OK
Chargement des objets à partir de 0x90a5a758 vers 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
choisi {
linux,initrd-end = ;
linux,initrd-start = ;
riscv,kernel-end = ;
riscv,kernel-start = ;
#address-cells = ;
#size-cells = ;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
libfdt fdt_path_offset() a retourné FDT_ERR_NOTFOUND
choisi {
linux,initrd-end = ;
linux,initrd-start = ;
riscv,kernel-end = ;
riscv,kernel-start = ;
#address-cells = ;
#size-cells = ;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
Chargement de l'image du noyau ... OK
Démarrage du noyau dans
3
2
1
0
## Démarrage de l'application à 0x80000000 ...
bbl loader
SIFIVE, INC.
5555555555555555555555555
5555 5555
5555 5555
5555 5555
5555 5555555555555555555555
5555 555555555555555555555555
5555 5555
5555 5555
5555 5555
5555555555555555555555555555 55555
55555 555555555 55555
55555 55555 55555
55555 5 55555
55555 55555
55555 55555
55555 55555
55555 55555
55555 55555
555555555
55555
5
SiFive RISC-V Core IP
[ 0.000000] OF: fdt: Ignorer la plage mémoire 0x80000000 - 0x80200000
[ 0.000000] Version de Linux 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc version 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99)) #1 SMP Mer Jul 3 21:29:21 MSK 2019
[ 0.000000] console de démarrage [early0] activée
[ 0.000000] Ramdisk initial à : 0x(____ptrval____) (16777216 octets)
[ 0.000000] Plages de zone :
[ 0.000000] DMA32 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Normal [mem 0x00000000c0000000-0x00000bffffffffff]
[ 0.000000] Départ de la zone mobile pour chaque nœud
[ 0.000000] Plages de nœuds de mémoire précoce
[ 0.000000] nœud 0 : [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Configuration de la mémoire initiale nœud 0 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Au nœud 0, nombre total de pages : 261632
[ 0.000000] zone DMA32 : 3577 pages utilisées pour memmap
[ 0.000000] zone DMA32 : 0 page réservée
[ 0.000000] zone DMA32 : 261632 pages, lot LIFO : 63
[ 0.000000] TLB IO logiciel : mappé [mem 0xbb1fc000-0xbf1fc000] (64 Mo)(l'emblème est affiché par BBL, et ce qui concerne les horodatages — le noyau).
Heureusement, je ne sais pas comment partout, mais sur RocketChip, lors de la connexion du débogueur via JTAG, on peut capter des trap dès la sortie de la boîte — le débogueur s'arrêtera exactement à ce moment-là.
Le programme a reçu le signal SIGTRAP, piège de trace/d'arrêt.
0xffffffe0000024ca dans ?? ()
(gdb) bt
#0 0xffffffe0000024ca dans ?? ()
Trace de retour arrêtée : le cadre précédent est identique à ce cadre (pile corrompue ?)
(gdb) fichier work/linux/vmlinux
Un programme est déjà en cours de débogage.
Êtes-vous sûr de vouloir changer le fichier ? (y ou n) y
Lecture des symboles à partir de work/linux/vmlinux... terminé.
(gdb) bt
#0 0xffffffe0000024ca dans setup_smp () à /hdd/trosinenko/fpga/freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c:75
#1 0x0000000000000000 dans ?? ()
Trace de retour arrêtée : le cadre n'a pas enregistré le PC.freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c :
void __init setup_smp(void)
{
struct device_node *dn = NULL;
int hart;
bool found_boot_cpu = false;
int cpuid = 1;
while ((dn = of_find_node_by_type(dn, "cpu"))) {
hart = riscv_of_processor_hartid(dn);
if (hart < 0)
continue;
if (hart == cpuid_to_hartid_map(0)) {
BUG_ON(found_boot_cpu);
found_boot_cpu = 1;
continue;
}
cpuid_to_hartid_map(cpuid) = hart;
set_cpu_possible(cpuid, true);
set_cpu_present(cpuid, true);
cpuid++;
}
BUG_ON(!found_boot_cpu); // < VOUS ÊTES ICI
}Comme le disait une vieille blague, CPU non trouvé, exécution de l'émulation logicielle. Ou pas. Égaré dans un seul cœur de processeur.
/* The lucky hart to first increment this variable will boot the other cores */
atomic_t hart_lottery;
unsigned long boot_cpu_hartid;Un bon commentaire dans linux/arch/riscv/kernel/setup.c — c'est comme peindre une clôture selon la méthode de Tom Sawyer. En gros, aujourd'hui, il n'y a étonnamment pas de gagnants, le prix est reporté au prochain tirage...
Je propose de conclure cet article déjà trop long.
À suivre. Il y aura un combat avec une erreur sournoise qui parvient à se cacher si on l'approche lentement avec singlestep.
Screencast textuel de chargement (lien externe):
Source : habr.com
