Část 3: Téměř načítání Linuxu z SD karty do RocketChip

Část 3: Téměř načítání Linuxu z SD karty do RocketChip В předchozí díl byl implementován víceméně řadič pracovní paměti, respektive wrapper over IP Core od Quartus, což je adaptér pro TileLink. Dnes v sekci „Portujeme RocketChip na málo známou čínskou desku s Cyclone“ uvidíte funkční konzoli. Proces trval trochu déle: už jsem si říkal, že rychle spustím Linux a půjdu dál, ale nebylo tomu tak. V této části navrhuji podívat se na proces spouštění U-Boot, BBL a nesmělé pokusy linuxového jádra o inicializaci. Ale existuje konzole - U-Boot, a docela pokročilá, mající mnoho z toho, co byste očekávali od plnohodnotné konzole.

Hardware bude obsahovat SD kartu připojenou přes rozhraní SPI a také UART. V softwarové části bude BootROM nahrazen xip na sdboot a ve skutečnosti byly přidány následující fáze načítání (na SD kartu).

Dokončení hardwaru

Takže úkol: musíte přejít na „velké“ jádro a připojit UART (od Raspberry) a SD adaptér (použili jsme kartu od Catalex se šesti piny: GND, VCC, MISO, MOSI, SCK, CS) .

V zásadě bylo všechno docela jednoduché. Ale než jsem si to uvědomil, byl jsem trochu házen ze strany na stranu: po předchozí době jsem se rozhodl, že znovu musím zamíchat System něco jako HasPeripheryUART (a podle toho implementace), totéž pro SD kartu - a vše bude připraveno. Pak jsem se rozhodl podívat se, jak to bylo implementováno ve „seriózním“ designu. Takže, co je na tom vážného? Arty se zjevně nehodí - monstrum zůstává unleahshed.DevKitConfigs. A najednou se ukázalo, že všude jsou nějaké překryvy, které se přidávaly přes parametry pomocí kláves. Myslím, že je to pravděpodobně velmi flexibilní a konfigurovatelné, ale rád bych alespoň něco spustil... Nemáte to samé, jen jednodušší a otravnější?... Tehdy jsem narazil vera.iofpga.FPGAChip pro Microsemi FPGA a hned jsem to rozebral na uvozovky a pokusil se analogicky udělat vlastní implementaci, naštěstí je víceméně celé „rozvržení základní desky“ v jednom souboru.

Ukázalo se, že opravdu stačí přidat System.scala line

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

Čára v těle třídy System přidává do souboru dts informace o frekvenci, na které tato část našeho SoC pracuje. Pokud jsem pochopil, DTS/DTB je statická obdoba technologie plug-and-play pro vestavěná zařízení: strom popisu dts je zkompilován do binárního souboru dtb a přenesen bootloaderem do jádra, aby mohl správně nakonfigurovat Hardware. Zajímavé je, že bez linky s tlclock vše syntetizuje perfektně, ale kompilace BootROM (dovolte mi připomenout, že to již bude sdboot) nebude fungovat - během procesu kompilace analyzuje soubor dts a vytvoří hlavičku s makrem TL_CLK, díky čemuž bude schopen správně nakonfigurovat frekvenční děliče pro externí rozhraní.

Budete také muset mírně upravit „zapojení“:

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
}

Registrační řetězce, abych byl upřímný, byly přidány jednoduše analogicky s některými jinými místy v původním kódu. S největší pravděpodobností by měli chránit před metastabilita. Možná v některé bloky již mají vlastní ochranu, ale nejprve ji chci spustit alespoň „na kvalitní úrovni“. Zajímavější otázkou pro mě je, proč se MISO a MOSI liší dq? Zatím jsem nenašel odpověď, ale zdá se, že zbytek kódu závisí právě na takovém spojení.

Fyzicky jsem jednoduše přiřadil designové piny k volným kontaktům na bloku a posunul propojku volby napětí na 3.3V.

SD adaptér

Pohled shora:

Část 3: Téměř načítání Linuxu z SD karty do RocketChip

Pohled zespoda:

Část 3: Téměř načítání Linuxu z SD karty do RocketChip

Ladění softwaru: Nástroje

Nejprve si promluvme o dostupných ladicích nástrojích a jejich omezeních.

Minicom

Nejprve si budeme muset nějak přečíst, jaký výstup má bootloader a jádro. K tomu na Linuxu (v tomto případě na RaspberryPi) potřebujeme program Minicom. Obecně řečeno, každý program, který pracuje se sériovým portem, bude stačit.

Vezměte prosím na vědomí, že při spuštění musí být název zařízení portu specifikován jako -D /dev/ttyS0 - po opci -D. No, hlavní informace: ukončit, použít Ctrl-A, X. Vlastně jsem měl případ, kdy tato kombinace nefungovala - pak lze jednoduše říci ze sousední relace SSH killall -KILL minicom.

Je tu ještě jedna funkce. RaspberryPi má konkrétně dva UARTy a oba porty už lze k něčemu uzpůsobit: jeden pro Bluetooth, druhý standardně vydává konzoli jádra. Naštěstí lze toto chování potlačit podle tohoto návodu.

Přepis paměti

Při ladění, abych ověřil hypotézu, jsem někdy musel načíst bootloader (pardon) do RAM přímo z hostitele. Možná to jde udělat přímo z GDB, ale nakonec jsem šel jednoduchou cestou: zkopíroval jsem potřebný soubor do Raspberry, také přes SSH (telnet z OpenOCD) přeposlal port 4444 a použil příkaz load_image. Když to uděláte, zdá se, že je všechno zamrzlé, ale ve skutečnosti "nespí, jen pomalu bliká": Stáhne soubor, prostě to udělá rychlostí několika kilobajtů za sekundu.

Vlastnosti instalace breakpointů

Mnoho lidí na to pravděpodobně nemuselo myslet při ladění běžných programů, ale body přerušení nejsou vždy nastaveny v hardwaru. Někdy nastavení bodu přerušení zahrnuje dočasné zapsání speciálních pokynů na správné místo přímo do strojového kódu. Například takto fungoval můj standardní příkaz b v GDB. Následuje následující:

  • do BootROM nemůžete dát tečku, protože ROM
  • Můžete nastavit bod přerušení na kódu načteném do RAM z SD karty, ale musíte počkat, až se načte. Jinak nepřepíšeme kus kódu, ale zavaděč přepíše náš bod přerušení

Jsem si jistý, že můžete výslovně požádat o použití hardwarových bodů přerušení, ale stejně jich je omezený počet.

Rychlá výměna BootROM

V počáteční fázi ladění je často potřeba opravit BootROM a zkusit to znovu. Je tu ale problém: BootROM je součástí návrhu nahraného do FPGA a jeho syntéza je otázkou několika minut (a to po téměř okamžité kompilaci samotného obrazu BootROM z C a Assembleru...). Naštěstí ve skutečnosti všechno mnohem rychlejší: pořadí akcí je následující:

  • regenerovat bootrom.mif (přešel jsem na MIF místo HEX, protože jsem měl vždy nějaké problémy s HEX a MIF je nativní formát Alter)
  • v Quartus říci Processing -> Update Memory Initialization File
  • na položce Assembler (v levém sloupci Úkolů) příkaz Spustit znovu

Vše o všem - pár desítek sekund.

Příprava SD karty

Vše je zde relativně jednoduché, ale musíte být trpěliví a mít asi 14 GB místa na disku:

git clone https://github.com/sifive/freedom-u-sdk
git submodule update --recursive --init
make

Poté musíte vložit čistou, nebo spíše takovou, která neobsahuje nic potřebného, ​​SD kartu a spustit

sudo make DISK=/dev/sdX format-boot-loader

… kde sdX — zařízení přiřazené ke kartě. POZOR: data na kartě budou smazána, přepsána a vůbec! Sotva se vyplatí provádět celou montáž zespodu sudoprotože pak budou všechny artefakty stavby patřit root, a montáž bude muset být provedena zespodu sudo neustále.

Výsledkem je karta označená v GPT se čtyřmi oddíly, z nichž jeden má FAT uEnv.txt a bootovatelný obraz ve formátu FIT (obsahuje několik dílčích obrazů, každý s vlastní adresou pro stahování), druhý oddíl je prázdný, má být naformátován v Ext4 pro Linux. Další dva oddíly - tajemný: U-Boot žije na jednom (jeho offset, pokud jsem pochopil, je pevně zakódován do BootROM), na druhém, jak se zdá, jeho proměnné prostředí žijí, ale zatím je nepoužívám.

Úroveň jedna, BootROM

Populární moudrost říká: „Pokud se v programování tančí s tamburínou, pak se v elektronice také tančí s hasicím přístrojem. Není to ani o tom, že jednou jsem málem spálil desku a rozhodl se, že „No, GND je stejně nízká úroveň“. (podle všeho by rezistor neuškodil...) Jde spíše o to, že pokud vám odtamtud nerostou ruce, tak elektronika nepřestává přinášet překvapení: při pájení konektoru na desku se mi stále nedařilo pořádně připájet kontakty - video ukazuje, jak se pájka sama roztírá přes celé spojení stačí použít páječku, pro mě „plácnul“ náhodně. No, možná ta pájka nebyla vhodná pro teplotu páječky, možná něco jiného... Obecně, když jsem viděl, že už mám tucet kontaktů, vzdal jsem to a začal jsem ladit. A pak to začalo tajemný: Připojil jsem RX/TX z UART, načtu firmware - píše

INIT
CMD0
ERROR

Vše je logické - modul SD karty jsem nepřipojil. Napravíme situaci, nahrajeme firmware... A ticho... Proč jsem si to nerozmyslel, ale právě se otevřela malá krabička: jeden z pinů modulu musel být připojen k VCC. V mém případě modul podporoval napájení 5V, takže jsem bez přemýšlení zapojil vodič z modulu na opačnou stranu desky. V důsledku toho se křivě připájený konektor zešikmil a Kontakt UART byl jednoduše ztracen. facepalm.jpg Obecně platí, že „špatná hlava nedá odpočinek nohám“ a křivé ruce nedají odpočinek hlavě...

V důsledku toho jsem viděl dlouho očekávané

INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING /

Navíc se pohybuje a točí se indikátor zatížení. Okamžitě se mi vybaví školní časy a poklidné načítání MinuetOS z diskety. Pokud se disk nesemele.

Problém je v tom, že po zprávě BOOT se nic neděje. To znamená, že je čas připojit se přes OpenOCD k Raspberry, ke GDB na hostiteli a podívat se, co to je.

Za prvé, připojení pomocí GDB to okamžitě ukázalo $pc (počítadlo programu, adresa aktuální instrukce) letí na 0x0 - pravděpodobně se to stane po více chybách. Tedy ihned po vydání zprávy BOOT Přidáme nekonečnou smyčku. To ho na chvíli zdrží...

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;
 }

Takový záludný kód se používá „pro spolehlivost“: někde jsem slyšel, že nekonečná smyčka je nedefinované chování, ale kompilátor to pravděpodobně neuhodne (připomínám, že podle 0x10000 umístěný BootROM).

Část 3: Téměř načítání Linuxu z SD karty do RocketChip

Mohlo by se zdát, co jiného čekat - drsně vložené, jaké jsou tam zdrojové kódy? Ale v ten článek autor ladil kód C... Kreks-fex-pex:

(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.

Část 3: Téměř načítání Linuxu z SD karty do RocketChip

Stačí si stáhnout nikoli soubor MIF nebo přihrádku, ale původní verzi ve formátu ELF.

Nyní můžete n-tým pokusem uhodnout adresu, kde bude provádění pokračovat (to je další důvod, proč kompilátor neměl uhodnout, že smyčka je nekonečná). tým

set variable $pc=0xADDR

umožňuje měnit hodnotu registru za běhu (v tomto případě adresu aktuální instrukce). S jeho pomocí můžete měnit hodnoty zapsané do paměti (a paměťově mapované registry).

Nakonec jsem došel k závěru (nejsem si jistý, co je správné), že máme „obraz karty SD nesprávného systému“ a musíme jít ne na úplný začátek stažených dat, ale 0x89800 bajtů dále:

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

Snad na to mělo vliv i to, že nemít po ruce zbytečnou 4Gb kartu, vzal jsem 2Gb a náhodně ji vyměnil v Makefile DEMO_END=11718750 na DEMO_END=3078900 (nehledejte význam v konkrétním významu - žádný neexistuje, jen je nyní obrázek umístěn na kartě).

Úroveň dva, U-Boot

Nyní stále „padáme“, ale už jsme na správném místě 0x0000000080089a84. Zde musím uznat: ve skutečnosti prezentace nejede „se všemi zastávkami“, ale je částečně napsána „po“, takže sem se mi již podařilo vložit správný dtb soubor z našeho SoC, opravit jej v nastavení HiFive_U-Boot variabilní CONFIG_SYS_TEXT_BASE=0x80089800 (místo 0x08000000), aby adresa ke stažení odpovídala skutečné. Nyní načteme mapu další úrovně, další obrázek:

(gdb) file ../freedom-u-sdk/work/HiFive_U-Boot/u-boot
(gdb) tui en

A vidíme:

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

Navíc přeskakujeme mezi linkami 308 a 309. A není se čemu divit, vzhledem k tomu, že v $sp spočívá smysl 0xfffffffe31cdc0a0. Bohužel také neustále „utíká“ kvůli řádku 307. Zkusme proto nastavit zarážku na trap_entrya pak se vraťte na 0x80089800 (vstupní bod U-Bootu), a doufejme, že nebude vyžadovat správné nastavení registrů před skokem... Vypadá to, že to funguje:

(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

Ukazatel zásobníku je tak-tak, upřímně řečeno: ukazuje na úplné vynechání RAM (pokud samozřejmě ještě nemáme překlad adres, ale doufejme v jednoduchou možnost).

Zkusme nahradit ukazatel za 0x881cf950. V důsledku toho docházíme k závěru, že handle_trap volali a volali a zároveň jdeme do _exit_trap s argumentem epc=2148315240 (v desítkové soustavě):

(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

Nastavit bod přerušení na strnlen, pokračujeme a uvidíme:

(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

Zdá se, že, _exit_trap chce poskytnout ladicí informace o výjimce, ke které došlo, ale nemůže. Takže naše zdroje se zase nějak nezobrazují. set directories ../freedom-u-sdk/HiFive_U-Boot/ O! Nyní zobrazeno!

Spusťte to znovu a ze zásobníku se podívejme na příčinu původního problému, který způsobil první chybu (mcause == 5). Jestli jsem správně pochopil co je napsáno zde na straně 37, pak tato výjimka znamená Load access fault. Zdá se, že důvodem je zde

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 má stejný nesprávný význam a uvnitř board_init_f_init_reserve dojde k chybě. Vypadá to, že toto je viník: proměnná s jednoznačným názvem CONFIG_SYS_INIT_SP_ADDR. Je definován v souboru HiFive_U-Boot/include/configs/HiFive-U540.h. V určitém okamžiku jsem si dokonce myslel, že bych možná měl přidat boot loader pro procesor - možná by bylo jednodušší procesor trochu opravit? Ale pak jsem viděl, že to byl spíš artefakt z ne zcela dokončeného#if 0-specifická nastavení pro jinou konfiguraci paměti a můžete zkusit provést toto:

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 */

V určitém okamžiku počet berliček technologické spojovací prvky dosáhl kritického bodu. Po menším úsilí jsem dospěl k potřebě udělat správný port pro mou desku. K tomu potřebujeme zkopírovat a upravit řadu souborů tak, aby vyhovovaly naší konfiguraci.

No, přibližně, tady je malá tabulka

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

Podrobnosti najdete v úložišť.

Jak se ukázalo, na této desce SiFive mají registry některých zařízení různé adresy. Také se ukázalo, že U-Boot se konfiguruje pomocí mechanismu Kconfig, známého již z linuxového jádra – můžete například příkaz make menuconfiga před vámi se objeví pohodlné textové rozhraní s popisy parametrů pomocí ? atd. Obecně platí, že když jsem dal dohromady popis třetí z popisů dvou desek, vyhodili odtud všemožné domýšlivé PLL rekonfigurace (zřejmě to nějak souvisí s ovládáním z hostitelského počítače přes PCIe, ale není to jisté) , dostal jsem nějaký firmware, který mi za správného počasí na Marsu dal přes UART zprávu o tom, z jakého commit hashe to bylo zkompilováno a kolik mám DRAM (ale tuto informaci jsem sám napsal do záhlaví).

Jediná škoda je, že po tomto většinou přestala deska reagovat přes JTAG procesoru a načítání z SD karty bohužel v mé konfiguraci není rychlé. Na druhou stranu, někdy BootROM vydal zprávu, že ERROR, nepodařilo se zavést a okamžitě se objevil U-Boot. Tehdy mi to došlo: zjevně se po restartu bitstreamu do FPGA paměť nevymaže, nemá čas „odtrénovat“ atd. Zkrátka můžete jednoduše, když se objeví zpráva LOADING / spojit se s debuggerem a příkazem set variable $pc=0x80089800, čímž se obejde toto dlouhé načítání (samozřejmě za předpokladu, že se minule zlomil dostatečně brzy, takže nestihl načíst nic nad původní kód).

Mimochodem, je obecně normální, že procesor úplně zamrzne a JTAG debugger se k němu nemůže připojit zprávami?

Error: unable to halt hart 0
Error:   dmcontrol=0x80000001
Error:   dmstatus =0x00030c82

Tak počkej! Tohle už jsem viděl! Něco podobného se stane, když je TileLink zablokován a já nějak nevěřím autorovi paměťového řadiče - napsal jsem to sám... Najednou jsem po první úspěšné přestavbě procesoru po úpravě řadiče viděl:

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

K této podivné linii předtím In: serial nevěnujte pozornost - snažil jsem se pochopit na zavěšeném procesoru, zda správně funguje s prostředím. Co tím myslíš: "Takhle to visí deset minut"? Alespoň se to podařilo přemístit a přejít do boot menu! Malá odbočka: ačkoli je U-Boot načten v prvních 2^24 bajtech z SD karty, při spuštění se zkopíruje na vzdálenější adresu, buď zapsanou v konfigurační hlavičce, nebo jednoduše na vyšší adresy RAM a provádí přemístění znaků ELF a přenáší tam kontrolu. Takže: vypadá to, že jsme tuto úroveň překonali a dostali bonus, že poté procesor nevisel pevně.

Proč tedy nefunguje časovač? Zdá se, že hodiny z nějakého důvodu nefungují...

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

Co když budete otáčet šipkami ručně?

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

Pak:

Hit any key to stop autoboot:  0
MMC_SPI: 0 at 0:1 hz 20000000 mode 0

Závěr: hodiny netikají. To je pravděpodobně důvod, proč vstup z klávesnice nefunguje:

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 == '[') {
...

Problém se ukázal být v tom, že jsem byl příliš chytrý: přidal jsem klíč do konfigurace procesoru:

  case DTSTimebase => BigInt(0)

... na základě skutečnosti, že komentář řekl „pokud nevíte, zanechte 0.“ A koneckonců WithNBigCores Jen jsem to nastavil na 1MHz (jak bylo mimochodem uvedeno v konfiguraci U-Boot). Ale sakra, jsem úhledný a pečlivý: nevím, tady je to 25 MHz! Nakonec nic nefunguje. Odstranil jsem svá „vylepšení“ a...

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 #

Můžete dokonce zadávat příkazy! Například po malém šťouchání můžete konečně uhodnout, že vstoupíte mmc_spi 1 10000000 0; mmc part, snížení frekvence SPI z 20MHz na 10MHz. Proč? No a v konfigu byla napsaná maximální frekvence 20MHz a je tam napsaná dodnes. Ale pokud jsem pochopil, rozhraní, alespoň zde, fungují takto: kód vydělí frekvenci hardwarové jednotky (moje je všude 25 MHz) cílem a výslednou hodnotu nastaví jako dělitel v odpovídajícím ovládacím prvku Registrovat. Problém je v tom, že pokud je pro 115200Hz UART přibližně to, co je potřeba, tak když vydělíte 25000000 20000000, dostanete 1, tzn. bude pracovat na 25 MHz. Možná je to normální, ale pokud jsou nastavena omezení, znamená to, že to někdo potřebuje (ale není to jisté)... Obecně je jednodušší to odložit a jít dál - daleko a bohužel na dlouhou dobu. 25MHz není Core i9.

Výstup na konzolu

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

Dobře, dostali jsme se na další úroveň, ale pořád mrzne. A občas kropí i výjimky. Mcause můžete vidět tak, že budete čekat na kód na zadané adrese $pc a poté si být na trap_entry. Samotný obslužný program U-Boot může vydávat pouze mcause = 0..4, takže se připravte na to, že uvíznete v nesprávném spouštění. Pak jsem šel do konfigurace, začal jsem se dívat na to, co měním, a vzpomněl jsem si: tam conf/rvboot-fit.txt psaný:

fitfile=image.fit
# below much match what's in FIT (ugha)

No, pojďme uvést všechny soubory do souladu, nahradit příkazový řádek jádra něčím takovým, protože existují podezření, že SIF0 - toto je výstup někde přes PCIe:

-bootargs=console=ttySIF0,921600 debug
+bootargs=console=ttyS0,125200 debug

A změňme hashovací algoritmus z SHA-256 na MD5: nepotřebuji kryptografickou sílu (zejména při ladění), trvá to strašně dlouho a pro zachycení chyb integrity při načítání je MD5 příliš snadné. Jaký je konečný výsledek? Předchozí úroveň jsme začali dokončovat znatelně rychleji (kvůli jednoduššímu hašování) a otevřela se další:

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

Ale hodiny netikají...

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

Jejda, vypadá to, že korekce hodin se ukázala jako placebo, i když se mi v tu chvíli zdálo, že to pomohlo. Ne, samozřejmě to musí být opraveno, ale nejprve ručně otočme šipky a uvidíme, co se stane:

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.

Mezitím…

   Loading Kernel Image ... OK
Booting kernel in
3
2
1
0
## Starting application at 0x80000000 ...

Ne, půjdu automatizovat hodiny – jinak se možná rozhodne zkalibrovat časovač tam!

Mezitím někde ukazuje adresa aktuálního návodu

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>

uvnitř nabitého zavaděče Berkeley. Osobně mě na tom mate ta zmínka htif — hostitelské rozhraní používané pro tetherované spouštění jádra (tedy ve spolupráci s hostitelským ARM), předpokládám samostatné. Pokud však tuto funkci najdete ve zdrojovém kódu, můžete vidět, že ne všechno je tak špatné:

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"); }
  }
}

Úkol: spusťte hodiny

Hledání registrů v CLINT nás vede k

    val io = IO(new Bundle {
      val rtcTick = Bool(INPUT)
    })

    val time = RegInit(UInt(0, width = timeWidth))
    when (io.rtcTick) { time := time + UInt(1) }

Což se pojí s RTC, nebo s tajemným MockAONem, o kterém jsem si zpočátku myslel: „Tak, co to tady máme? Nejasný? Pojďme to vypnout!" Vzhledem k tomu, že stále nerozumím tomu, jaký druh kouzla s hodinami se tam děje, tak tuto logiku jen znovu implementuji do 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
  }

Vydáváme se na linuxové jádro

Tady už se příběh protáhl a stal se trochu monotónním, takže ho popíšu odshora dolů:

BBL předpokládal přítomnost FDT při 0xF0000000, ale už jsem to opravil! No, podívejme se znovu... Našel jsem to HiFive_U-Boot/arch/riscv/lib/boot.c, nahrazen 0x81F00000, specifikované v konfiguraci spouštění U-Boot.

Pak si BBL stěžoval, že není žádná paměť. Moje cesta spočívala ve funkci mem_prop, co v riscv-pk/machine/fdt.c: odtud jsem se dozvěděl, že musíte označit uzel fdt ram jako device_type = "memory" - pak možná bude potřeba opravit generátor procesoru, ale zatím to zapíšu ručně - každopádně jsem tento soubor přenesl ručně.

Nyní jsem dostal zprávu (poskytovanou ve formátované podobě s návratem vozíku):

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.

Zdá se, že možnosti jsou uvedeny podle potřeby riscv,kernel-start и riscv,kernel-end v DTB, ale analyzují se nuly. Ladění query_chosen ukázal, že BBL se pokouší analyzovat 32bitovou adresu, ale narazí na pár <0x0 0xADDR>a první hodnota se zdá být nejméně významné bity. Přidáno do sekce chosen

chosen {
      #address-cells = <1>;
      #size-cells = <0>;
      ...
}

a opraveno generování hodnot: nepřidávat 0x0 první prvek.

Těchto 100500 XNUMX jednoduchých kroků vám usnadní pozorování pádu tučňáka:

Skrytý text

   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)

(logo zobrazuje BBL a logo s časovými razítky zobrazuje jádro).

Naštěstí nevím, jak je to všude, ale na RocketChip, když připojíte debugger přes JTAG, můžete chytat pasti z krabice - debugger se zastaví přesně v tomto bodě.

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

svobody-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); // < ВЫ НАХОДИТЕСЬ ЗДЕСЬ
}

Jak řekl starý vtip, CPU nenalezen, běží softwarová emulace. No, nebo neběhat. Ztraceno v jediném jádru procesoru.

/* The lucky hart to first increment this variable will boot the other cores */
atomic_t hart_lottery;
unsigned long boot_cpu_hartid;

Pěkný komentář v linux/arch/riscv/kernel/setup.c - druh nátěru plotu metodou Toma Sawyera. Obecně platí, že dnes z nějakého důvodu nebyli žádní výherci, cena se převádí do dalšího slosování...

Zde navrhuji ukončit již tak dlouhý článek.

Pokračování příště. Dojde k boji s mazaným broukem, který se dokáže schovat, pokud se k němu pomalu připlížíte singlestepem.

Screencast ke stažení textu (externí odkaz):
Část 3: Téměř načítání Linuxu z SD karty do RocketChip

Zdroj: www.habr.com

Přidat komentář