In è stato realizzato un controllore della memoria più o meno funzionante, o meglio, una wrapper sopra l'IP Core di Quartus, che funge da adattatore per TileLink. Oggi nella rubrica "Portiamo RocketChip su una scheda cinese poco conosciuta con Cyclone" vedrete una console funzionante. Il processo si è un po' allungato: pensavo di avviare rapidamente Linux e di andare avanti, ma non è andata così. In questa parte propongo di dare un'occhiata al processo di avvio di U-Boot, BBL, e ai timidi tentativi del kernel Linux di inizializzarsi. Ma c'è una console — quella di U-Boot, e abbastanza avanzata, con molte delle cose che ci si aspetta da una console completa.
Nella parte hardware sarà aggiunta una scheda SD, collegata tramite interfaccia SPI, oltre a UART. Nella parte software il BootROM sarà sostituito con xip in sdboot e, in effetti, saranno aggiunte le seguenti fasi di avvio (sulla scheda SD).
Affinamento della parte hardware
Quindi, compito: passare al nucleo "grande" e collegare UART (da Raspberry) e l'adattatore SD (è stata utilizzata una scheda da Catalex con sei pin: GND, VCC, MISO, MOSI, SCK, CS).
In linea di massima, tutto è stato piuttosto semplice. Ma prima di realizzarlo, mi sono un po' confuso: dopo l'ultima volta ho pensato che dovessi semplicemente aggiungere a Sistema qualcosa come HasPeripheryUART (e implementarlo di conseguenza), lo stesso per la scheda SD — e tutto sarebbe stato pronto. Poi ho deciso di vedere come fosse implementato in un design "serio". Allora, cosa abbiamo di serio qui? Arty, a quanto pare, non va bene — rimane il mostro unleahshed.DevKitConfigs. E improvvisamente ho scoperto che ci sono overlay ovunque, che vengono aggiunti tramite parametri con chiavi. Intuisco che probabilmente è molto flessibile e configurabile, ma vorrei poter avviare almeno qualcosa per cominciare... E per caso non avete qualcosa di simile, solo più semplice e malandato?.. Qui mi sono imbattuto in vera.iofpga.FPGAChip per il FPGA Microsemi e subito l'ho smontato in citazioni, cercando di fare la mia implementazione per analogia, dato che qui è più o meno tutto il "cablaggio della scheda madre" in un unico file.
Si è rivelato, infatti, che bisogna semplicemente aggiungere a System.scala le 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
...La riga nel corpo della classe Sistema aggiunge informazioni sulla frequenza a cui opera questa parte del nostro SoC nel file dts. Per quanto ho capito, DTS/DTB è una sorta di analogia statica 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 affinché possa configurare correttamente l'hardware. È interessante notare che senza la riga con tlclock si sintetizza tutto perfettamente, ma non sarà possibile compilare BootROM (ricordo che ora sarà già sdboot) - durante il processo di compilazione analizza il file dts e crea un header con la macro TL_CLK, grazie alla quale potrà configurare correttamente i divisori di frequenza per le interfacce esterne.
Sarà anche necessario apportare alcune modifiche alla "traccia":
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 alcuni altri punti del codice originale. È probabile che debbano proteggere da . Forse in alcuni blocchi ci sia già una loro protezione, ma per iniziare si desidera far girare tutto almeno "a un buon livello". Una domanda che mi interessa di più è perché MISO e MOSI siano su dq? Ответа я пока так и не нашёл, но, похоже, остальной код рассчитывает именно на такое подключение.
Fisicamente, ho semplicemente assegnato i pin del design ai contatti liberi sul connettore e ho spostato il jumper per la selezione della tensione su 3.3V.
Adattatore SD
Vista dall'alto:

Vista dal basso:

Debugging della parte software: strumenti
Iniziamo a parlare degli strumenti di debug disponibili e delle loro limitazioni.
Minicom
Innanzitutto, avremo bisogno di un modo per leggere ciò che il bootloader e il kernel stanno stampando. Per questo, su Linux (in questo caso, su quello che è sul RaspberryPi) avremo bisogno del programma Minicom. In generale, qualsiasi programma per gestire la porta seriale andrà bene.
Si noti che all'avvio il nome del dispositivo della porta deve essere specificato come -D /dev/ttyS0 — dopo l'opzione -D. E l'informazione principale: per uscire usate Ctrl-A, X. In realtà, ho avuto un caso in cui questa combinazione non ha funzionato — allora si può semplicemente dire da una sessione SSH adiacente killall -KILL minicom.
C'è anche un'altra caratteristica. In particolare sul RaspberryPi ci sono due UART, e entrambe le porte possono già essere configurate per qualcosa: una per il Bluetooth, attraverso l'altra la console del kernel viene visualizzata per impostazione predefinita. Fortunatamente, questo comportamento può essere riconfigurato .
Scrittura in memoria
Durante il debug, per verificare l'ipotesi, a volte dovevo caricare il bootloader (scusa) direttamente nella RAM dall'host. Forse si può fare direttamente da GDB, ma alla fine ho scelto la soluzione più semplice: ho copiato il file necessario sul Raspberry, 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 dorme, sta solo lampeggiando lentamente»: sta caricando il file, ma lo fa a una velocità di un paio di kilobyte al secondo.
Caratteristiche dell'impostazione dei breakpoint
Probabilmente molti non ci hanno mai pensato durante il debug di programmi normali, ma i breakpoint non sempre vengono impostati a livello hardware. A volte impostare un breakpoint consiste nel sovrascrivere temporaneamente un'istruzione speciale nel punto giusto direttamente nel codice macchina. Ad esempio, così ha funzionato il mio comando standard b in GDB. Ecco cosa ne deriva:
- non si può impostare un breakpoint all'interno del BootROM, perché il ROM
- può impostare un breakpoint sul codice caricato nella RAM dalla scheda SD, ma bisogna aspettare che venga caricato. Altrimenti non siamo noi a sovrascrivere un pezzo di codice, ma il bootloader sovrascriverà il nostro breakpoint
Sono sicuro che si può richiedere esplicitamente di usare breakpoint hardware, ma ce ne sono comunque in numero limitato.
Sostituzione rapida del BootROM
Nella fase iniziale del debug spesso sorge il desiderio di correggere 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 soltanto dopo la quasi istantanea compilazione dell'immagine del BootROM da C e Assembler…). Fortunatamente, in realtà tutto molto più velocemente: la sequenza di azioni è la seguente:
- rigenerare bootrom.mif (sono passato a MIF invece di HEX, perché con HEX ho sempre avuto problemi, mentre MIF è il formato nativo di Altera)
- in Quartus dire
Processing -> Update Memory Initialization File - nel punto Assembler (nella colonna di sinistra Tasks) dare il comando Start again
Il tutto impiega solo una manciata di secondi.
Preparazione della scheda SD
Qui tutto è relativamente semplice, ma è necessario avere pazienza e circa 14 GB di spazio su disco:
git clone https://github.com/sifive/freedom-u-sdk
git submodule update --recursive --init
makeDopo di che è necessario inserire una scheda SD pulita, ovvero che non contenga nulla di utile, e eseguire
sudo make DISK=/dev/sdX format-boot-loader… dove sdX è il dispositivo assegnato alla scheda. ATTENZIONE: i dati sulla scheda verranno eliminati, sovrascritti e così via! Difficilmente ha senso effettuare l'intera compilazione da sudo, perché altrimenti tutti gli artefatti della compilazione apparterranno a root, e la compilazione dovrà essere effettuata da sudo continuamente.
Alla fine si ottiene una scheda partizionata in GPT con quattro partizioni, in una delle quali c'è FAT con uEnv.txt e un'immagine di avvio in formato FIT (contiene diverse sottoimmagini, ciascuna con il proprio indirizzo di avvio), l'altra partizione è pulita, e si prevede di formattarla in Ext4 per Linux. Le altre due partizioni sono misteriose: in una vive U-Boot (il suo offset, per quanto ne so, è codificato in BootROM), nell'altra sembrano vivere le sue variabili ambientali, ma per ora non le utilizzo.
Livello primo, BootROM
La saggezza popolare dice: «Se nella programmazione ci sono danze con tamburi, nell'elettronica ci sono anche danze con gli estintori». Non è nemmeno che una volta stavo per bruciare una scheda, pensando che «Beh, GND è lo stesso di un livello basso» (probabilmente, una resistenza non avrebbe guastato…) Si tratta piuttosto del fatto che se le mani non arrivano da dove dovrebbero, l'elettronica continua a riservare sorprese: saldando un connettore sulla scheda, non sono riuscito a saldare correttamente i contatti — nei video mostrano come la saldatura si distribuisce da sola su tutta la connessione, basta toccare con il saldatore, a me invece si «accumulava» in modo casuale. Beh, forse la saldatura non era adatta alla temperatura del saldatore, forse qualcos'altro… Insomma, vedendo che avevo già una dozzina di contatti, ho rinunciato e ho iniziato a fare debug. E qui è iniziato il misterioso: ho collegato RX/TX dall'UART, carico il firmware — e scrive
INIT
CMD0
ERROREcco, tutto logico — non ho collegato il modulo della scheda SD. Correggo la situazione, carico il firmware… E silenzio… Ho pensato di tutto, ma la cosa era semplice: uno dei pin del modulo doveva essere collegato a VCC. Nel mio caso, il modulo supportava 5V per l'alimentazione, quindi, non ci ho pensato sopra, ho collegato il cavo che proveniva dal modulo sull'altro lato della scheda. Alla fine, il connettore mal saldato si è storto, e ho semplicemente perso il contatto UART. facepalm.jpg In generale, «una testa malandata non dà pace alle gambe», e le mani goffe alla testa...
Alla fine ho visto in Minicom il tanto atteso
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING /Inoltre, l'indicatore di caricamento si muove e gira. Ricorda i tempi della scuola e il caricamento lento di MinuetOS da floppy disk. A parte il fatto che il lettore non scricchiola.
Il problema è che dopo il messaggio BOOT non succede nulla. È quindi il momento di collegarsi tramite OpenOCD su Raspberry, con 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) va a finire in 0x0 — probabilmente questo accade dopo un errore multiplo. Quindi, subito dopo aver emesso il messaggio BOOT aggiungeremo un ciclo infinito. Questo lo ritarderà per un po'...
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;
}Un codice così ingegnoso è usato "per affidabilità": ho sentito da qualche parte che, in teoria, il ciclo infinito può causare comportamento indefinito, e qui il compilatore difficilmente se ne accorgerà (Ricordo che 0x10000 si trova nel BootROM).

