In è stato implementato un controller di memoria funzionante, o meglio, un wrapper sopra l'IP Core di Quartus, che funge da adattatore per TileLink. Oggi, nella rubrica 'Portiamo RocketChip su una poco conosciuta scheda cinese con Cyclone', vedrete una console funzionante. Il processo si è un po' allungato: pensavo di avviare rapidamente Linux e proseguire, ma così non è stato. In questa parte, vi propongo di guardare il processo di avvio di U-Boot, BBL e le timide tentativi di inizializzazione del kernel Linux. Ma la console c'è — quella di U-Boot, e piuttosto avanzata, che ha molto di ciò che ci si aspetta da una console completa.
Nella parte hardware verrà aggiunta una scheda SD, collegata tramite interfaccia SPI, e anche UART. Nella parte software, il BootROM sarà sostituito con xip con sdboot e, propriamente, saranno aggiunte le seguenti fasi di avvio (sulla scheda SD).
Miglioramento della parte hardware
Quindi, l'obiettivo: dobbiamo passare a un 'grande' core e collegare UART (da Raspberry) e un adattatore SD (è stata utilizzata una scheda da Catalex con sei pin: GND, VCC, MISO, MOSI, SCK, CS).
In effetti, è stato piuttosto semplice. Ma prima di rendermene conto, sono stato un po' sbalzato da un lato all'altro: dopo l'ultima volta ho deciso che dovevo solo mischiare di nuovo in System qualcosa del tipo HasPeripheryUART (e nella relativa implementazione), lo stesso per la scheda SD — e tutto sarà pronto. Poi ho deciso di dare un'occhiata a come è implementato in un design 'serio'. Allora, cosa abbiamo qui di serio? Arty, evidentemente, non si adatta — rimane il mostro unleahshed.DevKitConfigs. E improvvisamente ho scoperto che ci sono sovrapposizioni ovunque, che vengono aggiunte tramite parametri a chiave. Intuiamo che probabilmente è molto flessibile e configurabile, ma avrei bisogno di qualcosa per iniziare a far funzionare… Non avete qualcosa di simile, solo un po' più semplice e rudimentale?.. Qui mi sono imbattuto in vera.iofpga.FPGAChip per FPGA Microsemi e subito l'ho smontato in citazioni provando a realizzare la mia implementazione per analogia, fortunatamente qui c'è più o meno tutta la 'disposizione della scheda madre' in un unico file.
Si è scoperto che, in effetti, basta aggiungere a System.scala alcune righe
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
...Riga nel corpo della classe System aggiunge informazioni sulla frequenza alla quale lavora questa parte del nostro SoC nel file dts. Tanto che capisco, DTS/DTB è una sorta di analogico statico della tecnologia plug-and-play per dispositivi embedded: l'albero di descrizione dts viene compilato in un file binario dtb e passato dal bootloader al kernel per consentirgli di configurare correttamente l'hardware. Curiosamente, senza la riga con tlclock tutto si sintetizza perfettamente, ma non si potrà compilare BootROM (ricordo, ora sarà già sdboot) — durante il processo di compilazione analizza il file dts e crea un'intestazione con il macro TL_CLK, grazie al quale potrà configurare correttamente i divisori di frequenza per le interfacce esterne.
Occorrerà anche correggere un po' il "routing":
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))
// SD card
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
}Le catene di registri, a dire il vero, sono state aggiunte semplicemente per analogia con alcune altre parti del codice originale. Probabilmente dovrebbero proteggere da . Forse in alcuni blocchi c'è già una protezione propria, ma per iniziare vorrei farlo almeno a un "livello qualitativo". Una domanda che mi interessa di più è: perché MISO e MOSI sono collegati a diverse dq? Ответа я пока так и не нашёл, но, похоже, остальной код рассчитывает именно на такое подключение.
Fisicamente, ho semplicemente assegnato i pin di design su contatti liberi sul connettore e ho spostato il jumper per la scelta della tensione a 3.3V.
SD-adattatore
Vista dall'alto:

Vista dal basso:

