Parte 3: Carichiamo quasi Linux da una scheda SD su RocketChip

Parte 3: Carichiamo quasi Linux da una scheda SD su RocketChip In parte precedente è 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 metastabilità. 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:

Parte 3: Carichiamo quasi Linux da una scheda SD su RocketChip

Vista dal basso:

Parte 3: Carichiamo quasi Linux da una scheda SD su RocketChip

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 seguendo questo manuale.

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
make

Dopo 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
ERROR

Beh, 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).

Parte 3: Carichiamo quasi Linux da una scheda SD su RocketChip

A ben pensarci, cosa ci si può aspettare — il mondo degli embedded è così, che codici ci si possono aspettare. Ma in quell'articolo 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.

Parte 3: Carichiamo quasi Linux da una scheda SD su RocketChip

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=0xADDR

consente 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 .rodata

Forse 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 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)                      │

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 = 0x81cf950

Non è 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,0

Impostiamo 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 PC

Sembra 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 qui 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 di fissaggi tecnologici 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.h

Puoi vedere i dettagli in repository.

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 =0x00030c82

Aspettate 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:  3

Su 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:      0x00000000

E se girassi manualmente le lancette?

(gdb) set variable *0x0200bff8=310000000
(gdb) c

Allora:

Premi un tasto per fermare l'avvio automatico:  0
MMC_SPI: 0 a 0:1 hz 20000000 modalità 0

Risultato: 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 0x82000000

Ok, 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 debug

E 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
3

Solo che l'orologio non ticchetta...

(gdb) x/x 0x0200bff8
0x200bff8:      0x00000000

Ups, 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       80001c52

all'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):
Parte 3: Carichiamo quasi Linux da una scheda SD su RocketChip

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster