В
L'hardware includerà una scheda SD collegata tramite l'interfaccia SPI e una UART. Nella parte software, BootROM verrà sostituita con xip
su sdboot
e, infatti, sono state aggiunte le seguenti fasi di caricamento (sulla scheda SD).
Finitura dell'hardware
Quindi, il compito: devi passare a un core "grande" e collegare un UART (da Raspberry) e un adattatore SD (abbiamo usato una scheda Catalex con sei pin: GND, VCC, MISO, MOSI, SCK, CS) .
In linea di principio, tutto era abbastanza semplice. Ma prima di rendermene conto, sono stato sbattuto un po' da una parte all'altra: dopo la volta precedente, ho deciso che ancora una volta dovevo solo mescolare System
qualcosa di simile a HasPeripheryUART
(e implementazione di conseguenza), lo stesso per la scheda SD - e tutto sarà pronto. Poi ho deciso di vedere come veniva implementato in un progetto “serio”. Allora, cosa c'è di serio in questo? Apparentemente Arty non si adatta: il mostro rimane unleahshed.DevKitConfigs
. E all'improvviso si è scoperto che c'erano delle sovrapposizioni ovunque, che venivano aggiunte tramite parametri tramite tasti. Immagino che questo sia probabilmente molto flessibile e configurabile, ma vorrei almeno eseguire qualcosa prima... Non hai la stessa cosa, solo più semplice e fastidiosa? È stato allora che mi sono imbattuto vera.iofpga.FPGAChip
per FPGA Microsemi e l'ho immediatamente smontato per preventivi e ho provato a realizzare la mia implementazione per analogia, fortunatamente c'è più o meno l'intero "layout della scheda madre" in un unico file.
Si è scoperto che devi solo aggiungere System.scala
linea
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 con cui opera questa parte del nostro SoC nel file dts. Per quanto ho capito, DTS/DTB è un analogo statico della tecnologia plug-and-play per dispositivi embedded: l'albero di descrizione dts viene compilato in un file dtb binario e trasferito dal bootloader al kernel in modo che possa configurare correttamente il hardware. È interessante notare che, senza la linea con tlclock
tutto si sintetizza perfettamente, ma compilando la BootROM (permettetemi di ricordarvi, ora questo sarà già sdboot
) non funzionerà: durante il processo di compilazione analizza il file dts e crea un'intestazione con una macro TL_CLK
, grazie al quale sarà in grado di configurare correttamente i divisori di frequenza per le interfacce esterne.
Dovrai anche correggere leggermente il “cablaggio”:
Piattaforma.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. Molto probabilmente, dovrebbero proteggersi dq
? Non ho ancora trovato la risposta, ma sembra che il resto del codice si basi proprio su tale connessione.
Fisicamente ho semplicemente assegnato i pin di progettazione ai contatti liberi sul blocco e ho spostato il ponticello di selezione della tensione su 3.3 V.
Adattatore SD
Vista dall'alto:
Vista dal basso:
Debug del software: strumenti
Innanzitutto, parliamo degli strumenti di debug disponibili e dei loro limiti.
minicom
Per prima cosa dovremo leggere in qualche modo l'output del bootloader e del kernel. Per fare ciò su Linux (in questo caso su RaspberryPi), abbiamo bisogno del programma Minicom. In generale, va bene qualsiasi programma che funzioni con una porta seriale.
Tieni presente che all'avvio il nome del dispositivo della porta deve essere specificato come -D /dev/ttyS0
- dopo l'opzione -D
. Bene, l'informazione principale: uscire, usare Ctrl-A, X
. In realtà ho avuto un caso in cui questa combinazione non ha funzionato, quindi puoi semplicemente dirlo da una sessione SSH vicina killall -KILL minicom
.
C'è un'altra caratteristica. Nello specifico il RaspberryPi ha due UART ed entrambe le porte possono già essere adattate per qualcosa: una per Bluetooth, l'altra di default emette la console del kernel. Fortunatamente, questo comportamento può essere ignorato
Riscrittura della memoria
Durante il debug, per verificare un'ipotesi, a volte dovevo farlo caricare il bootloader (scusate) nella RAM direttamente dall'host. Forse questo si può fare direttamente da GDB, ma alla fine ho seguito un percorso semplice: ho copiato il file necessario su Raspberry, ho inoltrato anche la porta 4444 via SSH (telnet da OpenOCD) e ho usato il comando load_image
. Quando lo fai, sembra che tutto sia congelato, ma in realtà “non dorme, lampeggia solo lentamente”: Scarica il file, lo fa semplicemente alla velocità di un paio di kilobyte al secondo.
Funzionalità di installazione dei punti di interruzione
Molte persone probabilmente non hanno dovuto pensare a questo durante il debug di programmi regolari, ma i punti di interruzione non sono sempre impostati nell'hardware. A volte l'impostazione di un punto di interruzione implica la scrittura temporanea di istruzioni speciali nel posto giusto direttamente nel codice macchina. Ad esempio, ecco come funzionava il mio comando standard b
nel GDB. Ecco quanto segue:
- non puoi inserire un punto all'interno di BootROM perché ROM
- È possibile impostare un punto di interruzione sul codice caricato nella RAM dalla scheda SD, ma è necessario attendere fino al caricamento. Altrimenti non riscriveremo un pezzo di codice, ma il caricatore riscriverà il nostro punto di interruzione
Sono sicuro che puoi chiedere esplicitamente di utilizzare i punti di interruzione hardware, ma ce n'è comunque un numero limitato.
Sostituzione rapida della BootROM
Nella fase iniziale del debug, spesso si desidera riparare la BootROM e riprovare. Ma c'è un problema: la BootROM è parte del progetto caricato nell'FPGA e la sua sintesi è questione di pochi minuti (e questo avviene dopo la compilazione quasi istantanea dell'immagine BootROM stessa da C e Assembler...). Fortunatamente, in realtà tutto più veloce: la sequenza delle azioni è la seguente:
- rigenerare bootrom.mif (sono passato a MIF anziché HEX, perché ho sempre avuto dei problemi con HEX e MIF è il formato nativo di Alter)
- in Quarto dicono
Processing -> Update Memory Initialization File
- sulla voce Assembler (nella colonna sinistra di Attività) il comando Avvia di nuovo
Tutto su tutto: un paio di decine di secondi.
Preparazione della scheda SD
Qui tutto è relativamente semplice, ma devi essere paziente e avere circa 14 GB di spazio su disco:
git clone https://github.com/sifive/freedom-u-sdk
git submodule update --recursive --init
make
Dopodiché è necessario inserire una scheda SD pulita, o meglio, che non contenga nulla di necessario, ed eseguire
sudo make DISK=/dev/sdX format-boot-loader
… Dove sdX
— dispositivo assegnato alla carta. ATTENZIONE: i dati presenti sulla card verranno cancellati, sovrascritti e in generale! Non vale quasi la pena eseguire l'intero assemblaggio da sotto sudo
perché in tal caso tutti gli artefatti di costruzione apparterranno a root
, e il montaggio dovrà essere fatto da sotto sudo
costantemente.
Il risultato è una carta contrassegnata in GPT con quattro partizioni, una delle quali ha FAT uEnv.txt
e un'immagine avviabile in formato FIT (contiene diverse immagini secondarie, ciascuna con il proprio indirizzo di download), l'altra partizione è vuota, dovrebbe essere formattata in Ext4 per Linux. Altre due sezioni - misterioso: U-Boot vive su uno (il suo offset, per quanto ho capito, è codificato in BootROM), dall'altro, a quanto pare, le sue variabili di ambiente vivono, ma non le uso ancora.
Livello uno, BootROM
La saggezza popolare dice: "Se nella programmazione si balla con un tamburello, nell'elettronica si balla anche con un estintore". Non si tratta nemmeno del fatto che una volta ho quasi bruciato il tabellone, decidendo che "Bene, GND è allo stesso livello basso". (a quanto pare, una resistenza non farebbe male, dopotutto...) Si tratta più del fatto che se le mani non crescono da lì, l'elettronica non smette mai di sorprendere: durante la saldatura del connettore sulla scheda, non sono ancora riuscito a saldare correttamente i contatti - il video mostra come la saldatura si diffonde direttamente su tutta la connessione basta applicare un saldatore, per me ha “schiaffeggiato” a caso. Beh, forse la saldatura non era adatta alla temperatura del saldatore, forse qualcos'altro... In generale, quando ho visto che avevo già una dozzina di contatti, ho rinunciato e ho iniziato a eseguire il debug. E poi è iniziato enigmatico: Ho collegato RX/TX da UART, carico il firmware - dice
INIT
CMD0
ERROR
Bene, tutto è logico: non ho collegato il modulo della scheda SD. Correggiamo la situazione, carichiamo il firmware... E silenzio... Perché non ho cambiato idea, ma la scatoletta si è appena aperta: uno dei pin del modulo doveva essere collegato a VCC. Nel mio caso, il modulo supportava 5 V per l'alimentazione, quindi, senza pensarci due volte, ho collegato il cavo proveniente dal modulo al lato opposto della scheda. Di conseguenza, il connettore saldato storto si è distorto e Il contatto UART è stato semplicemente perso. facepalm.jpg In generale, “una testa cattiva non dà riposo alle gambe”, e le mani storte non danno riposo alla testa...
Di conseguenza, ho visto il tanto atteso
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING /
Inoltre, si muove e l'indicatore di caricamento gira. Ricordo subito i miei giorni di scuola e il piacevole caricamento di MinuetOS da un floppy disk. A meno che l'unità non macina.
Il problema è che dopo il messaggio BOOT non succede nulla. Ciò significa che è ora di connettersi tramite OpenOCD a Raspberry, a GDB sull'host e vedere di cosa si tratta.
Innanzitutto, la connessione tramite GDB lo ha dimostrato immediatamente $pc
(contatore del programma, indirizzo dell'istruzione corrente) vola verso 0x0
- questo probabilmente accade dopo più errori. Pertanto, immediatamente dopo l'emissione del messaggio BOOT
Aggiungiamo 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ì complicato viene utilizzato "per affidabilità": ho sentito da qualche parte che un ciclo infinito è un comportamento indefinito, ma è improbabile che il compilatore indovini (ti ricordo che secondo 0x10000
che si trova nella BootROM).
Sembrerebbe, cos'altro aspettarsi: hard embedded, che tipo di codici sorgente ci sono? Ma in
(gdb) file builds/zeowaa-e115/sdboot.elf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from builds/zeowaa-e115/sdboot.elf...done.
Devi solo scaricare non il file MIF o il cestino, ma la versione originale in formato ELF.
Ora potete indovinare all'ennesimo tentativo l'indirizzo dove proseguirà l'esecuzione (questo è un altro motivo per cui il compilatore non avrebbe dovuto indovinare che il ciclo è infinito). Squadra
set variable $pc=0xADDR
consente di modificare al volo il valore del registro (in questo caso, l'indirizzo dell'istruzione corrente). Con il suo aiuto, puoi modificare i valori scritti in memoria (e i registri mappati in memoria).
Alla fine, sono giunto alla conclusione (non sono sicuro di quale sia corretta) che abbiamo "un'immagine della scheda SD del sistema sbagliato" e non dobbiamo andare all'inizio dei dati scaricati, ma a 0x89800
byte ulteriormente:
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 .rodata
Forse su questo ha influito anche il fatto che non avendo a portata di mano una scheda da 4Gb non necessaria, ne ho presa una da 2Gb e l'ho sostituita a caso nel Makefile DEMO_END=11718750
su DEMO_END=3078900
(non cercare il significato in un significato specifico: non ce n'è, è solo che ora l'immagine è posizionata sulla carta).
Livello due, U-Boot
Adesso stiamo ancora “cadendo”, ma siamo già nel posto giusto 0x0000000080089a84
. Qui devo ammetterlo: in effetti la presentazione non va “di tutto punto”, ma è scritta parzialmente “dopo”, quindi ecco che sono già riuscito ad inserire il file dtb corretto dal nostro SoC, correggetelo nelle impostazioni HiFive_U-Boot
variabile CONFIG_SYS_TEXT_BASE=0x80089800
(invece di 0x08000000
) in modo che l'indirizzo di download corrisponda a quello effettivo. Ora carichiamo la mappa del livello successivo, un'altra immagine:
(gdb) file ../freedom-u-sdk/work/HiFive_U-Boot/u-boot
(gdb) tui en
E 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 è sorprendente, dato che in $sp
sta il significato 0xfffffffe31cdc0a0
. Ahimè, anche questo “scappa” costantemente a causa della riga 307. Proviamo quindi a impostare un punto di interruzione su trap_entry
, e poi torna a 0x80089800
(punto di ingresso di U-Boot), e speriamo che non richieda la corretta impostazione dei registri prima di saltare... Sembra che funzioni:
(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 = 0x81cf950
Lo stack pointer è così così, francamente: punta a bypassare del tutto la RAM (a meno che, ovviamente, non abbiamo già la traduzione degli indirizzi, ma speriamo in un'opzione semplice).
Proviamo a sostituire il puntatore con 0x881cf950
. Di conseguenza, arriviamo alla conclusione che handle_trap
chiamato e chiamato, e allo stesso tempo entriamo _exit_trap
con un argomento epc=2148315240
(in decimale):
(gdb) x/10i 2148315240
0x800cb068 <strnlen+12>: lbu a4,0(a5)
0x800cb06c <strnlen+16>: bnez a4,0x800cb078 <strnlen+28>
0x800cb070 <strnlen+20>: sub a0,a5,a0
0x800cb074 <strnlen+24>: ret
0x800cb078 <strnlen+28>: addi a5,a5,1
0x800cb07c <strnlen+32>: j 0x800cb064 <strnlen+8>
0x800cb080 <strdup>: addi sp,sp,-32
0x800cb084 <strdup+4>: sd s0,16(sp)
0x800cb088 <strdup+8>: sd ra,24(sp)
0x800cb08c <strdup+12>: li s0,0
Imposta il punto di interruzione su strnlen
, proseguiamo 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=<optimized out>, precision=<optimized out>, flags=<optimized out>) 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 %08x , ra %08lxn", fmt@entry=0x800d4458 "exception code: %d , %s , epc %08x , ra %08lxn", 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 %08x , ra %08lxn", 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 %08x , ra %08lxn", args=args@entry=0x881cc188) at lib/vsprintf.c:717
#5 0x00000000800ccb50 in printf (fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc %08x , ra %08lxn") at lib/vsprintf.c:792
#6 0x000000008008a9f0 in _exit_trap (regs=<optimized out>, epc=2148315240, code=<optimized out>) at arch/riscv/lib/interrupts.c:92
#7 handle_trap (mcause=<optimized out>, epc=<optimized out>, regs=<optimized out>) 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 PC
Sembra _exit_trap
vuole fornire informazioni di debug sull'eccezione che si è verificata, ma non può. Quindi, in qualche modo le nostre fonti non vengono più visualizzate. set directories ../freedom-u-sdk/HiFive_U-Boot/
DI! Ora visualizzato!
Bene, eseguiamolo di nuovo e vediamo dallo stack trace la causa del problema originale che ha causato il primo errore (mcause == 5
). Se ho capito bene cosa c'è scritto Load access fault
. Il motivo sembra essere quello 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 /* force 16 byte alignment */
#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 /* jump to board_init_f() */
$sp
ha lo stesso significato errato, e dentro board_init_f_init_reserve
si verifica un errore. Sembra che il colpevole sia questo: una variabile con un nome inequivocabile CONFIG_SYS_INIT_SP_ADDR
. È definito nel file HiFive_U-Boot/include/configs/HiFive-U540.h
. Ad un certo punto ho anche pensato che forse avrei dovuto aggiungere un boot loader per il processore - forse sarebbe più semplice sistemare un po' il processore? Ma poi ho visto che era più simile ad un manufatto non del tutto completato#if 0
-impostazioni specifiche per una diversa configurazione di memoria e puoi provare a farlo:
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 */
Ad un certo punto il numero di stampelle
Bene, approssimativamente, ecco un tavolino
trosinenko@trosinenko-pc:/hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot$ git show --name-status
commit 39cd67d59c16ac87b46b51ac1fb58f16f1eb1048 (HEAD -> zeowaa-1gb)
Author: Anatoly Trosinenko <[email protected]>
Date: Tue Jul 2 17:13:16 2019 +0300
Initial support for Zeowaa A-E115FB board
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.h
I dettagli possono essere trovati in
Come si è scoperto, su questa scheda SiFive i registri di alcuni dispositivi hanno indirizzi diversi. Si è anche scoperto che U-Boot è configurato utilizzando il meccanismo Kconfig, già familiare dal kernel Linux: ad esempio, puoi comandare make menuconfig
, e davanti a te apparirà una comoda interfaccia testuale che mostra le descrizioni dei parametri tramite ?
eccetera. In generale, dopo aver messo insieme una descrizione della terza dalle descrizioni di due schede, lanciando da lì ogni sorta di pretenziose riconfigurazioni PLL (a quanto pare, questo è in qualche modo collegato al controllo dal computer host tramite PCIe, ma questo non è certo) , ho ricevuto del firmware che, con il tempo giusto su Marte, mi ha dato un messaggio tramite UART su quale hash di commit è stato compilato e su quanta DRAM ho (ma io stesso ho scritto queste informazioni nell'intestazione).
L'unico peccato è che dopo questo la scheda di solito smette di rispondere tramite il processore JTAG, e il caricamento da una scheda SD, ahimè, non è veloce nella mia configurazione. D'altra parte, a volte BootROM dava il messaggio " ERROR
, non è stato possibile avviarsi e U-Boot è apparso immediatamente. Fu allora che mi resi conto: a quanto pare, dopo aver riavviato il bitstream nell'FPGA, la memoria non viene cancellata, non ha il tempo di "disallenarsi", ecc. In breve, puoi semplicemente quando appare un messaggio LOADING /
connettersi con un debugger e un comando set variable $pc=0x80089800
, aggirando così questo lungo caricamento (ovviamente, presupponendo che l'ultima volta si sia rotto abbastanza presto e non abbia avuto il tempo di caricare qualcosa sopra il codice originale).
A proposito, è generalmente normale che il processore si blocchi completamente e il debugger JTAG non possa connettersi ad esso con i messaggi?
Error: unable to halt hart 0
Error: dmcontrol=0x80000001
Error: dmstatus =0x00030c82
Quindi aspetta! L'ho già visto! Qualcosa di simile accade quando TileLink è in stallo e per qualche motivo non mi fido dell'autore del controller di memoria - l'ho scritto io stesso... All'improvviso, dopo la prima ricostruzione riuscita del processore dopo aver modificato il 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: 3
A questa strana frase prima In: serial
non prestare attenzione: stavo cercando di capire su un processore sospeso se funziona correttamente con l'ambiente. Cosa intendi con "È rimasto così per dieci minuti"? Almeno è riuscito a riposizionarsi e ad accedere al menu di avvio! Una piccola digressione: nonostante U-Boot venga caricato nei primi 2^24 byte dalla scheda SD, all'avvio si copia ad un indirizzo più lontano, scritto nell'header di configurazione, o semplicemente agli indirizzi più alti della RAM , ed esegue la rilocazione ELF dei caratteri e trasferisce lì il controllo. Quindi: sembra che abbiamo superato questo livello e abbiamo ricevuto un bonus per il fatto che il processore non si è bloccato saldamente dopo.
Allora perché il timer non funziona? Sembra che l'orologio non funzioni per qualche motivo...
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000
Cosa succede se giri le frecce manualmente?
(gdb) set variable *0x0200bff8=310000000
(gdb) c
poi:
Hit any key to stop autoboot: 0
MMC_SPI: 0 at 0:1 hz 20000000 mode 0
Conclusione: il tempo non stringe. Questo è probabilmente il motivo per cui l'input da tastiera non funziona:
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:
/* First char of ANSI escape sequence 'e' */
if (c == 'e') {
*esc = 1;
*key = KEY_NONE;
}
break;
case 1:
/* Second char of ANSI '[' */
if (c == '[') {
...
Il problema si è rivelato che sono stato un po' troppo intelligente: ho aggiunto la chiave alla configurazione del processore:
case DTSTimebase => BigInt(0)
... basandosi sul fatto che il commento diceva “se non lo sai, lascia 0”. E dopo tutto WithNBigCores
L'ho appena impostato su 1 MHz (come, tra l'altro, era indicato nella configurazione di U-Boot). Ma cavolo, sono pulito e meticoloso: lì non so, qui sono 25MHz! Alla fine, non funziona nulla. Ho rimosso i miei "miglioramenti" e...
Hit any key to stop autoboot: 0
MMC_SPI: 0 at 0:1 hz 20000000 mode 0
## Unknown partition table type 0
libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
** No partition table - mmc 0 **
## Info: input data size = 34 = 0x22
Running uEnv.txt boot2...
## Error: "boot2" not defined
HiFive-Unleashed #
Puoi anche inserire comandi! Ad esempio, dopo aver curiosato un po', puoi finalmente indovinare come entrare mmc_spi 1 10000000 0; mmc part
, riducendo la frequenza SPI da 20 MHz a 10 MHz. Perché? Bene, la frequenza massima di 20 MHz è stata scritta nella configurazione ed è ancora scritta lì. Ma, a quanto ho capito, le interfacce, almeno qui, funzionano così: il codice divide la frequenza dell'unità hardware (la mia è 25 MHz ovunque) per il target, e imposta il valore risultante come divisore nel controllo corrispondente Registrati. Il problema è che se per un UART da 115200 Hz c'è approssimativamente ciò che è necessario, se dividi 25000000 per 20000000 otterrai 1, cioè funzionerà a 25 MHz. Forse questo è normale, ma se vengono stabilite delle restrizioni, significa che qualcuno ne ha bisogno (ma questo non è certo)... In generale, è più facile metterlo da parte e andare avanti - lontano e, ahimè, per molto tempo. 25 MHz non è un Core i9.
Uscita 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
Partition Map for MMC device 0 -- Partition Type: EFI
Part Start LBA End LBA Name
Attributes
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() returned FDT_ERR_NOTFOUND
2376 bytes read in 0 ms
Running uEnv.txt boot2...
15332118 bytes read in 0 ms
## Loading kernel from FIT Image at 90000000 ...
Using 'config-1' configuration
Trying 'bbl' kernel subimage
Description: BBL/SBI/riscv-pk
Type: Kernel Image
Compression: uncompressed
Data Start: 0x900000d4
Data Size: 74266 Bytes = 72.5 KiB
Architecture: RISC-V
OS: Linux
Load Address: 0x80000000
Entry Point: 0x80000000
Hash algo: sha256
Hash value: 28972571467c4ad0cf08a81d9cf92b9dffc5a7cb2e0cd12fdbb3216cf1f19cbd
Verifying Hash Integrity ... sha256+ OK
## Loading fdt from FIT Image at 90000000 ...
Using 'config-1' configuration
Trying 'fdt' fdt subimage
Description: unavailable
Type: Flat Device Tree
Compression: uncompressed
Data Start: 0x90e9d31c
Data Size: 6911 Bytes = 6.7 KiB
Architecture: RISC-V
Load Address: 0x81f00000
Hash algo: sha256
Hash value: 10b0244a5a9205357772ea1c4e135a4f882409262176d8c7191238cff65bb3a8
Verifying Hash Integrity ... sha256+ OK
Loading fdt from 0x90e9d31c to 0x81f00000
Booting using the fdt blob at 0x81f00000
## Loading loadables from FIT Image at 90000000 ...
Trying 'kernel' loadables subimage
Description: Linux kernel
Type: Kernel Image
Compression: uncompressed
Data Start: 0x900123e8
Data Size: 10781356 Bytes = 10.3 MiB
Architecture: RISC-V
OS: Linux
Load Address: 0x80200000
Entry Point: unavailable
Hash algo: sha256
Hash value: 72a9847164f4efb2ac9bae736f86efe7e3772ab1f01ae275e427e2a5389c84f0
Verifying Hash Integrity ... sha256+ OK
Loading loadables from 0x900123e8 to 0x80200000
## Loading loadables from FIT Image at 90000000 ...
Trying 'ramdisk' loadables subimage
Description: buildroot initramfs
Type: RAMDisk Image
Compression: gzip compressed
Data Start: 0x90a5a780
Data Size: 4467411 Bytes = 4.3 MiB
Architecture: RISC-V
OS: Linux
Load Address: 0x82000000
Entry Point: unavailable
Hash algo: sha256
Hash value: 883dfd33ca047e3ac10d5667ffdef7b8005cac58b95055c2c2beda44bec49bd0
Verifying Hash Integrity ... sha256+ OK
Loading loadables from 0x90a5a780 to 0x82000000
Ok, abbiamo raggiunto il livello successivo, ma fa ancora freddo. E a volte cosparge anche di eccezioni. Puoi vedere mcause restando in attesa del codice all'indirizzo specificato $pc
e dopo si
essere su trap_entry
. Il gestore U-Boot stesso può restituire solo mcause = 0..4, quindi preparati a rimanere bloccato in un avvio errato. Poi sono entrato nella configurazione, ho iniziato a guardare cosa stavo cambiando e mi sono ricordato: ecco conf/rvboot-fit.txt
è scritto:
fitfile=image.fit
# below much match what's in FIT (ugha)
Bene, rendiamo conformi tutti i file, sostituiamo la riga di comando del kernel con qualcosa di simile, poiché ci sono sospetti che SIF0
- questo è l'output da qualche parte tramite PCIe:
-bootargs=console=ttySIF0,921600 debug
+bootargs=console=ttyS0,125200 debug
E cambiamo l'algoritmo di hashing da SHA-256 a MD5: non ho bisogno della forza crittografica (soprattutto durante il debug), ci vuole un tempo terribilmente lungo e per rilevare errori di integrità durante il caricamento, MD5 è troppo facile. Qual è il risultato finale? Abbiamo iniziato a completare il livello precedente notevolmente più velocemente (grazie all'hashing più semplice) e si è aperto quello successivo:
...
Verifying Hash Integrity ... md5+ OK
Loading loadables from 0x90a5a758 to 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
chosen {
linux,initrd-end = <0x00000000 0x83000000>;
linux,initrd-start = <0x00000000 0x82000000>;
riscv,kernel-end = <0x00000000 0x80a00000>;
riscv,kernel-start = <0x00000000 0x80200000>;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
};
libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
chosen {
linux,initrd-end = <0x00000000 0x83000000>;
linux,initrd-start = <0x00000000 0x82000000>;
riscv,kernel-end = <0x00000000 0x80a00000>;
riscv,kernel-start = <0x00000000 0x80200000>;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
};
Loading Kernel Image ... OK
Booting kernel in
3
Ma il tempo non scorre...
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000
Ops, sembra che la correzione dell'orologio si sia rivelata un placebo, anche se all'epoca mi sembrava che fosse d'aiuto. No, ovviamente è da sistemare, ma prima giriamo manualmente le frecce e vediamo cosa succede:
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=1000000
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=2000000
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=3000000
(gdb) c
Continuing.
Nel frattempo ...
Loading Kernel Image ... OK
Booting kernel in
3
2
1
0
## Starting application at 0x80000000 ...
No, andrò ad automatizzare l'orologio, altrimenti forse deciderà di calibrare il timer lì!
Nel frattempo, l'indirizzo dell'istruzione attuale punta da qualche parte
0000000080001c20 <poweroff>:
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 <softfloat_countLeadingZeros8+0x558>
80001c2e: e406 sd ra,8(sp)
80001c30: f7fff0ef jal ra,80001bae <printm>
80001c34: 8522 mv a0,s0
80001c36: 267000ef jal ra,8000269c <finisher_exit>
80001c3a: 00010797 auipc a5,0x10
80001c3e: 41e78793 addi a5,a5,1054 # 80012058 <htif>
80001c42: 639c ld a5,0(a5)
80001c44: c399 beqz a5,80001c4a <poweroff+0x2a>
80001c46: 72c000ef jal ra,80002372 <htif_poweroff>
80001c4a: 45a1 li a1,8
80001c4c: 4501 li a0,0
80001c4e: dc7ff0ef jal ra,80001a14 <send_ipi_many>
80001c52: 10500073 wfi
80001c56: bff5 j 80001c52 <poweroff+0x32>
all'interno del Berkeley Boot Loader caricato. Personalmente, ciò che mi confonde è la menzione htif
— Interfaccia host utilizzata per il lancio tethered del kernel (ovvero, in collaborazione con l'host ARM), ho assunto standalone. Tuttavia, se trovi questa funzione nel codice sorgente, puoi vedere che non tutto è poi così male:
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"); }
}
}
Missione: 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) }
Il che si collega a RTC, ovvero al misterioso MockAON, a cui inizialmente avevo pensato: “Allora, cosa abbiamo qui? Non chiaro? Spegniamolo!" Dato che ancora non capisco che tipo di magia dell'orologio stia accadendo lì, reimplementerò semplicemente questa logica in System.scala
:
val rtcDivider = RegInit(0.asUInt(16.W)) // на всякий случай поддержу до 16ГГц, я оптимист :)
val mhzInt = p(DevKitFPGAFrequencyKey).toInt
// Преположим, частота равна целому числу мегагерц
rtcDivider := Mux(rtcDivider === (mhzInt - 1).U, 0.U, rtcDivider + 1.U)
outer.clintOpt.foreach { clint =>
clint.module.io.rtcTick := rtcDivider === 0.U
}
Ci dirigiamo verso il kernel Linux
Qui la storia si è già trascinata ed è diventata un po’ monotona, quindi la descriverò da cima a fondo:
BBL ha ipotizzato la presenza di FDT a 0xF0000000
, ma l'ho già corretto! Bene, diamo un'occhiata di nuovo... Trovato in HiFive_U-Boot/arch/riscv/lib/boot.c, sostituito da 0x81F00000
, specificato nella configurazione di avvio di U-Boot.
Quindi la BBL si è lamentata del fatto che non c'era memoria. La mia strada risiedeva nella funzione mem_prop
che riscv-pk/machine/fdt.c: da lì ho imparato che è necessario contrassegnare il nodo ram fdt come device_type = "memory"
- quindi, forse, sarà necessario correggere il generatore del processore, ma per ora lo scriverò semplicemente manualmente - comunque, ho trasferito manualmente questo file.
Ora ho ricevuto il messaggio (fornito in forma formattata, 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 indicate secondo necessità riscv,kernel-start
и riscv,kernel-end
in DTB, ma gli zeri vengono analizzati. Debug query_chosen
ha mostrato che BBL sta tentando di analizzare un indirizzo a 32 bit, ma ne incontra una coppia <0x0 0xADDR>
e il primo valore sembra essere quello meno significativo. Aggiunto alla sezione chosen
chosen {
#address-cells = <1>;
#size-cells = <0>;
...
}
e corretta la generazione dei valori: non aggiungere 0x0
il primo elemento.
Questi 100500 semplici passaggi renderanno facile osservare la caduta di un pinguino:
Testo nascosto
Verifying Hash Integrity ... md5+ OK
Loading loadables from 0x90a5a758 to 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
chosen {
linux,initrd-end = <0x83000000>;
linux,initrd-start = <0x82000000>;
riscv,kernel-end = <0x80a00000>;
riscv,kernel-start = <0x80200000>;
#address-cells = <0x00000001>;
#size-cells = <0x00000000>;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
chosen {
linux,initrd-end = <0x83000000>;
linux,initrd-start = <0x82000000>;
riscv,kernel-end = <0x80a00000>;
riscv,kernel-start = <0x80200000>;
#address-cells = <0x00000001>;
#size-cells = <0x00000000>;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
Loading Kernel Image ... OK
Booting kernel in
3
2
1
0
## Starting application at 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: Ignoring memory range 0x80000000 - 0x80200000
[ 0.000000] Linux version 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc version 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99)) #1 SMP Wed Jul 3 21:29:21 MSK 2019
[ 0.000000] bootconsole [early0] enabled
[ 0.000000] Initial ramdisk at: 0x(____ptrval____) (16777216 bytes)
[ 0.000000] Zone ranges:
[ 0.000000] DMA32 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Normal [mem 0x00000000c0000000-0x00000bffffffffff]
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] On node 0 totalpages: 261632
[ 0.000000] DMA32 zone: 3577 pages used for memmap
[ 0.000000] DMA32 zone: 0 pages reserved
[ 0.000000] DMA32 zone: 261632 pages, LIFO batch:63
[ 0.000000] software IO TLB: mapped [mem 0xbb1fc000-0xbf1fc000] (64MB)
(il logo viene visualizzato da BBL e quello con i timestamp viene visualizzato dal kernel).
Fortunatamente, non so come sia ovunque, ma su RocketChip, quando colleghi il debugger tramite JTAG, puoi catturare le trappole fuori dalla scatola: il debugger si fermerà esattamente in questo punto.
Program received signal SIGTRAP, Trace/breakpoint trap.
0xffffffe0000024ca in ?? ()
(gdb) bt
#0 0xffffffe0000024ca in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) file work/linux/vmlinux
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from work/linux/vmlinux...done.
(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 stopped: frame did not save the 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); // < ВЫ НАХОДИТЕСЬ ЗДЕСЬ
}
Come diceva una vecchia barzelletta, CPU non trovata, emulazione software in esecuzione. Bene, o non correre. 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;
Bel commento dentro linux/arch/riscv/kernel/setup.c - una sorta di pittura di una recinzione utilizzando il metodo Tom Sawyer. In generale, per qualche motivo non ci sono stati vincitori oggi, il premio viene trasferito all'estrazione successiva...
Qui mi propongo di concludere il già lungo articolo.
Continua. Ci sarà un combattimento con un astuto insetto che riesce a nascondersi se ti avvicini lentamente con un solo passo.
Screencast per il download del testo (link esterno):
Fonte: habr.com