Debugging della parte software: strumenti
Iniziamo a parlare degli strumenti di debug disponibili e dei loro limiti.
Minicom
Per prima cosa, avremo bisogno di leggere ciò che il bootloader e il kernel stanno producendo. A tal fine, su Linux (in questo caso — su quello che è su RaspberryPi) ci vorrà il programma Minicom. In generale, qualsiasi programma per lavorare con la porta seriale andrà bene.
Si prega di notare che durante l'avvio il nome del dispositivo della porta deve essere specificato come -D /dev/ttyS0 — dopo l'opzione -D. E la cosa principale: per uscire utilizzare Ctrl-A, X. In effetti, ho avuto un caso in cui questa combinazione non ha funzionato — allora si può semplicemente dire dal terminale SSH adiacente killall -KILL minicom.
C'è anche un'altra particolarità. In particolare su Raspberry Pi ci sono due UART, e entrambe le porte potrebbero essere già allocate per qualcosa: una per il Bluetooth, mentre l'altra di default visualizza la console del kernel. Fortunatamente, questo comportamento può essere riconfigurato .
Scrittura in memoria
Durante il debug, per verificare un'ipotesi a volte mi è stato necessario caricare il bootloader (scusate) nella memoria RAM direttamente dall'host. Forse si può fare direttamente da GDB, ma alla fine ho scelto la strada più semplice: ho copiato sul Raspberry il file necessario, ho inoltrato anche la porta 4444 tramite SSH (telnet da OpenOCD) e ho usato il comando load_image. Quando lo esegui, sembra che tutto si sia bloccato, ma in realtà «non sta dormendo, sta solo lampeggiando lentamente»: sta caricando il file, lo fa semplicemente alla velocità di qualche kilobyte al secondo.
Caratteristiche dell'installazione dei breakpoint
Probabilmente, molti non ci hanno mai pensato durante il debug di normali programmi, ma i breakpoint non vengono sempre impostati a livello hardware. A volte, impostare un breakpoint significa scrivere temporaneamente un'istruzione speciale nel punto desiderato direttamente nel codice macchina. Ad esempio, così funzionava il mio comando standard b in GDB. Ecco cosa ne consegue:
- non è possibile impostare un punto all'interno del BootROM, perché il ROM
- è possibile mettere un punto di interruzione sul codice caricato in RAM da una scheda SD, ma bisogna aspettare che venga caricato. Altrimenti, non saremo noi a sovrascrivere un pezzo di codice, ma il bootloader sovrascriverà il nostro breakpoint
Sono sicuro che si possa richiedere esplicitamente di utilizzare breakpoint hardware, ma in ogni caso sono limitati in numero.
Sostituzione rapida del BootROM
Nella fase iniziale del debug, spesso sorge il desiderio di modificare il BootROM e riprovare. Ma c'è un problema: il BootROM è parte del design caricato nella FPGA, e la sua sintesi richiede alcuni minuti (e questo dopo la quasi istantanea compilazione dell'immagine BootROM da C e Assembler…). Fortunatamente, in realtà è tutto molto più veloce: la sequenza di azioni è la seguente:
- rigenerare bootrom.mif (sono passato a MIF invece di HEX, perché con HEX avevo sempre qualche problema, mentre MIF è il formato nativo di Altera)
- in Quartus dire
Processing -> Aggiorna il file di inizializzazione della memoria - nella voce Assembler (nella colonna sinistra Attività) dare il comando Ripeti
Per tutto questo servono solo pochi secondi.
Preparazione della scheda SD
Qui tutto è relativamente semplice, ma è necessario avere pazienza e circa 14Gb di spazio disponibile sul disco:
git clone https://github.com/sifive/freedom-u-sdk
git submodule update --recursive --init
makeDopo di che bisogna inserire una scheda SD pulita, o meglio, priva di qualsiasi dato utile, e eseguire
sudo make DISK=/dev/sdX format-boot-loader… dove sdX è il dispositivo assegnato alla scheda. ATTENZIONE: i dati sulla scheda verranno sterminati, sovrascritti e via dicendo! Non ha molto senso fare l'intera build da sudo, perché allora tutti gli artefatti della build appartengono a root, e la build dovrà essere fatta da sudo costantemente.
Alla fine si ottiene una scheda, contrassegnata in GPT con quattro partizioni, una delle quali è FAT con uEnv.txt e un'immagine di avvio in formato FIT (che contiene più sottoimmagini, ciascuna con il proprio indirizzo di avvio), l'altra partizione è pulita, e si presume che verrà formattata in Ext4 per Linux. Altre due partizioni — misteriosi: su uno vive U-Boot (il suo offset, se non erro, è incorporato nel BootROM), sull'altro, sembra che risiedano le sue variabili d'ambiente, ma per ora non le uso.
Livello uno, BootROM
Un proverbio popolare dice: «Se in programmazione ci sono danze attorno a un tamburello, in elettronica ci sono anche danze con un estintore». Non parlo nemmeno del fatto che una volta stavo per bruciare la scheda, pensando che «beh, GND è comunque un livello basso» (evidentemente, una resistenza non farebbe male…) Si tratta piuttosto del fatto che se le mani non sono nel posto giusto, l'elettronica continua a riservare sorprese: saldando un connettore sulla scheda, non sono riuscito a saldare i contatti correttamente — nei video mostrano come la saldatura si distribuisce da sola su tutta la connessione, basta toccare con il saldatore, mentre a me si «accumula» a caso. Beh, forse la saldatura non era adatta alla temperatura del saldatore, forse qualcos'altro… Insomma, vedendo che avevo già un buon numero di contatti, ho lasciato perdere e ho iniziato il debug. E qui è cominciato tutto. misterioso: ho collegato RX/TX dall'UART, carico il firmware — scrive
INIT
CMD0
ERRORBeh, tutto è logico: non ho collegato il modulo SD. Correggiamo la situazione, caricando il firmware... E silenzio... Ho pensato a tutto, ma il mistero si risolveva facilmente: uno dei pin del modulo doveva essere collegato a VCC. Nel mio caso, il modulo supportava 5V per l'alimentazione, quindi, senza pensarci troppo, ho collegato il cavo che proveniva dal modulo dall'altra parte della scheda. Alla fine, il connettore saldato male si è storto, e ho semplicemente perso il contatto UART. facepalm.jpg In generale, "una testa stupida non dà pace alle gambe", e mani maldestri alla testa...
Alla fine, ho visto in Minicom l'atteso
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING /Inoltre, l'indicatore di caricamento si muove e gira. Ricorda davvero i tempi scolastici e il caricamento lento di MinuetOS da dischetto. Solo che il lettore di dischetti non scricchiola.
Il problema è che, dopo il messaggio BOOT, non succede nulla. Quindi, è il momento giusto per collegarsi tramite OpenOCD a Raspberry, e a questo GDB sull'host, e vedere di cosa si tratta.
In primo luogo, la connessione tramite GDB ha subito mostrato che $pc (program counter, indirizzo dell'istruzione corrente) sta volando in 0x0 — probabilmente, accade dopo un errore multiplo. Pertanto, subito dopo il messaggio di BOOT aggiungiamo un ciclo infinito. Questo lo ritarderà un attimo…
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;
}Questo codice astuto è utilizzato "per affidabilità": ho sentito da qualche parte che, teoricamente, un ciclo infinito è un Comportamento Indefinito, e qui il compilatore difficilmente se ne accorgerà (Ricordo che secondo 0x10000 si trova BootROM).

A ben pensarci, cosa ci si può aspettare — il mondo degli embedded è così, che codici ci si possono aspettare. Ma in l'autore stava debuggando codice C… Crex-fex-pex:
(gdb) file builds/zeowaa-e115/sdboot.elf
Un programma è già in fase di debug.
Sei sicuro di voler cambiare il file? (y o n) y
Lettura dei simboli da builds/zeowaa-e115/sdboot.elf...completata.
Bisogna caricare non il file MIF e non bin, ma la versione originale nel formato ELF.
Ora si può provare a indovinare l'indirizzo da cui continuare l'esecuzione (questo è un motivo in più per cui il compilatore non avrebbe dovuto intuire che il ciclo è infinito). Il comando
set variable $pc=0xADDRconsente di modificare il valore di un registro al volo (in questo caso — l'indirizzo dell'istruzione corrente). Con lo stesso comando è possibile cambiare i valori scritti in memoria (e nei registri mappati in memoria).
Alla fine sono giunto alla conclusione (non sono sicuro che sia corretta) che abbiamo "un'immagine della scheda sd non di quel sistema", e dobbiamo passare non all'inizio dei dati caricati, ma a 0x89800 byte più avanti:
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 .rodataForse ciò è stato influenzato dal fatto che, non avendo a disposizione una scheda da 4Gb, ho preso una da 2Gb e, tramite tentativi, l'ho sostituita nel Makefile DEMO_END=11718750 con DEMO_END=3078900 (non cercate un significato nel valore specifico — non ce n'è, semplicemente ora l'immagine si adatta alla scheda).
Livello due, U-Boot
Ora stiamo ancora "cadendo", ma siamo già all'indirizzo 0x0000000080089a84. Qui devo ammettere: in realtà, la narrazione non procede "con tutte le fermate", ma viene scritta parzialmente "in seguito", quindi qui ho già avuto il tempo di inserire il corretto file dtb dal nostro SoC, correggere le impostazioni HiFive_U-Boot variabile CONFIG_SYS_TEXT_BASE=0x80089800 (invece di 0x08000000), affinché l'indirizzo di caricamento coincida con quello effettivo. Ora stiamo caricando la scheda di livello successivo con un'altra immagine:
(gdb) file ../freedom-u-sdk/work/HiFive_U-Boot/u-boot
(gdb) tui enE vediamo:
│304 /* │
│305 * trap entry │
│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) │Saltiamo tra le righe 308 e 309. E non sorprende, dato che $sp contiene il valore 0xfffffffe31cdc0a0. Purtroppo, continua a "scappare" a causa della riga 307. Dobbiamo quindi provare a impostare un breakpoint su trap_entry, e poi tornare a 0x80089800 (il punto d'ingresso U-Boot), nella speranza che non richieda la corretta impostazione dei registri prima del salto… Sembra funzionare:
(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 = 0x81cf950Non è un grande puntatore allo stack, a dirla tutta: punta completamente fuori dalla RAM (a meno che non abbiamo già la traduzione degli indirizzi, ma speriamo in una soluzione semplice).
Proviamo a sostituire il puntatore con 0x881cf950. In definitiva, arriviamo a handle_trap è chiamato e viene chiamato, mentre ci allontaniamo da _exit_trap con argomento epc=2148315240 (in forma decimale):
(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,0Impostiamo un breakpoint su strnlen, continuiamo e vediamo:
(gdb) bt
#0 strnlen (s=s@entry=0x10060000 "", count=18446744073709551615) at lib/string.c:283
#1 0x00000000800cc14c in string (buf=buf@entry=0x881cbd4c "", end=end@entry=0x881cc15c "", s=0x10060000 "", field_width=, precision=, flags=) at lib/vsprintf.c:265
#2 0x00000000800cc63c in vsnprintf_internal (buf=buf@entry=0x881cbd38 "exception code: 5 , ", size=size@entry=1060, fmt=0x800d446e "s , epc x , ra lxn", fmt@entry=0x800d4458 "exception code: %d , %s , epc x , ra lxn", args=0x881cc1a0,
args@entry=0x881cc188) at lib/vsprintf.c:619
#3 0x00000000800cca54 in vsnprintf (buf=buf@entry=0x881cbd38 "exception code: 5 , ", size=size@entry=1060, fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc x , ra lxn", args=args@entry=0x881cc188) at lib/vsprintf.c:710
#4 0x00000000800cca68 in vscnprintf (buf=buf@entry=0x881cbd38 "exception code: 5 , ", size=size@entry=1060, fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc x , ra lxn", args=args@entry=0x881cc188) at lib/vsprintf.c:717
#5 0x00000000800ccb50 in printf (fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc x , ra lxn") at lib/vsprintf.c:792
#6 0x000000008008a9f0 in _exit_trap (regs=, epc=2148315240, code=) at arch/riscv/lib/interrupts.c:92
#7 handle_trap (mcause=, epc=, regs=) at arch/riscv/lib/interrupts.c:55
#8 0x0000000080089b10 in trap_entry () at /hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot/arch/riscv/cpu/HiFive/start.S:343
Backtrace stopped: frame did not save the PCSembra che _exit_trap voglia fornire informazioni di debug sull'eccezione verificatasi, ma non ci riesce. Quindi, ci sono di nuovo problemi con la visualizzazione del codice sorgente. set directories ../freedom-u-sdk/HiFive_U-Boot/ Oh! Ora si visualizzano!
Bene, proviamo di nuovo e vediamo nel stack trace il motivo del problema originale che ha causato il primo errore (mcause == 5). Se ho capito bene ciò che è scritto a pag. 37, questa eccezione significa Accesso caricato errore. La causa sembra essere che qui
arch/riscv/cpu/HiFive/start.S:
call_board_init_f:
li t0, -16
li t1, CONFIG_SYS_INIT_SP_ADDR
and sp, t1, t0 /* forzare l'allineamento a 16 byte */
#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 /* salta a board_init_f() */
$sp ha quel valore non corretto, e all’interno board_init_f_init_reserve si verifica un errore. Sembra proprio essere colpa di: una variabile con un nome esplicito CONFIG_SYS_INIT_SP_ADDR. È definita nel file HiFive_U-Boot/include/configs/HiFive-U540.h. A un certo punto ho persino pensato, e se, lasciamo perdere, migliorare il caricatore per il processore — sarà più facile modificare un po' il processore? Ma poi ho visto che questo somiglia più a un artefatto di configurazioni di memoria non completamente terminate, e si può provare a fare così:#if 0-impostazione delle configurazioni per un'altra configurazione della memoria, e puoi provare a fare così:
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 /* SDRAM Bank #1 */
-#define PHYS_SDRAM_1
- (PHYS_SDRAM_0 + PHYS_SDRAM_0_SIZE) /* SDRAM Bank #2 */
-#define PHYS_SDRAM_0_SIZE 0x80000000 /* 2 GB */
-#define PHYS_SDRAM_1_SIZE 0x10000000 /* 256 MB */
+#define PHYS_SDRAM_0_SIZE 0x40000000 /* 1 GB */
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_0
#endif
/*
@@ -81,7 +78,7 @@
#define CONSOLE_ARG "console=ttyS0,115200 "
/* Init Stack Pointer */
-#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 /* partway up SDRAM */A un certo punto, il numero dei vincoli ha raggiunto un livello critico. Dopo aver riflettuto, sono giunto alla conclusione che era necessario realizzare un porting corretto sulla mia scheda. A tal fine, è necessario copiare e modificare un certo numero di file in base alla nostra configurazione.
Beh, più o meno, ecco un po'
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
Supporto iniziale per la scheda 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.hPuoi vedere i dettagli in .
Come si è rivelato, su questa scheda SiFive, i registri di alcuni dispositivi hanno indirizzi diversi. Inoltre, è emerso che U-Boot è configurabile tramite il meccanismo Kconfig già conosciuto dal kernel Linux — ad esempio, puoi dare il comando make menuconfig, e davanti a te comparirà un'interfaccia testuale comoda che mostra le descrizioni dei parametri. ? e così via. In generale, unendo le descrizioni di due schede, ho creato la descrizione di una terza, eliminando tutte quelle impostazioni pompose del PLL (che a quanto pare sono collegate al controllo dal computer host tramite PCIe, ma non è sicuro), ho ottenuto un firmware che, con le giuste condizioni su Marte, mi ha inviato via UART un messaggio riguardo a quale hash di commit era stato compilato e quanta RAM avevo (ma queste informazioni le avevo scritte io stesso nell'intestazione).
Peccato solo che dopo questo la scheda di solito smetteva di rispondere tramite JTAG del processore, e l'avvio dalla scheda SD — purtroppo, nella mia configurazione, non è rapido. D'altra parte, a volte il BootROM restituiva un messaggio che ERROR, non riuscito ad avviarsi, e subito appariva U-Boot. A questo punto mi è diventato chiaro: evidentemente, dopo il riavvio, il bitstream nella FPGA non viene sovrascritto, non riesce a "resettersi" e così via. Insomma, si può semplicemente collegarsi con il debugger quando appare il messaggio LOADING / e comandare set variable $pc=0x80089800, bypassando così questo lungo avvio (naturalmente, presupponendo che sia andato in crash abbastanza presto l'ultima volta, e non abbia caricato nulla sopra il codice originale).
A proposito, è normale che il processore si blocchi completamente e che il debug JTAG non riesca a collegarsi con i messaggi
Errore: impossibile fermare hart 0
Errore: dmcontrol=0x80000001
Errore: dmstatus =0x00030c82Aspettate un attimo! L'ho già visto! Qualcosa di simile accade quando si verifica un deadlock di TileLink e non mi fido molto dell'autore del controller di memoria — lo ha scritto lui stesso… Improvvisamente, dopo il primo ricompilamento riuscito del processore dopo aver modificato il controller, ho visto:
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING
BOOT
U-Boot 2018.09-g39cd67d-dirty (03 lug 2019 - 13:50:33 +0300)
DRAM: 1 GiB
MMC:
PRIMA DI CARICARE ENVPRIMA DI FDTCONTROLADDRPRIMA DI LOADADDRIn: serial
Out: serial
Err: serial
Premi un tasto per fermare l'autoboot: 3Su questa strana riga prima In: serial non preoccuparti — stavo cercando di capire su un processore bloccato se funzionasse correttamente con l'ambiente. Cosa significa "è già bloccato da dieci minuti"? È almeno riuscito a rilocalizzarsi e passare al menu di avvio! Una piccola digressione: anche se U-Boot viene caricato nei primi 2^24 byte dalla scheda SD, una volta avviato, si copia in un indirizzo più lontano, che sia quello registrato nell'intestazione di configurazione o in indirizzi più alti della memoria RAM, esegue la rilocalizzazione dei simboli ELF e trasferisce il controllo. Quindi: sembra che questo livello sia stato superato e come bonus abbiamo un processore che non si blocca completamente dopo questo.
Quindi, perché il timer non funziona? Sembra che l'orologio non funzioni per qualche motivo...
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000E se girassi manualmente le lancette?
(gdb) set variable *0x0200bff8=310000000
(gdb) cAllora:
Premi un tasto per fermare l'avvio automatico: 0
MMC_SPI: 0 a 0:1 hz 20000000 modalità 0Risultato: l'orologio non funziona. Probabilmente per questo motivo non funziona nemmeno l'input da tastiera:
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:
/* Primo carattere della sequenza di escape ANSI 'e' */
if (c == 'e') {
*esc = 1;
*key = KEY_NONE;
}
break;
case 1:
/* Secondo carattere di ANSI '[' */
if (c == '[') {
...Il problema era che avevo esagerato: ho aggiunto nella configurazione del processore la chiave:
case DTSTimebase => BigInt(0)… basandomi su quanto era scritto nel commento «se non sai — lasciati 0». E infatti WithNBigCores spostava proprio su 1MHz (come, tra l'altro, era indicato nella configurazione di U-Boot). Ma io, accidenti, sono preciso e meticoloso: qui non so, qui 25MHz! Alla fine niente funziona. Ho tolto i miei «miglioramenti» e…
Premi un tasto per fermare l'autoboot: 0
MMC_SPI: 0 a 0:1 hz 20000000 modalità 0
## Tipo di tabella delle partizioni sconosciuto 0
libfdt fdt_path_offset() ha restituito FDT_ERR_NOTFOUND
** Nessuna tabella di partizioni - mmc 0 **
## Info: dimensione dei dati in ingresso = 34 = 0x22
Esecuzione di uEnv.txt boot2...
## Errore: "boot2" non definito
HiFive-Unleashed #È possibile anche inserire comandi! Ad esempio, dopo un po' di tentativi si può finalmente intuire di inserire mmc_spi 1 10000000 0; mmc part, riducendo la frequenza SPI da 20MHz a 10MHz. Perché? Beh, nel file di configurazione era impostata la frequenza massima di 20MHz, e lo è anche ora. Ma, per quanto ho capito, le interfacce, almeno qui, funzionano in questo modo: il codice divide la frequenza del blocco hardware (per me — è sempre 25MHz) per quella target, e imposta il valore risultante come divisore nel registro di controllo corrispondente. Il problema è che se per il UART da 115200Hz di per sé risulta più o meno appropriato, dividendo direttamente 25000000 per 20000000 si ottiene 1, ossia funzionerà a 25MHz. Può darsi che sia accettabile, ma se ci sono delle limitazioni, significa che a qualcuno serve (anche se non è certo)... Insomma, è più facile settarlo e andare avanti — lontano e, ahimè, a lungo. 25MHz non è esattamente un Core i9.
Output della console
HiFive-Unleashed # env edit mmcsetup
edit: mmc_spi 1 10000000 0; mmc part
HiFive-Unleashed # boot
MMC_SPI: 1 at 0:1 hz 10000000 mode 0
Mappa delle partizioni per il dispositivo MMC 0 -- Tipo di partizione: EFI
Part Start LBA End LBA Nome
Attributi
Tipo GUID
GUID partizione
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() ha restituito FDT_ERR_NOTFOUND
2376 bytes letti in 0 ms
Esecuzione uEnv.txt boot2...
15332118 bytes letti in 0 ms
## Caricamento del kernel da FIT Image a 90000000 ...
Utilizzando la configurazione 'config-1'
Prova 'bbl' subimmagine del kernel
Descrizione: BBL/SBI/riscv-pk
Tipo: Immagine del kernel
Compressione: non compressa
Inizio dati: 0x900000d4
Dimensione dati: 74266 Bytes = 72.5 KiB
Architettura: RISC-V
OS: Linux
Indirizzo di caricamento: 0x80000000
Punto di ingresso: 0x80000000
Algoritmo Hash: sha256
Valore Hash: 28972571467c4ad0cf08a81d9cf92b9dffc5a7cb2e0cd12fdbb3216cf1f19cbd
Verifica dell'integrità dell'hash ... sha256+ OK
## Caricamento fdt da FIT Image a 90000000 ...
Utilizzando la configurazione 'config-1'
Prova 'fdt' subimmagine fdt
Descrizione: non disponibile
Tipo: Flat Device Tree
Compressione: non compressa
Inizio dati: 0x90e9d31c
Dimensione dati: 6911 Bytes = 6.7 KiB
Architettura: RISC-V
Indirizzo di caricamento: 0x81f00000
Algoritmo Hash: sha256
Valore Hash: 10b0244a5a9205357772ea1c4e135a4f882409262176d8c7191238cff65bb3a8
Verifica dell'integrità dell'hash ... sha256+ OK
Caricamento fdt da 0x90e9d31c a 0x81f00000
Avvio utilizzando il blob fdt a 0x81f00000
## Caricamento componenti caricabili da FIT Image a 90000000 ...
Prova 'kernel' subimmagine caricabile
Descrizione: kernel Linux
Tipo: Immagine del kernel
Compressione: non compressa
Inizio dati: 0x900123e8
Dimensione dati: 10781356 Bytes = 10.3 MiB
Architettura: RISC-V
OS: Linux
Indirizzo di caricamento: 0x80200000
Punto di ingresso: non disponibile
Algoritmo Hash: sha256
Valore Hash: 72a9847164f4efb2ac9bae736f86efe7e3772ab1f01ae275e427e2a5389c84f0
Verifica dell'integrità dell'hash ... sha256+ OK
Caricamento componenti caricabili da 0x900123e8 a 0x80200000
## Caricamento componenti caricabili da FIT Image a 90000000 ...
Prova 'ramdisk' subimmagine caricabile
Descrizione: buildroot initramfs
Tipo: Immagine RAMDisk
Compressione: compressa gzip
Inizio dati: 0x90a5a780
Dimensione dati: 4467411 Bytes = 4.3 MiB
Architettura: RISC-V
OS: Linux
Indirizzo di caricamento: 0x82000000
Punto di ingresso: non disponibile
Algoritmo Hash: sha256
Valore Hash: 883dfd33ca047e3ac10d5667ffdef7b8005cac58b95055c2c2beda44bec49bd0
Verifica dell'integrità dell'hash ... sha256+ OK
Caricamento componenti caricabili da 0x90a5a780 a 0x82000000Ok, abbiamo fatto un passo avanti, ma continua a bloccarsi. A volte l'eccezione appare. Possiamo vedere mcause catturando il codice all'indirizzo specificato. $pc e dopo si ci si trova su trap_entry. Il gestore stesso di U-Boot può visualizzare solo per mcause = 0..4, quindi preparatevi a entrare in un ciclo di caricamento errato. Qui sono andato nel file di configurazione, ho iniziato a controllare cosa avevo cambiato e mi sono ricordato: c'è scritto in conf/rvboot-fit.txt che dice:
fitfile=image.fit
# qui sotto deve corrispondere a ciò che c'è in FIT (ugha)Bene, allineiamo tutti i file, sostituiamo la riga di comando del kernel approssimativamente in questo modo, poiché ci sono sospetti che SIF0 — questo è l'output da qualche parte su PCIe:
-bootargs=console=ttySIF0,921600 debug
+bootargs=console=ttyS0,125200 debugE per completare cambiamo l'algoritmo di hashing da SHA-256 a MD5: non ho bisogno di robustezza crittografica (soprattutto durante il debug), si dice che ci voglia un sacco di tempo, e per rilevare gli errori di integrità durante il caricamento, MD5 è più che sufficiente. Quindi, qual è il risultato? Abbiamo iniziato a passare il livello precedente molto più velocemente (grazie a un hashing più semplice), e si è aperto il successivo:
...
Verifica l'integrità dell'hash ... md5+ OK
Caricamento dei loadables da 0x90a5a758 a 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() ha restituito 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";
};
Caricamento dell'immagine del kernel ... OK
Avvio del kernel tra
3Solo che l'orologio non ticchetta...
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000Ups, sembra che la correzione dell'orologio si sia rivelata un placebo, anche se allora mi è sembrata efficace. Certo, bisogna sistemarlo, ma per ora proviamo a girare le lancette manualmente e vediamo che succede:
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=1000000
(gdb) c
Continuando.
^C
Programma ricevuto segnale SIGINT, Interruzione.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=2000000
(gdb) c
Continuando.
^C
Programma ricevuto segnale SIGINT, Interruzione.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=3000000
(gdb) c
Continuando.Nel frattempo...
Caricamento dell'immagine del kernel ... OK
Avvio del kernel in
3
2
1
0
## Avvio dell'applicazione a 0x80000000 ...No, andrò a automatizzare il movimento dell'orologio — altrimenti, magari, penserà di calibrare lì il timer!
E l'indirizzo dell'istruzione corrente punta nel frattempo da qualche parte in
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 80001c52all'interno del Berkeley Boot Loader caricato. Personalmente, mi confonde il riferimento htif — interfaccia host, utilizzata per avviare il kernel in modalità tethered (ossia in cooperazione con l'ARM host), io pensavo fosse standalone. Tuttavia, se si trova questa funzione nel codice sorgente, si vede che non è tutto così male:
void poweroff(uint16_t code)
{
printm("Spegnerern");
finisher_exit(code);
if (htif) {
htif_poweroff();
} else {
send_ipi_many(0, IPI_HALT);
while (1) { asm volatile ("wfin"); }
}
}Quest: avvia l'orologio
La ricerca dei registri in CLINT ci porta a
val io = IO(new Bundle {
val rtcTick = Bool(INPUT)
})
val time = RegInit(UInt(0, width = timeWidth))
when (io.rtcTick) { time := time + UInt(1) }Che si collega al RTC, o al misterioso MockAON, di cui inizialmente ho pensato: «Quindi, cos'è questo? Incomprensibile? Disconnettiamoci!» Poiché non capisco ancora quale magia di clock avvenga lì, quindi riallaccio semplicemente questa logica a System.scala:
val rtcDivider = RegInit(0.asUInt(16.W)) // per ogni evenienza supporto fino a 16GHz, sono ottimista :)
val mhzInt = p(DevKitFPGAFrequencyKey).toInt
// Supponiamo che la frequenza sia un numero intero in megahertz
rtcDivider := Mux(rtcDivider === (mhzInt - 1).U, 0.U, rtcDivider + 1.U)
outer.clintOpt.foreach { clint =>
clint.module.io.rtcTick := rtcDivider === 0.U
}Procedendo verso il kernel Linux
Qui la narrazione è già prolungata e diventata un po' monotona, quindi descriverò in sintesi:
BBL prevedeva la presenza di FDT all'indirizzo 0xF0000000, ma l'avevo già corretto! Beh, cerchiamo ancora... Trovato in HiFive_U-Boot/arch/riscv/lib/boot.c, sostituito con 0x81F00000, specificato nella configurazione di avvio di U-Boot.
Poi BBL si è lamentato che non c'era memoria. Il mio percorso conduceva alla funzione mem_prop, che si trova in riscv-pk/machine/fdt.c: da lì ho scoperto che è necessario contrassegnare il nodo fdt ram come device_type = "memory" — poi, forse, sarà necessario apportare modifiche al generatore del processore, ma per ora scriverò semplicemente a mano — tanto ho trasferito questo file manualmente.
Ora ho ricevuto un messaggio (riportato in formato formattato, con ritorni a capo):
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.Sembra che le opzioni siano specificate correttamente riscv,kernel-start e riscv,kernel-end nel DTB, ma vengono analizzati come zeri. Il debug query_chosen ha mostrato che BBL sta cercando di analizzare un indirizzo a 32 bit, ma trova una coppia <0x0 0xADDR>, e il primo valore sembra i bit meno significativi. Ho aggiunto nella sezione chosen
chosen {
#address-cells = ;
#size-cells = ;
...
}e ho corretto la generazione dei valori: non aggiungere 0x0 come primo elemento.
Questi 100500 semplici passaggi ti permetteranno di vedere facilmente e semplicemente come crolla il pinguino:
Testo nascosto
Verifica dell'integrità dell'hash ... md5+ OK
Caricamento dei caricamenti da 0x90a5a758 a 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
chosen {
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() restituito FDT_ERR_NOTFOUND
chosen {
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";
};
Caricamento dell'immagine del kernel ... OK
Avvio del kernel in
3
2
1
0
## Avvio dell'applicazione a 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: Ignorando l'intervallo di memoria 0x80000000 - 0x80200000
[ 0.000000] Versione Linux 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc version 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99)) #1 SMP Mer Lug 3 21:29:21 MSK 2019
[ 0.000000] bootconsole [early0] abilitato
[ 0.000000] Ramdisk iniziale a: 0x(____ptrval____) (16777216 byte)
[ 0.000000] Intervalli di zona:
[ 0.000000] DMA32 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Normale [mem 0x00000000c0000000-0x00000bffffffffff]
[ 0.000000] Inizio della zona mobile per ciascun nodo
[ 0.000000] Intervalli di nodi di memoria iniziali
[ 0.000000] nodo 0: [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Impostazione di initmem nodo 0 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Su nodo 0 pagine totali: 261632
[ 0.000000] zona DMA32: 3577 pagine utilizzate per memmap
[ 0.000000] zona DMA32: 0 pagine riservate
[ 0.000000] zona DMA32: 261632 pagine, batch LIFO:63
[ 0.000000] software IO TLB: mappato [mem 0xbb1fc000-0xbf1fc000] (64MB)(l'emblema è BBL, e per quanto riguarda i timestamp, è il kernel).
Fortunatamente, non so se sia ovunque così, ma su RocketChip, collegando il debugger via JTAG, si possono catturare i trap fin da subito: il debugger si fermerà esattamente in quel punto.
Il programma ha ricevuto il segnale SIGTRAP, trap di traccia/punto di interruzione.
0xffffffe0000024ca in ?? ()
(gdb) bt
#0 0xffffffe0000024ca in ?? ()
Backtrace interrotta: il frame precedente è identico a questo frame (stack corrotto?)
(gdb) file work/linux/vmlinux
Un programma è già in fase di debug.
Sei sicuro di voler cambiare il file? (y o n) y
Lettura dei simboli da work/linux/vmlinux... fatto.
(gdb) bt
#0 0xffffffe0000024ca in setup_smp () at /hdd/trosinenko/fpga/freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c:75
#1 0x0000000000000000 in ?? ()
Backtrace interrotta: il frame non ha salvato il 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); // < SEI QUI
}Come si dice in un vecchio aneddoto, CPU non trovata, esecuzione in emulazione software.. O non in esecuzione. Ci siamo persi in un singolo core del processore.
/* The lucky hart to first increment this variable will boot the other cores */
atomic_t hart_lottery;
unsigned long boot_cpu_hartid;Un buon commento in linux/arch/riscv/kernel/setup.c — una certa verniciatura della recinzione secondo il metodo di Tom Sawyer. In effetti, oggi non ci sono stati vincitori, il premio è rinviato al prossimo turno…
Propongo di concludere qui un articolo che si è già protratto troppo.
Continua in arrivo. In esso ci sarà una lotta con un ingegnoso errore che riesce a nascondersi se ci ci avvicina lentamente con un singlestep.
Registrazione video del caricamento (link esterno):
Fonte: habr.com