A prima vista, che ci si può aspettare di più — un embedded severo, che tipo di sorgente? Ma infatti nell l'autore ha debuggato 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 or n) y
Lettura dei simboli da builds/zeowaa-e115/sdboot.elf... fatto.
Devi caricare non un file MIF e nemmeno un bin, ma la versione originale in formato ELF.
Ora si può tentare di indovinare l'indirizzo dove la esecuzione continuerà (questo è un altro motivo per cui il compilatore non doveva rendersi conto che il ciclo fosse infinito). Il comando
set variable $pc=0xADDRpermette di cambiare il valore del registro al volo (in questo caso — l'indirizzo dell'istruzione corrente). Con esso, puoi anche modificare i valori memorizzati in memoria (e i registri mappati in memoria).
Alla fine sono giunto alla conclusione (non sono sicuro che sia corretta) che abbiamo "un'immagine della scheda SD di un altro sistema", e bisogna andare 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 .rodataProbabilmente, questo è dovuto anche al fatto che, non avendo a disposizione la scheda da 4Gb, ho preso quella da 2Gb e, a tentoni, ho sostituito nel Makefile. DEMO_END=11718750 in DEMO_END=3078900 (non cercate un significato specifico — non ce n'è, semplicemente ora l'immagine si adatta alla scheda).
Livello due, U-Boot
Ora stiamo ancora 'cadendo', ma ci troviamo già all'indirizzo 0x0000000080089a84. Qui devo ammettere: in realtà, la narrazione non procede 'con tutte le fermate', ma viene scritta parzialmente 'dopo', quindi qui ho già avuto tempo di inserire il file dtb corretto per il nostro SoC, e di modificare nelle impostazioni. HiFive_U-Boot variabile CONFIG_SYS_TEXT_BASE=0x80089800 (invece di 0x08000000), affinché l'indirizzo di caricamento coincida con quello reale. Ora carichiamo la scheda del 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) │Inoltre, saltiamo tra le righe 308 e 309. E non sorprende, considerando che in $sp c'è il valore 0xfffffffe31cdc0a0. Purtroppo, scappa continuamente a causa della riga 307. Pertanto, proveremo a impostare un punto di arresto su trap_entry, e poi tornare di nuovo su 0x80089800 (il punto di ingresso di U-Boot), e speriamo che non richieda una corretta impostazione dei registri prima della transizione... 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 dello stack, a dirla tutta: punta completamente al di fuori della RAM (a meno che, naturalmente, non abbiamo già la traduzione degli indirizzi, ma speriamo in una soluzione semplice).
Proveremo a sostituire il puntatore con 0x881cf950. Alla fine arriviamo al punto che handle_trap viene chiamato continuamente, mentre ci allontaniamo verso _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) in lib/string.c:283
#1 0x00000000800cc14c in string (buf=buf@entry=0x881cbd4c "", end=end@entry=0x881cc15c "", s=0x10060000 "", field_width=, precision=, flags=) in 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) in 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) in 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) in lib/vsprintf.c:717
#5 0x00000000800ccb50 in printf (fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc x , ra lxn") in lib/vsprintf.c:792
#6 0x000000008008a9f0 in _exit_trap (regs=, epc=2148315240, code=) in arch/riscv/lib/interrupts.c:92
#7 handle_trap (mcause=, epc=, regs=) in arch/riscv/lib/interrupts.c:55
#8 0x0000000080089b10 in trap_entry () in /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, i sorgenti non vengono nuovamente visualizzati. set directories ../freedom-u-sdk/HiFive_U-Boot/ Oh! Ora sono visibili!
Bene, facciamolo partire di nuovo e vediamo tramite il backtrace il motivo del problema originale che ha causato il primo errore (mcause == 5). Se ho capito bene ciò che è scritto a pagina 37, allora questa eccezione significa Load access fault. La causa, apparentemente, è 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 /* forza 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 errato, e all'interno board_init_f_init_reserve si verifica un errore. Sembra proprio essere il colpevole: la variabile con un nome inequivocabile 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, lascia stare, modificare il caricatore per il processore — magari è più facile correggere un po' il processore? Ma poi ho visto che somiglia più a un artefatto di impostazioni non completamente impostate per un'altra configurazione della memoria, e si può provare a fare così:#if 0diff --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 di soluzioni temporaneedi rinforzo tecnologico Circa, 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 +0300Supporto iniziale per la scheda Zeowaa A-E115FBM 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.h
I dettagli possono essere visualizzati inCome si è scoperto, su questa scheda SiFive alcuni registri di dispositivi hanno indirizzi diversi. Inoltre, è emerso che U-Boot viene configurato tramite il già conosciuto meccanismo Kconfig del kernel Linux — ad esempio, è possibile ordinare .
make menuconfig , e davanti a te apparirà un'interfaccia testuale comoda con la visualizzazione delle descrizioni dei parametri., e vi apparirà un'interfaccia testuale comoda con la visualizzazione delle descrizioni dei parametri per ? In sintesi, combinando le descrizioni di due schede per creare una terza, escludendo varie impostazioni di PLL piuttosto pompose (probabilmente collegate alla gestione dal computer host tramite PCIe, ma non è certo), ho ottenuto un certo firmware che, con il giusto tempo su Marte, mi inviava tramite UART un messaggio riguardo l'hash del commit da cui era stato assemblato, e riguardo a quanto DRAM avessi (ma questa informazione l'avevo già inserita io nell'intestazione).
Peccato solo che dopo ciò la scheda di solito smetteva di rispondere tramite JTAG del processore, mentre il caricamento dalla scheda SD, purtroppo, nella mia configurazione non è veloce. D'altra parte, a volte il BootROM mostrava un messaggio che ERROR, non sono riuscito a caricare, e subito appariva U-Boot. A quel punto mi è apparso chiaro: evidentemente, dopo il riavvio, il bitstream nella FPGA non viene sovrascritto e non ha il tempo di "disattivarsi", ecc. Insomma, è possibile semplicemente, all'apparizione del messaggio LOADING / collegarsi con un debugger e impartire il comando set variable $pc=0x80089800, bypassando così questo lungo caricamento (ovviamente, supponendo che la volta precedente si sia bloccato piuttosto presto e non abbia avuto il tempo di caricare qualcosa sopra il codice originale).
A proposito, è normale che il processore si blocchi completamente e che non possa collegarsi un debugger JTAG con i messaggi
Error: unable to halt hart 0
Error: dmcontrol=0x80000001
Error: dmstatus =0x00030c82Aspetta! Questo lo ho già visto! Qualcosa di simile accade in caso di deadlock nel TileLink, e non mi fido molto dell'autore del controller di memoria — l'ho scritto io stesso… Improvvisamente, dopo il primo successo nella ricompilazione del processore dopo la modifica del controller, ho visto:
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING
BOOT
U-Boot 2018.09-g39cd67d-dirty (Jul 03 2019 - 13:50:33 +0300)
DRAM: 1 GiB
MMC:
BEFORE LOAD ENVBEFORE FDTCONTROLADDRBEFORE LOADADDRIn: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 3Su questa strana riga prima In: serial non prestare attenzione — stavo cercando di capire su un processore bloccato se funzionasse correttamente con l'ambiente. Cosa significa, «È bloccato da dieci minuti»? È riuscito almeno a rilocarsi e ad accedere al menu di avvio! Una piccola digressione: anche se U-Boot si carica nei primi 2^24 byte dalla scheda SD, una volta avviato, si copia altrove all'indirizzo, che sia scritto nell'intestazione di configurazione o semplicemente negli indirizzi superiori della memoria RAM, effettua la rilocazione dei simboli ELF e passa il controllo. Ecco: sembra che questo livello sia stato superato e come bonus abbiamo un processore che non si blocca completamente dopo questo.
Allora, perché il timer non funziona? Sembra che l'orologio non vada proprio...
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000E se girassi le lancette manualmente?
(gdb) set variable *0x0200bff8=310000000
(gdb) cAllora:
Premi un tasto per interrompere l'autoboot: 0
MMC_SPI: 0 a 0:1 hz 20000000 modalità 0Conclusione: l'orologio non va. Probabilmente, per lo stesso 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 si è rivelato essere che ho esagerato un po': ho aggiunto nella configurazione del processore la chiave:
case DTSTimebase => BigInt(0)… basandomi sul fatto che nel commento era detto «se non sai, lascia 0». E infatti WithNBigCores proprio lo impostava a 1MHz (come, tra l'altro, indicato nella configurazione di U-Boot). Ma io, accidenti, sono preciso e scrupoloso: lì non so, qui 25MHz! In fin dei conti, niente funziona. Ho tolto i miei "miglioramenti" e...
Premi un tasto per interrompere l'autoboot: 0
MMC_SPI: 0 a 0:1 hz 20000000 modalità 0
## Tipo di tabella partizione sconosciuto 0
libfdt fdt_path_offset() restituito FDT_ERR_NOTFOUND
** Nessuna tabella partizione - mmc 0 **
## Info: dimensione dati in input = 34 = 0x22
Esecuzione di uEnv.txt boot2...
## Errore: "boot2" non definito
HiFive-Unleashed #Si possono persino 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é? Bene, nel configurazione era scritta la frequenza massima di 20MHz, e così è ancora adesso. Ma, per quanto ho capito, le interfacce, almeno qui, funzionano in questo modo: il codice divide la frequenza dell'hardware (ho 25MHz ovunque) per la frequenza target, e imposta il valore risultante come divisore nel registro di controllo corrispondente. E il problema è che se per un UART a 115200Hz va bene, ma se si divide esattamente 25000000 per 20000000 si ottiene 1, cioè funzionerà a 25MHz. Forse va bene così, ma se vengono imposti dei limiti, significa che a qualcuno serve (anche se non è sicuro)... Insomma, è più facile impostare e andare avanti — lontano e, ahimè, a lungo. 25MHz non è proprio 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 a 0:1 hz 10000000 modalità 0
Mappa delle partizioni per il dispositivo MMC 0 -- Tipo di partizione: EFI
Part Inizio LBA Fine LBA Nome
Attributi
Tipo 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() restituito FDT_ERR_NOTFOUND
2376 bytes letti in 0 ms
Esecuzione di uEnv.txt boot2...
15332118 bytes letti in 0 ms
## Caricamento del kernel dall'immagine FIT a 90000000 ...
Utilizzando la configurazione 'config-1'
Prova il sottogruppo del kernel 'bbl'
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 d'ingresso: 0x80000000
Algoritmo hash: sha256
Valore hash: 28972571467c4ad0cf08a81d9cf92b9dffc5a7cb2e0cd12fdbb3216cf1f19cbd
Verifica dell'integrità dell'hash ... sha256+ OK
## Caricamento fdt dall'immagine FIT a 90000000 ...
Utilizzando la configurazione 'config-1'
Prova il sottogruppo fdt '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 delle risorse caricabili dall'immagine FIT a 90000000 ...
Prova il sottogruppo 'kernel' delle risorse caricabili
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 d'ingresso: non disponibile
Algoritmo hash: sha256
Valore hash: 72a9847164f4efb2ac9bae736f86efe7e3772ab1f01ae275e427e2a5389c84f0
Verifica dell'integrità dell'hash ... sha256+ OK
Caricamento delle risorse caricabili da 0x900123e8 a 0x80200000
## Caricamento delle risorse caricabili dall'immagine FIT a 90000000 ...
Prova il sottogruppo 'ramdisk' delle risorse caricabili
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 d'ingresso: non disponibile
Algoritmo hash: sha256
Valore hash: 883dfd33ca047e3ac10d5667ffdef7b8005cac58b95055c2c2beda44bec49bd0
Verifica dell'integrità dell'hash ... sha256+ OK
Caricamento delle risorse caricabili da 0x90a5a780 a 0x82000000Va bene, siamo passati a un nuovo livello, ma si blocca ancora. A volte lancia anche eccezioni. Puoi vedere mcause, aspettando il codice all'indirizzo specificato $pc e dopo si trovare su trap_entry. L'elaboratore stesso di U-Boot può stampare solo per mcause = 0..4, quindi preparatevi a dover affrontare un caricamento non corretto. Qui sono andato a controllare la configurazione, ho cominciato a vedere cosa avevo cambiato, e ho ricordato: c'era scritto in conf/rvboot-fit.txt :
fitfile=image.fit
# sotto deve corrispondere a quanto in FIT (ugha)Bene, allineiamo tutti i file, sostituendo la riga di comando del kernel più o meno così, poiché ci sono sospetti che SIF0 — è un'uscita da qualche parte tramite PCIe:
-bootargs=console=ttySIF0,921600 debug
+bootargs=console=ttyS0,125200 debugE per comodità cambieremo l'algoritmo di hashing da SHA-256 a MD5: non mi serve una grande resistenza crittografica (soprattutto durante il debug), si dice che impieghi un tempo folle, e per rilevare errori di integrità al caricamento, MD5 è più che sufficiente. E quindi, che cosa risulta? Siamo riusciti a superare il livello precedente notevolmente più velocemente (grazie a un hashing più semplice), e si è aperto il livello successivo:
...
Verificando l'integrità dell'hash ... md5+ OK
Caricamento dei file caricabili 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() 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 in
3Solo che l'orologio non ticchetta...
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000Ops, sembra che la correzione dell'orologio sia stata un placebo, anche se allora mi sembrava che avesse funzionato. Certo, bisogna ripararlo, ma iniziamo a ruotare le lancette manualmente e vediamo cosa succede:
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=1000000
(gdb) c
Continuando.
^C
Il programma ha ricevuto il segnale SIGINT, Interruzione.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=2000000
(gdb) c
Continuando.
^C
Il programma ha ricevuto il 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ò ad automatizzare l'orologio — se no, potrebbe decidere di calibrare il timer!
E l'indirizzo dell'istruzione corrente nel frattempo punta 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 80001c52dentro del Berkeley Boot Loader caricato. Personalmente, sono sorpreso dalla menzione htif — interfaccia host utilizzata per l'avvio collegato del kernel (cioè in cooperazione con l'host ARM), pensavo fosse standalone. Tuttavia, se si trova questa funzione nel 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"); }
}
}Ricerca: avviare 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 all'RTC, o al misterioso MockAON, di cui inizialmente ho pensato: «Quindi, cos'è qui? Non chiaro? Spegniamo!» Poiché non capisco ancora bene quale sia la magia del clock, quindi semplicemente rilancerò questa logica in System.scala:
val rtcDivider = RegInit(0.asUInt(16.W)) // per precauzione supporterò fino a 16GHz, sono ottimista :)
val mhzInt = p(DevKitFPGAFrequencyKey).toInt
// Supponiamo che la frequenza sia un numero intero di 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 il racconto si è già protratto ed è diventato un po' monotono, quindi lo descriverò a grandi linee:
BBL prevedeva la presenza di un FDT all'indirizzo 0xF0000000, e io l'avevo già corretto! Bene, cerchiamo ancora... Trovato in HiFive_U-Boot/arch/riscv/lib/boot.c, sostituito con 0x81F00000, indicato nella configurazione di avvio di U-Boot.
Poi BBL si lamentava che non c'era memoria. La mia strada conduceva alla funzione mem_prop, che si trova in riscv-pk/machine/fdt.c: da lì ho capito che era necessario etichettare il nodo fdt ram come device_type = "memory" — poi, forse sarà necessario correggere il generatore di processore, ma per ora lo scriverò semplicemente a mano — comunque ho trasferito questo file manualmente.
Ora ho ricevuto un messaggio (riportato in formato formattato, con ritorni a capo):
Questo è il dummy_payload di bbl. Per avviare un vero kernel, riconfigura bbl
con il flag --with-payload=PATH, poi ricostruisci bbl. In alternativa,
bbl può essere usato in modalità solo firmware aggiungendo nodi device-tree
per un payload esterno e usando le opzioni -bios e -kernel di QEMU.Sembra che le opzioni siano specificate correttamente riscv,kernel-start e riscv,kernel-end nel DTB, ma i valori vengono analizzati come zero. Debugging query_chosen ha mostrato che BBL sta cercando di analizzare un indirizzo di 32 bit, mentre ottiene una coppia <0x0 0xADDR>, e il primo valore sembra avere i bit meno significativi. Ho aggiunto alla sezione chosen
chosen {
#address-cells = ;
#size-cells = ;
...
}e ho corretto la generazione dei valori: non aggiungere 0x0 come primo elemento.
Questi 100500 passaggi semplici ti permetteranno di vedere facilmente e rapidamente come cade il pinguino:
Testo nascosto
Verifica dell'integrità degli 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 = ;
#address-cells = ;
#size-cells = ;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
libfdt fdt_path_offset() ha 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 di Linux 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc versione 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 ogni nodo
[ 0.000000] Intervalli di nodo di memoria iniziale
[ 0.000000] nodo 0: [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Impostazione nodi di memoria iniziale 0 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Su nodo 0 totalpages: 261632
[ 0.000000] Zona DMA32: 3577 pagine usate per memmap
[ 0.000000] Zona DMA32: 0 pagine riservate
[ 0.000000] Zona DMA32: 261632 pagine, LIFO batch:63
[ 0.000000] IO TLB software: mappato [mem 0xbb1fc000-0xbf1fc000] (64MB)(l'emblema è visualizzato da BBL, mentre ciò che riguarda i timestamp è il kernel).
Fortunatamente, non so se ovunque, ma su RocketChip, collegando il debugger tramite JTAG, puoi intercettare i trap direttamente — il debugger si fermerà esattamente in quel punto.
Il programma ha ricevuto il segnale SIGTRAP, Trace/breakpoint trap.
0xffffffe0000024ca in ?? ()
(gdb) bt
#0 0xffffffe0000024ca in ?? ()
Backtrace fermato: 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 () a /hdd/trosinenko/fpga/freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c:75
#1 0x0000000000000000 in ?? ()
Backtrace fermato: il frame non ha salvato il PCfreedom-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 diceva in un vecchio aneddoto, CPU non trovata, esecuzione dell'emulazione software. O forse non in esecuzione. Perso 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 sorta di pittura di una recinzione secondo il metodo di Tom Sawyer. In generale, oggi non ci sono vincitori, il premio viene rimandato al prossimo turno...
Propongo di concludere questo articolo già troppo lungo.
Continua. Ci sarà una lotta con un errore astuto che riesce a nascondersi, se ci si avvicina lentamente con il singlestep.
Screencast di caricamento (link esterno):
Fonte: habr.com
