Część 3: Prawie ładujemy Linux z karty SD na RocketChip

Część 3: Prawie ładujemy Linux z karty SD na RocketChip W poprzedniej części Zrealizowano w miarę działający kontroler pamięci, a dokładniej — opakowanie nad IP Core z Quartus, które jest przejściówką do TileLink. Dziś w sekcji „Portujemy RocketChip na mało znaną chińską płytkę z Cyclone” zobaczycie działającą konsolę. Proces trochę się przedłużył: myślałem, że szybko uruchomię Linuxa i pójdziemy dalej, ale nic z tego. W tej części proponuję przyjrzeć się procesowi uruchamiania U-Boot, BBL i niepewnym próbom inicjalizacji jądra Linuxa. Ale konsola jest — U-Boot-owska, i dość zaawansowana, mająca wiele z tego, czego oczekujecie od pełnoprawnej konsoli.

W części sprzętowej dołączy się karta SD, połączona przez interfejs SPI, a także UART. W części programowej BootROM zostanie zamieniony z xip na sdboot i dodane zostaną kolejne etapy ładowania (na karcie SD).

Dopracowanie części sprzętowej

Tak więc, zadanie: trzeba przejść na „duże” jądro i podłączyć UART (z Raspberry) oraz adapter SD (używana była jakaś płytka od Catalex z sześcioma pinami: GND, VCC, MISO, MOSI, SCK, CS).

Zasadniczo, wszystko było dość proste. Ale zanim to zrozumiałem, trochę mnie powaliło z boku na bok: po poprzednim razem postanowiłem, że znów trzeba po prostu podmieszać w System coś w rodzaju HasPeripheryUART (i w implementację odpowiednio), to samo dla karty SD — i wszystko będzie gotowe. Potem postanowiłem sprawdzić, jak to jest zrealizowane w „poważnym” projekcie. Co zatem mamy w tym poważnym? Arty, najwyraźniej, nie pasuje — zostaje potwór unleahshed.DevKitConfigs. I wówczas okazało się, że wszędzie są jakieś overlaye, które są dodawane przez parametry po kluczach. Przypuszczam, że to prawdopodobnie bardzo elastyczne i konfigurowalne, ale chciałbym przynajmniej, żeby coś można było uruchomić na początek… A nie macie czegoś podobnego, tylko prostszego i bardziej prymitywnego?.. Wtedy natknąłem się na vera.iofpga.FPGAChip dla FPGA Microsemi i od razu przekopiowałem to na cytaty, próbując stworzyć swoją implementację na podobieństwo, szczęśliwie cała „rozprowadzanie płyty głównej” znajduje się w jednym pliku.

Okazało się, że rzeczywiście, trzeba tylko dodać do System.scala linie

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

Linia w ciele klasy System dodaje informacje o częstotliwości, na której działa ta część naszego SoC, do pliku dts. Z tego, co rozumiem, DTS/DTB jest statycznym odpowiednikiem technologii plug-and-play dla urządzeń wbudowanych: drzewo opisu dts kompilowane jest do binarnego pliku dtb i przekazywane bootloaderowi do poprawnej konfiguracji sprzętu. Co ciekawe, bez linii z tlclock wszystko pięknie się syntetyzuje, ale nie uda się skompilować BootROM (przypomnę, że będzie to już sdboot) — podczas kompilacji analizuje plik dts i tworzy nagłówek z makrem TL_CLK, dzięki któremu będzie w stanie poprawnie skonfigurować dzielniki częstotliwości dla zewnętrznych interfejsów.

Będzie także potrzebne trochę poprawić „rozmieszczenie”:

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
}

Łańcuchy rejestrów, szczerze mówiąc, dodano po prostu na podstawie pewnych innych fragmentów rozwoju kodu. Prawdopodobnie mają one chronić przed metastabilnością. Może niektóre bloki już mają swoje zabezpieczenia, ale na początek chciałbym uruchomić przynajmniej „na wysokim poziomie”. Bardziej interesujące dla mnie pytanie — dlaczego MISO i MOSI są podłączone do różnych dq Fizycznie po prostu przypisałem wyjścia projektu do wolnych styków na złączu i przestawiłem zworkę wyboru napięcia na 3.3V.? Ответа я пока так и не нашёл, но, похоже, остальной код рассчитывает именно на такое подключение.

SD-adapter

Widok z góry:

Widok z dołu:

Część 3: Prawie ładujemy Linux z karty SD na RocketChip

Debugowanie części programowej: narzędzia

Część 3: Prawie ładujemy Linux z karty SD na RocketChip

Na początek porozmawiajmy o dostępnych narzędziach debugowania i ich ograniczeniach.

Minicom

Po pierwsze, będziemy potrzebować jakoś odczytać to, co wyświetla bootloader i jądro. W tym celu na Linuxie (w tym przypadku — na tym, który jest na RaspberryPi) będziemy potrzebować programu Minicom. Generalnie nada się każdy program do obsługi portu szeregowego.

Zwróć uwagę, że przy uruchamianiu nazwa urządzenia portu powinna być podana jako

-D /dev/ttyS0 — po opcji . A główna informacja: aby wyjść, użyj , to będzie on umieszczał w nim ostatni blok masterchaina:Ctrl-A, X . Rzeczywiście miałem przypadek, kiedy ta kombinacja nie zadziałała — wtedy można z sąsiedniej sesji SSH po prostu powiedziećkillall -KILL minicom killall -KILL minicom.

Jest jeszcze jedna cecha. Konkretnie na Raspberry Pi są dwa UART, a oba porty mogą być już do czegoś przystosowane: jeden do Bluetooth, a przez drugi z domyślnie wyświetlana jest konsola jądra. Na szczęście, to zachowanie można przestawić. zgodnie z tym podręcznikiem.

Przebudowa pamięci

Podczas debugowania, aby sprawdzić hipotezę, zdarzało mi się czasami załadować bootloader (przepraszam) do pamięci RAM bezpośrednio z hosta. Może da się to zrobić bezpośrednio z GDB, ale ostatecznie poszedłem po prostej linii: skopiowałem na Raspberry potrzebny plik, przekierowałem przez SSH również port 4444 (telnet z OpenOCD) i skorzystałem z polecenia load_image. Gdy go wykonujesz, wydaje się, że wszystko się zawiesiło, ale w rzeczywistości «nie śpi, po prostu wolno miga»: ładuje plik, po prostu robi to z prędkością kilku kilobajtów na sekundę.

Cechy ustalania punktów przerwania

Prawdopodobnie wielu z was nie zastanawiało się nad tym przy debugowaniu zwykłych programów, ale punkty przerwania nie zawsze są ustawiane sprzętowo. Czasami ustalenie punktu przerwania polega na tymczasowym zapisaniu specjalnej instrukcji w odpowiednim miejscu bezpośrednio w kodzie maszynowym. Na przykład, tak działała u mnie standardowa komenda b w GDB. Oto, co z tego wynika:

  • nie można ustawić punktu wewnątrz BootROM, ponieważ ROM
  • można ustawić punkt przerwania na kod załadowany do pamięci RAM z karty SD, ale trzeba poczekać, aż zostanie załadowany. W przeciwnym razie nie my przepiszemy kawałek kodu, a bootloader przepisze nasz punkt przerwania

Jestem pewien, że można wyraźnie poprosić o użycie sprzętowych punktów przerwania, ale ich liczba jest w każdym przypadku ograniczona.

Szybka zamiana BootROM

Na początkowym etapie debugowania często pojawia się chęć poprawienia BootROM i spróbowania jeszcze raz. Ale jest problem: BootROM jest częścią projektu załadowywanego do FPGA, a jego synteza zajmuje kilka minut (a to po prawie natychmiastowej kompilacji samego obrazu BootROM z C i Assemblera…). Na szczęście w rzeczywistości wszystko jest znacznie szybsze: kolejność działań jest następująca:

  • ponownie wygenerować bootrom.mif (przeszedłem na MIF zamiast HEX, ponieważ z HEX miałem wieczne problemy, a MIF to rodzaj własny formatu Altery)
  • w Quartus powiedzieć Processing -> Update Memory Initialization File
  • w punkcie Assembler (w lewej kolumnie Tasks) rozkazać Uruchom ponownie

Na wszystko - parę dziesiątek sekund.

Przygotowanie karty SD

Tut wszystko jest stosunkowo proste, ale trzeba uzbroić się w cierpliwość i około 14Gb miejsca na dysku:

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

Następnie należy włożyć czystą, a raczej niezawierającą niczego potrzebnego, kartę SD i wykonać

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

… gdzie sdX — urządzenie przypisane do karty. UWAGA: dane na karcie zostaną usunięte, nadpisane i w ogóle! Raczej nie warto robić całej kompilacji z sudo, ponieważ wtedy wszystkie artefakty kompilacji będą należeć do root, a kompilację trzeba będzie robić z sudo na stałe.

W efekcie otrzymujemy kartę oznaczoną w GPT z czterema partycjami, w której jedna z nich to FAT z uEnv.txt i obrazem bootowalnym w formacie FIT (zawiera on kilka podobrazów, każdy z własnym adresem ładowania), inna partycja — czysta, planuje się sformatować ją w Ext4 dla Linuksa. Dwie pozostałe partycje — tajemnicze: na jednej mieszka U-Boot (jego offset, jak rozumiem, jest zapisany w BootROM), na drugiej, jak się wydaje, znajdują się jego zmienne środowiskowe, ale ja ich na razie nie używam.

Poziom pierwszy, BootROM

Ludowa mądrość mówi: „Jeśli w programowaniu są tańce z bębnem, to w elektronice — jeszcze z gaśnicą”. Nie chodzi nawet o to, że raz prawie spaliłem płytkę, myśląc: „Cóż, GND — to przecież niski poziom” (wyraźnie, rezystor by się przydał…) Chodzi bardziej o to, że jeśli ręce nie rosną stamtąd, to elektronika przestaje przynosić niespodzianki: lutując złącze na płytce, nie udało mi się poprawnie wylutować styków — w filmach widać, jak lut bezpośrednio się rozchodzi po całym połączeniu, wystarczy przyłożyć lutownicę, ja jednak miałem go „posklejanego” jak popadnie. Cóż, może lut nie pasował do temperatury lutownicy, pewnie coś jeszcze… W każdym razie, widząc, że mam już dziesięć styków, się poddałem i zacząłem debugować. A tu zaczęło się tajemnicze: podłączyłem RX/TX z UART-a, ładuję firmware — pisze

INIT
CMD0
ERROR

No, wszystko logiczne — moduł karty SD nie został podłączony. Naprawiam sytuację, ładuję firmware… I cisza… Co tylko nie przemknęło mi przez myśl, a pudełko otwierało się bardzo prosto: jeden z pinów modułu trzeba było podłączyć do VCC. W moim przypadku moduł obsługiwał 5V do zasilania, więc, nie myśląc długo, wpiąłem przewód ciągnący się od modułu do przeciwnej strony płytki. W rezultacie krzywo przylutowane złącze lekko się przekrzywiło, i po prostu zgubił się kontakt UART. facepalm.jpg Generalnie, "zła głowa nie daje nogom spokoju", a krzywe ręce – głowie...

W końcu zobaczyłem w Minicom długo oczekiwane

INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
ŁADOWANIE /

Co więcej, wskaźnik ładowania kręci się i porusza. Przypominają się czasy szkolne i wolne ładowanie MinuetOS z dyskietki. Tylko dysk nie skrzypi.

Problem w tym, że po komunikacie BOOT nic się nie dzieje. Czas połączyć się przez OpenOCD na Raspberry, a do niego GDB na hoście, i zobaczyć, co to takiego.

Po pierwsze, połączenie za pomocą GDB od razu pokazało, że $pc (licznik programu, adres bieżącej instrukcji) ucieka do 0x0 – prawdopodobnie dzieje się tak po wielokrotnej usterce. Dlatego zaraz po wydaniu komunikatu BOOT dodamy nieskończoną pętlę. To go na chwilę zatrzyma...

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

Taki sprytny kod używa się "dla niezawodności": gdzieś słyszałem, że niby nieskończona pętla – to Niezdefiniowane Zachowanie, a tutaj kompilator raczej się nie domyśli (Przypominam, że po 0x10000 znajduje się BootROM).

Część 3: Prawie ładujemy Linux z karty SD na RocketChip

Wydawałoby się, a czego jeszcze oczekiwać – surowy embedded, jakie tu źródła. Ale przecież w tamtym artykule autor debugował kod w C... Kraks-feks-peks:

(gdb) file builds/zeowaa-e115/sdboot.elf
Program jest już debugowany.
Czy na pewno chcesz zmienić plik? (y or n) y
Czytanie symboli z builds/zeowaa-e115/sdboot.elf...gotowe.

Część 3: Prawie ładujemy Linux z karty SD na RocketChip

Tylko trzeba ładować nie plik MIF i nie bin, a oryginalną wersję w formacie ELF.

Teraz można w nieskończonej próbie zgadnąć adres, gdzie wykonanie się wznowi (to jeszcze jeden powód, dlaczego kompilator nie powinien był domyślić się, że pętla jest nieskończona). Komenda

set variable $pc=0xADDR

pozwala na zmianę wartości rejestru na bieżąco (w tym przypadku – adres bieżącej instrukcji). Tą samą metodą można zmieniać wartości zapisane w pamięci (oraz rejestry w mapowanej pamięci).

W końcu doszedłem do wniosku (nie jestem pewien, czy słusznego), że mamy „obraz karty SD tej systemu”, i należy przejść nie na sam początek załadowanych danych, a na 0x89800 bajt dalej:

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

Możliwe, że wpłynęło na to również to, że nie mając pod ręką niepotrzebnej karty 4 Gb, wziąłem 2 Gb i metodą prób i błędów zastąpiłem w Makefile. DEMO_END=11718750 na DEMO_END=3078900 (nie szukaj znaczenia w konkretnym znaczeniu — nie ma go, po prostu teraz obraz mieści się na karcie).

Poziom drugi, U-Boot

Teraz wciąż 'spadamy', ale już trafiamy na adres 0x0000000080089a84. Muszę przyznać, że tak naprawdę, przedstawienie nie idzie 'ze wszystkimi przystankami', a częściowo pisze się już 'potem', dlatego tutaj już zdążyłem podłożyć właściwy plik dtb z naszego SoC, poprawić w ustawieniach. HiFive_U-Boot zmienną CONFIG_SYS_TEXT_BASE=0x80089800 (zamiast 0x08000000), aby adres ładowania zgadzał się z rzeczywistym. Ładujemy teraz kartę następnego poziomu inny obraz:

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

I widzimy:

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

Przy czym skaczemy między liniami 308 a 309. I nie ma co się dziwić, biorąc pod uwagę, że w $sp jest wartość 0xfffffffe31cdc0a0. Niestety, ona także ciągle 'ucieka' z powodu linii 307. Dlatego spróbujemy ustawić punktBreak na trap_entry, a potem znów przejdźmy do 0x80089800 (punktu wejścia U-Boot), i miejmy nadzieję, że nie będzie wymagał poprawnego ustawienia rejestrów przed przejściem… Wygląda na to, że działa:

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

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

Niezbyt dobry wskaźnik stosu, szczerze mówiąc: wskazuje w ogóle poza RAM (o ile, oczywiście, nie mamy jeszcze translacji adresów, ale miejmy nadzieję na prostą wersję).

Spróbujmy zmienić wskaźnik na 0x881cf950. W rezultacie dochodzimy do tego, że handle_trap jest wywoływane i wywoływane, przy tym przechodząc do _exit_trap z argumentem epc=2148315240 (w postaci dziesiętnej):

(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

Ustawiamy breakpoint na strnlen, kontynuujemy i widzimy:

(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

Wygląda na to, _exit_trap że chce wydrukować informacje debugowania o wystąpieniu wyjątku, ale mu się to nie udaje. Wygląda na to, że źródła znów się nie wyświetlają. set directories ../freedom-u-sdk/HiFive_U-Boot/ O! Teraz się wyświetlają!

Cóż, uruchomimy jeszcze raz i zobaczymy w stosie wywołań przyczynę początkowego problemu, który wywołał pierwszy błąd (mcause == 5). Jeśli dobrze zrozumiałem, co jest napisane tutaj na stronie 37, to ten wyjątek oznacza błąd dostępu do pamięci. Przyczyną, jak się wydaje, jest to, że tutaj

arch/riscv/cpu/HiFive/start.S:

call_board_init_f:
    li  t0, -16
    li  t1, CONFIG_SYS_INIT_SP_ADDR
    and sp, t1, t0  /* wymusza 16-bajtowe wyrównanie */

#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       /* skok do board_init_f() */

$sp ma to niewłaściwe wartości, a w board_init_f_init_reserve wychodzi błąd. Wygląda na to, że winowajca to zmienna o jednoznacznej nazwie CONFIG_SYS_INIT_SP_ADDR. Została zdefiniowana w pliku HiFive_U-Boot/include/configs/HiFive-U540.hW pewnym momencie pomyślałem nawet, że może, darujmy sobie, poprawiać loader pod procesor — może łatwiej byłoby nieco zmodyfikować sam procesor? Ale potem zobaczyłem, że to bardziej przypomina artefakt z nie do końca poprawnych ustawień pod inną konfigurację pamięci, i można spróbować zrobić to tak:#if 0-

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

W pewnym momencie liczba obejść technicznych mocowań osiągnęła krytyczny poziom. Po pewnych rozważaniach doszedłem do wniosku, że konieczne będzie dokonanie poprawnego portu na moją płytkę. W tym celu trzeba skopiować i dostosować do naszej konfiguracji pewną ilość plików.

No, mniej więcej, to tyle

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

    Wstępne wsparcie dla płyty 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

Szczegóły można zobaczyć w repozytorium.

Okazało się, że na tej płycie SiFive rejestry niektórych urządzeń mają inne adresy. A co więcej, okazało się, że U-Boot konfiguruje się za pomocą znanego z jądra Linux mechanizmu Kconfig — na przykład można wydać polecenie generuj menuconfig, a przed wami pojawi się wygodny interfejs tekstowy z opisami parametrów. ? W skrócie, sklejając opisy dwóch płytek, stworzyłem opis trzeciej, wyrzucając wszelkie przesadzone ustawienia PLL (widocznie jest to jakoś związane z zarządzaniem z komputera hosta przez PCIe, ale to niepewne). Otrzymałem pewne oprogramowanie, które przy właściwej pogodzie na Marsie wysyłało mi przez UART wiadomość o tym, z jakiego hash'a komita zostało zbudowane, oraz ile mam DRAM (ale tę informację sam wpisałem w nagłówku).

Szkoda tylko, że po tym płyta zwykle przestawała odpowiadać przez JTAG procesora, a ładowanie z karty SD — niestety, w mojej konfiguracji nie jest szybkie. Z drugiej strony, czasami BootROM zwracał wiadomość, że ERROR, nie udało się załadować, a zaraz potem ukazywał się U-Boot. Właśnie wtedy dotarło do mnie, że: widocznie, po ponownym uruchomieniu bitstream w FPGA pamięć nie jest nadpisywana, nie ma czasu na "roztrenowanie" i tak dalej. Krótko mówiąc, można po prostu przy pojawieniu się wiadomości LOADING / połączyć się z debugerem i wydać polecenie set variable $pc=0x80089800, omijając w ten sposób to długie ładowanie (oczywiście zakładając, że ostatnio zawiodło wystarczająco wcześnie i nie miało czasu załadować coś ponad oryginalny kod).

Przy okazji, czy to w ogóle normalne, że procesor zupełnie się zawiesza, i nie może połączyć się z debugerem JTAG z komunikatami

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

Zaraz, czekajcie! Już to widziałem! Coś podobnego dzieje się przy deadlocku TileLink, a autorowi kontrolera pamięci jakoś nie ufam — sam to pisał… Nagle, po pierwszym udanym odbudowaniu procesora po edycji kontrolera zobaczyłem:

INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING
BOOT

U-Boot 2018.09-g39cd67d-dirty (03 lip 2019 - 13:50:33 +0300)

DRAM:  1 GiB
MMC:
BEFORE LOAD ENVBEFORE FDTCONTROLADDRBEFORE LOADADDRIn:    serial
Out:   serial
Err:   serial
Naciśnij dowolny klawisz, aby zatrzymać autoboot:  3

Na tę dziwną linię przed In: serial Nie zwracaj uwagi - próbowałem na zawieszonym procesorze zrozumieć, czy to działa poprawnie z otoczeniem. Co znaczy „Już dziesięć minut tak wisi”? Udało się przynajmniej przejść do menu rozruchowego! Małe dygresja: choć U-Boot ładowany jest wśród pierwszych 2^24 bajtów z karty SD, po uruchomieniu kopiuje się dalej na adres zapisany w nagłówku konfiguracji lub po prostu w wyższe adresy pamięci RAM, przeprowadza relokację symboli ELF i przekazuje tam sterowanie. Tak więc: wygląda na to, że ten poziom został przejrzany i bonusowo otrzymaliśmy procesor, który nie zawiesza się całkowicie po tym.

Więc dlaczego nie działa zegar? Wygląda na to, że zegary w ogóle z jakiegoś powodu nie działają…

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

A co, jeśli obrócimy wskazówki ręcznie?

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

W takim razie:

Naciśnij dowolny klawisz, aby zatrzymać autoboot:  0
MMC_SPI: 0 na 0:1 hz 20000000 tryb 0

Wynik: zegary nie działają. Prawdopodobnie z tego samego powodu nie działa również wejście z klawiatury:

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:
        /* Pierwszy znak sekwencji ucieczki ANSI 'e' */
        if (c == 'e') {
            *esc = 1;
            *key = KEY_NONE;
        }
        break;
    case 1:
        /* Drugi znak ANSI '[' */
        if (c == '[') {
...

Problem okazał się w tym, że trochę przesadziłem: dodałem w konfiguracji procesora klucz:

  case DTSTimebase => BigInt(0)

… kierując się tym, że w komentarzu napisano „jeśli nie wiesz - pozostaw 0”. I rzeczywiście WithNBigCores akurat ustawiał to na 1MHz (jak zresztą było wskazane w konfiguracji U-Boot). Ale ja, cholera, jestem ostrożny i skrupulatny: tam nie wiem, tu 25MHz! W efekcie nic nie działa. Usunąłem swoje „ulepszenia” i…

Naciśnij dowolny klawisz, aby zatrzymać autoboot:  0
MMC_SPI: 0 na 0:1 hz 20000000 tryb 0
## Nieznany typ tabeli partycji 0
libfdt fdt_path_offset() zwrócił FDT_ERR_NOTFOUND
** Brak tabeli partycji - mmc 0 **
## Informacja: rozmiar danych wejściowych = 34 = 0x22
Uruchamianie uEnv.txt boot2...
## Błąd: "boot2" nie jest zdefiniowane
HiFive-Unleashed #

Można nawet wprowadzać komendy! Na przykład, trochę pokombinowawszy, można w końcu zgadnąć, żeby wpisać mmc_spi 1 10000000 0; mmc part, zmniejszając częstotliwość SPI z 20MHz do 10MHz. Dlaczego? Cóż, w konfiguracji była zapisana maksymalna częstotliwość 20MHz, nadal jest tam zapisana. Ale, jak zrozumiałem, interfejsy, przynajmniej tutaj, działają w ten sposób: kod dzieli częstotliwość bloku sprzętowego (u mnie — wszędzie 25MHz) przez docelową, a następnie ustawia uzyskaną wartość jako dzielnik w odpowiednim rejestrze sterującym. Problem polega na tym, że jeśli dla UART-a 115200Hz będzie mniej więcej to, co potrzebne, to dzieląc 25000000 przez 20000000 otrzymamy 1, tzn. będzie działać na 25MHz. Może to i w porządku, ale jeśli ustalają ograniczenia, to znaczy, że komuś to potrzebne (ale to nie jest pewne)… W każdym razie, łatwiej to ustawić i iść dalej — daleko i, niestety, na długo. 25MHz — to nie jest Core i9.

Wyjście konsoli

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

Mapa partycji dla urządzenia MMC 0  --   Typ partycji: EFI

Część    Start LBA       Koniec LBA         Nazwa
        Atrybuty
        Typ GUID
        Partition GUID
  1     0x00000800      0x0000ffde      "Vfat Boot"
        attrs:  0x0000000000000000
        type:   ebd0a0a2-b9e5-4433-87c0-68b6b72699c7
        type:   dane
        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() zwrócił FDT_ERR_NOTFOUND
2376 bajtów odczytanych w 0 ms
Uruchamianie uEnv.txt boot2...
15332118 bajtów odczytanych w 0 ms
## Ładowanie jądra z obrazu FIT na 90000000 ...
   Używając konfiguracji 'config-1'
   Próbując podobraz jądra 'bbl'
     Opis:  BBL/SBI/riscv-pk
     Typ:         Obraz jądra
     Kompresja:  nieskompresowany
     Start danych:   0x900000d4
     Rozmiar danych:    74266 Bajtów = 72.5 KiB
     Architektura: RISC-V
     OS:           Linux
     Adres ładunku: 0x80000000
     Punkt wejścia:  0x80000000
     Algorytm skrótu:    sha256
     Wartość skrótu:   28972571467c4ad0cf08a81d9cf92b9dffc5a7cb2e0cd12fdbb3216cf1f19cbd
   Weryfikacja integralności skrótu ... sha256+ OK
## Ładowanie fdt z obrazu FIT na 90000000 ...
   Używając konfiguracji 'config-1'
   Próbując podobraz fdt 'fdt'
     Opis:  niedostępny
     Typ:         Płaski wykres urządzeń
     Kompresja:  nieskompresowany
     Start danych:   0x90e9d31c
     Rozmiar danych:    6911 Bajtów = 6.7 KiB
     Architektura: RISC-V
     Adres ładunku: 0x81f00000
     Algorytm skrótu:    sha256
     Wartość skrótu:   10b0244a5a9205357772ea1c4e135a4f882409262176d8c7191238cff65bb3a8
   Weryfikacja integralności skrótu ... sha256+ OK
   Ładowanie fdt z 0x90e9d31c do 0x81f00000
   Rozruch przy użyciu blobu fdt na 0x81f00000
## Ładowanie ładunków z obrazu FIT na 90000000 ...
   Próbując podobraz ładunków 'kernel'
     Opis:  Jądro Linuksa
     Typ:         Obraz jądra
     Kompresja:  nieskompresowany
     Start danych:   0x900123e8
     Rozmiar danych:    10781356 Bajtów = 10.3 MiB
     Architektura: RISC-V
     OS:           Linux
     Adres ładunku: 0x80200000
     Punkt wejścia:  niedostępny
     Algorytm skrótu:    sha256
     Wartość skrótu:   72a9847164f4efb2ac9bae736f86efe7e3772ab1f01ae275e427e2a5389c84f0
   Weryfikacja integralności skrótu ... sha256+ OK
   Ładowanie ładunków z 0x900123e8 do 0x80200000
## Ładowanie ładunków z obrazu FIT na 90000000 ...
   Próbując podobraz ładunków 'ramdisk'
     Opis:  buildroot initramfs
     Typ:         Obraz RAMDisk
     Kompresja:  skompresowany gzip
     Start danych:   0x90a5a780
     Rozmiar danych:    4467411 Bajtów = 4.3 MiB
     Architektura: RISC-V
     OS:           Linux
     Adres ładunku: 0x82000000
     Punkt wejścia:  niedostępny
     Algorytm skrótu:    sha256
     Wartość skrótu:   883dfd33ca047e3ac10d5667ffdef7b8005cac58b95055c2c2beda44bec49bd0
   Weryfikacja integralności skrótu ... sha256+ OK
   Ładowanie ładunków z 0x90a5a780 do 0x82000000

OK, przeszliśmy na nowy poziom, ale nadal się zawiesza. A czasami również generuje wyjątki. Można zobaczyć mcause, złapując kod pod wskazanym adresem $pc i po si znaleźć się na trap_entry. Procesor z U-Boot potrafi wyświetlać jedynie dla mcause = 0..4, więc przygotujcie się na zapętlenie na błędnym ładowaniu. Zajrzałem do konfiguracji, zacząłem sprawdzać, co zmieniałem, i przypomniałem sobie: to tam w conf/rvboot-fit.txt napisano:

fitfile=image.fit
# poniżej musi się zgadzać z tym, co w FIT (ugha)

Cóż, doprowadzimy wszystkie pliki do porządku, zmienimy linię poleceń jądra mniej więcej tak, ponieważ są podejrzenia, że SIF0 — to wyjście gdzieś przez PCIe:

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

I dodatkowo zmienimy algorytm haszowania z SHA-256 na MD5: nie potrzeba mi kryptograficznej odporności (tym bardziej w trakcie debugowania), oblicza się to niesamowicie długo, a do wychwytywania błędów integralności przy ładowaniu MD5 jest więcej niż wystarczające. Co z tego wynikło? Przechodzimy do poprzedniego etapu znacznie szybciej (dzięki prostszemu haszowaniu) i otworzyliśmy następny:

...
   Weryfikacja integralności hasha ... md5+ OK
   Ładowanie plików z 0x90a5a758 do 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
wybrane {
        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() zwrócił FDT_ERR_NOTFOUND
wybrane {
        linux,initrd-end = ;
        linux,initrd-start = ;
        riscv,kernel-end = ;
        riscv,kernel-start = ;
        bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
};
   Ładowanie obrazu jądra ... OK
Uruchamianie jądra za
3

Tylko zegary nie tykają...

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

Ups, wydaje się, że regulacja zegara okazała się placebo, choć wtedy wydawało mi się, że pomogło. Nie, oczywiście trzeba to naprawić, ale na początek przekręćmy wskazówki ręcznie i zobaczmy, co z tego wyniknie:

0x00000000bff6dbb0 w ?? ()
(gdb) set variable *0x0200bff8=1000000
(gdb) c
Kontynuacja.
^C
Program otrzymał sygnał SIGINT, Przerwanie.
0x00000000bff6dbb0 w ?? ()
(gdb) set variable *0x0200bff8=2000000
(gdb) c
Kontynuacja.
^C
Program otrzymał sygnał SIGINT, Przerwanie.
0x00000000bff6dbb0 w ?? ()
(gdb) set variable *0x0200bff8=3000000
(gdb) c
Kontynuacja.

W międzyczasie...

   Ładowanie obrazu jądra ... OK
Uruchamianie jądra za
3
2
1
0
## Rozpoczęcie aplikacji w 0x80000000 ...

Nie, idę zautomatyzować regulację zegara — bo może tam chce skalibrować timer!

A adres bieżącej instrukcji wskazuje w międzyczasie gdzieś na

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

w środku załadowanego Berkeley Boot Loader. Osobiście niepokoi mnie to wzmianka htif — interfejs hosta, wykorzystywany do tethered-uruchamiania jądra (czyli współpracy z hostującą ARM), myślałem, że to samodzielne. Jednak jeśli znajdę tę funkcję w źródłach, to widać, że nie jest tak źle:

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

Zadanie: uruchom zegar

Szukając rejestrów w CLINT, natrafiamy na

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

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

Który jest podłączony do RTC lub do tajemniczego MockAON, o którym na początku pomyślałem: „Co to za sprawy? Nierozumiałe? Odłączamy!” Gdyż do tej pory nie rozumiem, co to za taktowanie tam dzieje się, więc po prostu przedefiniuję tę logikę w System.scala:

  val rtcDivider = RegInit(0.asUInt(16.W)) // dla pewności, obsłużę do 16GHz, jestem optymistą :)
  val mhzInt = p(DevKitFPGAFrequencyKey).toInt
  // Zakładamy, że częstotliwość wynosi całkowitą liczbę megaherców
  rtcDivider := Mux(rtcDivider === (mhzInt - 1).U, 0.U, rtcDivider + 1.U)
  outer.clintOpt.foreach { clint =>
    clint.module.io.rtcTick := rtcDivider === 0.U
  }

Przemierzając w kierunku jądra Linux

Opowieść się już przedłużyła i stała nieco jednostajna, więc opiszę to powierzchownie:

BBL zakładał obecność FDT pod adresem 0xF0000000, ale już to poprawiłem! Cóż, poszukajmy jeszcze… Znaleziono w HiFive_U-Boot/arch/riscv/lib/boot.c, zmienione na 0x81F00000, które zostało wskazane w konfiguracji uruchamiania U-Boot.

Potem BBL narzekał, że brakuje pamięci. Moja droga wiodła do funkcji mem_prop, co w riscv-pk/machine/fdt.c: stąd dowiedziałem się, że muszę oznaczyć węzeł fdt ram jako device_type = "memory" — potem, być może, trzeba będzie poprawić generator procesora, ale na razie wpisałem ręcznie — i tak ten plik przenosiłem ręcznie.

Teraz otrzymałem komunikat (przedstawiony w sformatowanej formie, z powrotami karetki):

To jest dummy_payload bbl. Aby uruchomić prawdziwe jądro, przeconfigure bbl z flagą --with-payload=PATH, a następnie zbuduj bbl ponownie. Alternatywnie, bbl może być używany w trybie tylko firmware’owym, dodając węzły drzewa urządzeń dla zewnętrznego ładunku i używając opcji -bios oraz -kernel QEMU.

Wygląda na to, że opcje są podane prawidłowo riscv,kernel-start i riscv,kernel-end w DTB, ale analizują zera. Debugowanie query_chosen pokazało, że BBL próbował analizować 32-bitowy adres, a natrafił na parę <0x0 0xADDR>, a pierwsza wartość wydaje się być w niższych rzędach. Dodałem do sekcji chosen

chosen {
      #address-cells = ;
      #size-cells = ;
      ...
}

i poprawiłem generację wartości: nie dodawać jako pierwszy element. 0x0 Te 100500 prostych kroków pozwoli łatwo i prosto zobaczyć, jak pada pingwin:

Ukryty tekst

Ukryty tekst

   Weryfikacja integralności hasha ... md5+ OK
   Ładowanie danych z 0x90a5a758 do 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
wybrany {
        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() zwrócił FDT_ERR_NOTFOUND
wybrany {
        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"; 
};
   Ładowanie obrazu jądra ... OK
Uruchamianie jądra za
3
2
1
0
## Rozpoczynanie aplikacji pod adresem 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           555555           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: Ignorowanie zakresu pamięci 0x80000000 - 0x80200000
[    0.000000] Wersja systemu Linux 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc version 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99)) #1 SMP Śr Lip 3 21:29:21 MSK 2019
[    0.000000] bootconsole [early0] włączone
[    0.000000] Początkowy ramdisk w: 0x(____ptrval____) (16777216 bajtów)
[    0.000000] Zakresy stref:
[    0.000000]   DMA32    [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000]   Normal   [mem 0x00000000c0000000-0x00000bffffffffff]
[    0.000000] Początek strefy przenośnej dla każdego węzła
[    0.000000] Wczesne zakresy pamięci węzłów
[    0.000000]   węzeł   0: [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000] Inicjalizacja pamięci węzła 0 [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000] W węźle 0 całkowita liczba stron: 261632
[    0.000000]   Strefa DMA32: 3577 stron użytych do memmap
[    0.000000]   Strefa DMA32: 0 stron zarezerwowanych
[    0.000000]   Strefa DMA32: 261632 stron, LIFO batch:63
[    0.000000] programowe IO TLB: zmapowane [mem 0xbb1fc000-0xbf1fc000] (64MB)

(BBL wyświetla emblem, a to co z znacznikami czasu - jądro).

Na szczęście, nie wiem jak wszędzie, ale na RocketChip przy podłączeniu debuggera przez JTAG można przechwycić pułapki z pudełka - debugger zatrzyma się dokładnie w tym punkcie.

Program otrzymał sygnał SIGTRAP, pułapka śledzenia/kolizji.
0xffffffe0000024ca in ?? ()
(gdb) bt
#0  0xffffffe0000024ca in ?? ()
Backtrace stopped: poprzednia klatka identyczna z tą klatką (uszkodzona stos?)
(gdb) file work/linux/vmlinux
Program jest już debugowany.
Czy na pewno chcesz zmienić plik? (y lub n) y
Czytanie symboli z work/linux/vmlinux...zrobione.
(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: klatka nie zapisała 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); // < JESTEŚ TUTAJ
}

Jak mówi stary dowcip, CPU nie znaleziono, uruchamiam emulację programową. A może wcale nie uruchamiam. Zgubiliśmy się w pojedynczym rdzeniu procesora.

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

Dobry komentarz w linux/arch/riscv/kernel/setup.c — taka farba na płot metodą Toma Sawyera. W każdym razie, dzisiaj jakoś nie ma zwycięzców, nagroda zostaje przeniesiona na następny los.

Na tym proponuję zakończyć i tak już przeciągnięty artykuł.

Ciąg dalszy nastąpi. Będzie w nim walka z przebiegłym błędem, który zdążył się schować, jeśli powoli się do niego podchodzi singlestepem.

Tekstowy skrin-kast ładowania (link zewnętrzny):
Część 3: Prawie ładujemy Linux z karty SD na RocketChip

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster