Partea 3: Aproape că încarcăm Linux de pe cardul SD pe RocketChip

Partea 3: Aproape că încarcăm Linux de pe cardul SD pe RocketChip În partea anterioară a fost realizat un controler de memorie mai mult sau mai puțin funcțional, mai precis - un wrapper peste IP Core din Quartus, care servește ca un adaptor pentru TileLink. Astăzi, în rubrica „Portăm RocketChip pe o placă chineză puțin cunoscută cu Cyclone”, veți vedea o consolă funcțională. Procesul s-a întins puțin: deja mă gândeam că acum voi lansa rapid Linux și vom continua, dar nu a fost așa. În această parte, vă propun să privim procesul de pornire a U-Boot, BBL și încercările timid ale kernel-ului Linux de a se inițializa. Dar există o consolă - cea de la U-Boot, și destul de avansată, având multe din ceea ce ați aștepta de la o consolă completă.

În partea hardware, se va adăuga un card SD, conectat prin interfața SPI, precum și UART. În partea software, BootROM va fi înlocuit cu xip pe sdboot și, în esență, vor fi adăugate următoarele etape de încărcare (pe cardul SD).

Îmbunătățirea părții hardware

Deci, sarcina: trebuie să trecem la un nucleu „mare” și să conectăm UART (de la Raspberry) și adaptorul SD (s-a folosit o placă de la Catalex cu șase pini: GND, VCC, MISO, MOSI, SCK, CS).

În principiu, totul a fost destul de simplu. Dar înainte de a realiza asta, am fost puțin zbuciumat: după ultima dată, am decis că trebuie doar să adaug în System ceva de genul HasPeripheryUART (și în implementare, respectiv), la fel pentru cardul SD - și totul va fi gata. Apoi am decis să văd cum este implementat în un design „serios”. Deci, ce avem aici dintr-un design serios? Arty, se pare, nu se potrivește - rămâne monstrul unleahshed.DevKitConfigs. Și dintr-o dată am descoperit că sunt peste tot niște overlay-uri, care sunt adăugate prin parametri pe chei. Mi-am dat seama că este, probabil, foarte flexibil și configurabil, dar aș avea nevoie de ceva pentru a lansa… Dar nu aveți ceva similar, doar mai simplu și mai rudimentar?.. Aici am dat peste vera.iofpga.FPGAChip pentru FPGA Microsemi și imediat am luat câteva citate și am încercat să fac implementarea mea prin analogie, din fericire aici este mai mult sau mai puțin întreaga „distribuție a plăcii de sistem” într-un singur fișier.

Se pare, într-adevăr, trebuie doar să adaug în 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 din corpul clasei System adaugă informații despre frecvența la care funcționează această parte a SoC-ului nostru în fișierul dts. Din câte înțeleg, DTS/DTB este un fel de echivalent static al tehnologiei plug-and-play pentru dispozitive încorporate: arborele de descriere dts este compilat într-un fișier dtb binar și transmis bootloader-ului nucleului, astfel încât să poată configura corect hardware-ul. Ce este interesant este că, fără linia cu tlclock totul se sintetizează perfect, dar nu este posibil să compilezi BootROM (îmi amintesc că acum va fi deja sdboot) — în procesul de compilare, acesta parsează fișierul dts și creează un header cu macro-ul TL_CLK, datorită căruia va putea configura corect divizorii de frecvență pentru interfețele externe.

De asemenea, va fi necesar să ajustăm puțin „configurația”:

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
}

Lanturile de registre, sincer, au fost adăugate pur și simplu prin analogie cu unele alte locuri din codul original. Cel mai probabil, acestea ar trebui să protejeze împotriva metastabilității. Poate că în unele blocuri există deja propria protecție, dar pentru început aș dori să pornesc măcar „la un nivel de calitate”. O întrebare mai interesantă pentru mine este de ce MISO și MOSI sunt pe diferite dq? Ответа я пока так и не нашёл, но, похоже, остальной код рассчитывает именно на такое подключение.

Din punct de vedere fizic, pur și simplu am atribuit ieșirile designului pe contactele libere de pe conector și am mutat jumperul de selectare a tensiunii la 3.3V.

Adaptator SD

Vedere de sus:

Partea 3: Aproape că încarcăm Linux de pe cardul SD pe RocketChip

Vedere de jos:

Partea 3: Aproape că încarcăm Linux de pe cardul SD pe RocketChip

Debugging-ul părții software: instrumente

Mai întâi, să discutăm despre instrumentele de depanare disponibile și limitările acestora.

Minicom

În primul rând, va trebui să citim ceea ce trimite bootloader-ul și nucleul. Pentru aceasta, pe Linux (în acest caz — pe cel de pe RaspberryPi) ne va trebui programul Minicom. De fapt, orice program pentru lucrul cu portul serial va funcționa.

Rețineți că, la pornire, numele dispozitivului portului trebuie specificat ca -D \/dev\/ttyS0 — după opțiunea -D. Și informația principală: pentru a ieși, folosiți Ctrl-A, X. De fapt, am avut o situație în care această combinație nu a funcționat — atunci poți să îi spui dintr-o sesiune SSH vecină killall -KILL minicom.

Există și o altă caracteristică. Anume, pe Raspberry Pi sunt două UART-uri, iar ambele porturi pot fi deja folosite pentru ceva: unul pentru Bluetooth, prin care, în mod implicit, este redat consola nucleului. Din fericire, acest comportament poate fi reconfigurat. după acest manual.

Recriptarea memoriei

În timpul depanării, pentru a verifica ipoteza, uneori a trebuit să încarc bootloader-ul (îmi pare rău) în memoria RAM direct de pe gazdă. Poate că se poate face direct din GDB, dar în cele din urmă am ales calea simplă: am copiat fișierul necesar pe Raspberry, am redirecționat și portul 4444 prin SSH (telnet de la OpenOCD) și am folosit comanda load_image. Când o executați, pare că totul s-a blocat, dar de fapt „nu dormează, doar clipește încet”: încarcă fișierul, doar că o face cu o viteză de câțiva kilobiți pe secundă.

Particularitățile setării breakpoint-urilor

Probabil că mulți nu s-au gândit la asta atunci când depanează programe normale, dar punctele de oprire nu sunt întotdeauna setate hardware. Uneori, setarea unui breakpoint constă în scrierea temporară a unei instrucțiuni speciale într-un loc dorit direct în codul mașină.De exemplu, așa a funcționat comanda standard b în GDB. Iată ce rezultă din asta:

  • nu se poate seta un punct în interiorul BootROM-ului, deoarece ROM-ul
  • se poate seta un punct de oprire pe codul încărcat în RAM de pe cardul SD, dar trebuie să așteptați până când este încărcat. Altfel, nu vom rescrie o bucată de cod, ci bootloader-ul va rescrie breakpoint-ul nostru.

Sunt sigur că se poate solicita explicit utilizarea punctelor de oprire hardware, dar oricum sunt într-un număr limitat.

Suprascriere rapidă a BootROM-ului

În etapa inițială a depanării, apare adesea dorința de a corecta BootROM și de a încerca din nou. Dar există o problemă: BootROM-ul este parte a designului, încărcat în FPGA, iar sinteza acestuia durează câteva minute (și asta după o compunere aproape instantanee a imaginii BootROM din C și Assembler…). Din fericire, de fapt, totul este mult mai rapid: secvența de acțiuni este următoarea:

  • regenerarea bootrom.mif (am trecut pe MIF în loc de HEX, deoarece aveam mereu probleme cu HEX, iar MIF este formatul nativ Altera)
  • în Quartus spun Processing -> Update Memory Initialization File
  • la punctul Assembler (în coloana din stânga Tasks) comandați Start again

Totul durează câteva zeci de secunde.

Pregătirea cardului SD

Aici totul este relativ simplu, dar trebuie să ai răbdare și aproximativ 14GB de spațiu pe disk:

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

După care trebuie să introduci un card SD gol, adică fără nimic necesar, și să executați

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

… unde sdX — este dispozitivul asociat cardului. ATENȚIE: datele de pe card vor fi șterse, rescrise și așa mai departe! Probabil că nu are sens să faci întreaga construcție din sudo, pentru că atunci toate artefactele construcției vor aparține root, iar construcția va trebui să se facă din sudo în mod constant.

În rezultat, se obține un card partitionat în GPT cu patru secțiuni, una dintre care este FAT cu uEnv.txt și o imagine de boot în format FIT (acesta conține mai multe subimagini, fiecare cu propriul său adresă de boot), iar cealaltă secțiune — curată, se presupune că va fi formatată în Ext4 pentru Linux. Încă două secțiuni — misterioase: într-una se află U-Boot (offset-ul său, din câte înțeleg, este încorporat în BootROM), iar cealaltă, pare că găzduiește variabilele sale de mediu, dar eu până acum nu le folosesc.

Nivelul unu, BootROM

Înțelepciunea populară spune: „Dacă programarea are jocuri cu tamburul, în electronică mai sunt și cu extinctoarele.” Nu este vorba că odată am fost pe punctul de a arde placa, gândindu-mă că „Ei bine, GND este același nivel inferior” (probabil, un rezistor totuși nu ar fi stricat…) Vorba este mai degrabă că, dacă mâinile nu vin de unde trebuie, electronica continuă să aducă surprize: lipind un conector pe placă, nu am reușit să lipesc corect contacte — în video arată cum cositorul se întinde singur pe toată legătura, doar aplici fierul de lipit, în schimb, la mine „s-a împrăștiat” cum a vrut el. Ei bine, s-ar putea să nu am folosit cositorul potrivit pentru temperatura fierului, poate altceva… În general, văzând că am deja o duzină de contacte, am renunțat și am început să debuguez. Și atunci a început misterios: am conectat RX/TX de la UART, încarc firmware-ul — scrie

INIT
CMD0
ERROR

Ei bine, totul este logic — modulul SD nu l-am conectat. Corectăm situația, încărcăm firmware-ul… Și liniște… Ce n-am gândit, iar lădița se deschidea simplu: unul dintre pini trebuia conectat la VCC. În cazul meu, modulul suporta 5V pentru alimentare, așa că, fără să stau pe gânduri, am băgat firul, care venea de la modul, pe partea opusă a plăcii. În rezultat, conectorul lipit prost s-a deformat și am pierdut contactul UART. facepalm.jpg În general, „o cap prost nu dă odihnă picioarelor”, iar mâinile neîndemânatice — minții...

În cele din urmă, am văzut în Minicom așteptatul

INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING /

Mai mult, indicatorul de încărcare se mișcă. Îmi amintesc de anii de școală și de încărcarea lentă a MinuetOS de pe dischetă. Numai că unitatea de dischet nu scârțâie.

Problema este că după mesajul BOOT nu se întâmplă nimic. Înseamnă că este momentul să ne conectăm prin OpenOCD la Raspberry, cu GDB pe gazdă, și să vedem ce este aceasta.

În primul rând, conectarea cu GDB a arătat imediat că $pc (program counter, adresa instrucțiunii curente) se duce în 0x0 — probabil, se întâmplă după o eroare multiplă. De aceea, imediat după emiterea mesajului BOOT să adăugăm un ciclu infinit. Aceasta îl va întârzia puțin...

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

Asemenea coduri inteligente sunt folosite „pentru fiabilitate”: am auzit undeva că, se pare, un ciclu infinit este un Comportament Nedefinit, iar compilatorul cu siguranță nu va deduce asta (Îți reamintesc că 0x10000 se află BootROM).

Partea 3: Aproape că încarcăm Linux de pe cardul SD pe RocketChip

Ce altceva ne-am putea aștepta — este un embedded dur, ce coduri sursă mai pot exista? Dar în acel articol autorul a depanat codul C... Krex-fex-pex:

(gdb) file builds/zeowaa-e115/sdboot.elf
Un program este deja în curs de depanare.
Ești sigur că dorești să schimbi fișierul? (y sau n) y
Citind simbolurile din builds/zeowaa-e115/sdboot.elf... gata.

Partea 3: Aproape că încarcăm Linux de pe cardul SD pe RocketChip

Doar că trebuie să încarci nu un fișier MIF și nu bin, ci varianta originală în format ELF.

Acum poți ghici, la a n-a încercare, adresa unde va continua execuția (aceasta este încă o altă motivație pentru care compilatorul nu ar fi trebuit să-și deducă că ciclul este infinit). Comanda

set variable $pc=0xADDR

permete schimbarea valorii registrului on the fly (în acest caz — adresa instrucțiunii curente). Cu ajutorul acesteia, poți modifica valorile înregistrate în memorie (și registrele mapate în memorie).

În cele din urmă, am ajuns la concluzia (nu sunt sigur că este corectă) că avem „imaginea sd-card-ului unei alte sisteme”, iar trecerea ar trebui să se facă nu la începutul datelor încărcate, ci cu 0x89800 un byte mai departe:

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

Se poate ca acest lucru să fi fost influențat de faptul că, neavând la îndemână un card inutil de 4 Gb, am luat unul de 2 Gb și, prin metoda încercării, am înlocuit în Makefile. DEMO_END=11718750 pe DEMO_END=3078900 (nu căutați sensul într-o semnificație specifică — nu există, pur și simplu acum imaginea se încadrează pe card).

Nivelul doi, U-Boot

Acum încă «cădem», dar ajungem deja la adresa 0x0000000080089a84. Aici trebuie să recunosc: de fapt, prezentarea nu merge «cu toate opririle», ci este scrisă parțial «după», așa că aici am reușit deja să pun fișierul dtb corect de la SoC-ul nostru, să corectez în setări. HiFive_U-Boot . Observați că versiunea din fișier CONFIG_SYS_TEXT_BASE=0x80089800 (în loc de 0x08000000), pentru ca adresa de încărcare să corespundă cu cea reală. Încărcăm acum cardul de nivel următor cu o altă imagine:

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

Și vedem:

   │304     
/*                                               │
   │305      * intrare de capcană                                    │
   │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)                      │

De altfel, sărim între liniile 308 și 309. Și nu este surprinzător, având în vedere că în $sp se află valoarea 0xfffffffe31cdc0a0. Din păcate, ea de asemenea «fuge» constant din cauza liniei 307. Așa că vom încerca să setăm un punct de oprire pe trap_entry, apoi să revenim la 0x80089800 (punctul de intrare U-Boot), și să sperăm că nu necesită setarea corectă a registrelor înainte de trecere... Se pare că funcționează:

(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

Un pointer pe stivă destul de slab, să spunem: indică de fapt pe lângă memorie (dacă, desigur, nu avem deja o traducere a adreselor, dar să sperăm la varianta simplă).

Vom încerca să înlocuim pointerul cu 0x881cf950. În final ajungem la concluzia că handle_trap este apelat și apelat, în timp ce ne îndreptăm spre _exit_trap cu argumentul epc=2148315240 (în format zecimal):

(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

Setăm un breakpoint pe strnlen, continuăm și vedem:

(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

Se pare că _exit_trap vrea să furnizeze informații de depanare despre excepția care a avut loc, dar nu reușește. Așadar, din nou nu vedem sursele. set directories ../freedom-u-sdk/HiFive_U-Boot/ O! Acum apar!

Ei bine, să pornim din nou și să vedem prin stiva de apeluri cauza problemei inițiale care a dus la prima eroare (mcause == 5). Dacă am înțeles corect ce e scris aici pe pagina 37, atunci această excepție înseamnă Load access fault. Motivul, aparent, este că aici

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 are aceeași valoare incorectă, iar în interior board_init_f_init_reserve apare o eroare. Se pare că iată și vinovatul: variabila cu un nume sugestiv CONFIG_SYS_INIT_SP_ADDR. Ea este definită în fișierul HiFive_U-Boot/include/configs/HiFive-U540.h. La un moment dat, m-am gândit că poate ar fi mai ușor să ajustez procesorul, în loc să modific încărcătorul. Însă apoi am realizat că acest lucru semăna mai degrabă cu un artifact din setările incomplete pentru o altă configurație de memorie, și am decis să încerc să fac așa:#if 0-e configurări.

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

La un moment dat, numărul de soluții provizorii de fixare tehnologică a atins un prag critic. După ce m-am chinuit puțin, am realizat că e nevoie de un port corect pe placa mea. Pentru aceasta, trebuie să copiez și să modific un anumit număr de fișiere conform configurației noastre.

Ei bine, aproximativ, iată o mică parte

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

    Suport inițial pentru placa 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

Detalii pot fi văzute în repository.

După cum s-a dovedit, pe această placă SiFive, registrele anumitor dispozitive au alte adrese. De asemenea, s-a dovedit că U-Boot este configurat prin mecanismul familiar din kernelul Linux, Kconfig — de exemplu, pot să fac comanda make menuconfig, și înaintea dumneavoastră va apărea o interfață textuală convenabilă, care arată descrierile parametrilor. ? Și așa mai departe. În general, am combinat descrierile a două plăci pentru a crea descrierea unei a treia, eliminând tot felul de setări pompoase ale PLL (probabil, acest lucru are legătură cu gestionarea de la computerul gazdă prin PCIe, dar nu sunt sigur), și am obținut un fel de firmware, care, la o vreme bună pe Marte, îmi trimitea prin UART un mesaj despre din ce hash de commit a fost construită, precum și informații despre câtă DRAM am (dar aceste informații le-am specificat eu însumi în header).

Îmi pare rău doar că, după aceasta, placa de obicei înceta să răspundă prin JTAG-ul procesorului, iar bootarea de pe cardul SD — din păcate, în configurația mea, nu este rapidă. Pe de altă parte, uneori BootROM-ul emitea un mesaj că EROARE, nu s-a putut încărca, iar U-Boot apărea imediat. Atunci am realizat: se pare că, după restartarea bitstream-ului în FPGA, memoria nu este suprascrisă, nu are timp să se "deconfigurez" etc. În esență, pot pur și simplu să mă conectez cu debuggerul la apariția mesajului ÎNCARCĂRIE / să mă conectez cu debuggerul și să dau comanda set variable $pc=0x80089800, astfel ocolind această încărcare lungă (desigur, presupunând că în ultima dată a eșuat suficient de devreme și nu a reușit să încarce ceva deasupra codului original).

Apropo, este normal ca procesorul să se blocheze total și JTAG debuggerul să nu se poată conecta, cu mesajele

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

Așa, stai puțin! Am mai văzut asta! Ceva similar se întâmplă la deadlock-ul TileLink, iar autorului controller-ului de memorie nu îi am încredere — eu singur am scris... Deodată, după prima recompilare reușită a procesorului după editarea controller-ului, am observat:

INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
ÎNCARCĂRE
BOOT

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

DRAM:  1 GiB
MMC:
ÎNAINTE DE ÎNCĂRCARE ENVÎNAINTE DE CONTROL FDTÎNAINTE DE ÎNCĂRCARE ADDRIn:    serial
Out:   serial
Err:   serial
Apasă orice tastă pentru a opri autoboot:  3

Pe această linie ciudată înainte de In: serial Nu acordați atenție — încercam să văd dacă funcționează corect pe un procesor care se blochează. Ce înseamnă „A stat așa timp de zece minute”? A reușit măcar să se relocateze și să ajungă în meniul de boot! O mică paranteză: deși U-Boot pornește printre primii 2^24 octeți de pe cardul SD, odată activat, se copiază mai departe în memorie, fie la adresa scrisă în header-ul de configurare, fie pur și simplu în adresele superioare ale memoriei RAM, realizează relocarea simbolurilor ELF și predă controlul. Așadar, se pare că acest nivel a fost trecut și ca bonus am primit un procesor care nu se blochează complet după aceea.

Așadar, de ce nu funcționează cronometru? Se pare că ceasul, în principiu, nu merge dintr-un motiv oarecare...

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

Dar ce-ar fi să învârtim indicatoarele manual?

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

Atunci:

Atingeți orice tastă pentru a opri autoboot:  0
MMC_SPI: 0 la 0:1 hz 20000000 mod 0

Concluzie: Ceasul nu funcționează. Probabil din aceeași cauză, nu funcționează nici introducerea de la tastatură:

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:
        /* Primul caracter al secvenței de escape ANSI 'e' */
        if (c == 'e') {
            *esc = 1;
            *key = KEY_NONE;
        }
        break;
    case 1:
        /* Al doilea caracter al ANSI '[' */
        if (c == '[') {
...

Problema a fost că am complicat puțin lucrurile: am adăugat în configurația procesorului cheia:

  case DTSTimebase => BigInt(0)

… ghidându-mă după observația care spunea „dacă nu știți, lăsați 0”. Și de fapt WithNBigCores fixa tocmai la 1MHz (aproape că era menționat și în configurația U-Boot). Dar eu sunt, știți, meticulos și atent: acolo nu știu, aici 25MHz! În cele din urmă, nimic nu funcționează. Am eliminat „îmbunătățirile” mele și...

Atingeți orice tastă pentru a opri autoboot:  0
MMC_SPI: 0 la 0:1 hz 20000000 mod 0
## Tip necunoscut de tabel de partiții 0
libfdt fdt_path_offset() a returnat FDT_ERR_NOTFOUND
** Nu există tabel de partiții - mmc 0 **
## Info: dimensiunea datelor de intrare = 34 = 0x22
Se rulează uEnv.txt boot2...
## Eroare: "boot2" nu este definit

Puteți chiar să introduceți comenzi! De exemplu, după ce ați copt puțin, în sfârșit ați putea ghici să introduceți mmc_spi 1 10000000 0; mmc part, reducând frecvența SPI de la 20MHz la 10MHz. De ce? Ei bine, în configurație era scrisă frecvența maximă de 20MHz, este tot acolo acum. Însă, din câte am înțeles, interfețele, cel puțin aici, funcționează astfel: codul împarte frecvența modulului hardware (în cazul meu — peste tot 25MHz) la cea țintă, și setează valoarea obținută ca divizor în registrul de control corespunzător. Problema este că, dacă pentru UART de 115200Hz va fi aproximativ ceea ce trebuie, atunci dacă împarți direct 25000000 la 20000000 obții 1, adică va funcționa la 25MHz. Poate că este normal, dar dacă se impun limitări, înseamnă că este necesar pentru cineva (dar nu este sigur)… În general, este mai simplu să lămurești și să mergi mai departe — departe și, din păcate, pentru o perioadă lungă. 25MHz — nu este un Core i9.

Ieșirea consolei

HiFive-Unleashed # env edit mmcsetup
edit: mmc_spi 1 10000000 0; mmc part
HiFive-Unleashed # boot
MMC_SPI: 1 at 0:1 hz 10000000 mode 0

Partition Map for MMC device 0  --   Partition Type: EFI

Part    Start LBA       End LBA         Name
        Attributes
        Type GUID
        Partition GUID
  1     0x00000800      0x0000ffde      "Vfat Boot"
        attrs:  0x0000000000000000
        type:   ebd0a0a2-b9e5-4433-87c0-68b6b72699c7
        type:   data
        guid:   76bd71fd-1694-4ff3-8197-bfa81699c2fb
  2     0x00040800      0x002efaf4      "root"
        attrs:  0x0000000000000000
        type:   0fc63daf-8483-4772-8e79-3d69d8477de4
        type:   linux
        guid:   9f3adcc5-440c-4772-b7b7-283124f38bf3
  3     0x0000044c      0x000007e4      "uboot"
        attrs:  0x0000000000000000
        type:   5b193300-fc78-40cd-8002-e86c45580b47
        guid:   bb349257-0694-4e0f-9932-c801b4d76fa3
  4     0x00000400      0x0000044b      "uboot-env"
        attrs:  0x0000000000000000
        type:   a09354ac-cd63-11e8-9aff-70b3d592f0fa
        guid:   4db442d0-2109-435f-b858-be69629e7dbf
libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
2376 bytes read in 0 ms
Running uEnv.txt boot2...
15332118 bytes read in 0 ms
## Loading kernel from FIT Image at 90000000 ...
   Using 'config-1' configuration
   Trying 'bbl' kernel subimage
     Description:  BBL/SBI/riscv-pk
     Type:         Kernel Image
     Compression:  uncompressed
     Data Start:   0x900000d4
     Data Size:    74266 Bytes = 72.5 KiB
     Architecture: RISC-V
     OS:           Linux
     Load Address: 0x80000000
     Entry Point:  0x80000000
     Hash algo:    sha256
     Hash value:   28972571467c4ad0cf08a81d9cf92b9dffc5a7cb2e0cd12fdbb3216cf1f19cbd
   Verifying Hash Integrity ... sha256+ OK
## Loading fdt from FIT Image at 90000000 ...
   Using 'config-1' configuration
   Trying 'fdt' fdt subimage
     Description:  unavailable
     Type:         Flat Device Tree
     Compression:  uncompressed
     Data Start:   0x90e9d31c
     Data Size:    6911 Bytes = 6.7 KiB
     Architecture: RISC-V
     Load Address: 0x81f00000
     Hash algo:    sha256
     Hash value:   10b0244a5a9205357772ea1c4e135a4f882409262176d8c7191238cff65bb3a8
   Verifying Hash Integrity ... sha256+ OK
   Loading fdt from 0x90e9d31c to 0x81f00000
   Booting using the fdt blob at 0x81f00000
## Loading loadables from FIT Image at 90000000 ...
   Trying 'kernel' loadables subimage
     Description:  Linux kernel
     Type:         Kernel Image
     Compression:  uncompressed
     Data Start:   0x900123e8
     Data Size:    10781356 Bytes = 10.3 MiB
     Architecture: RISC-V
     OS:           Linux
     Load Address: 0x80200000
     Entry Point:  unavailable
     Hash algo:    sha256
     Hash value:   72a9847164f4efb2ac9bae736f86efe7e3772ab1f01ae275e427e2a5389c84f0
   Verifying Hash Integrity ... sha256+ OK
   Loading loadables from 0x900123e8 to 0x80200000
## Loading loadables from FIT Image at 90000000 ...
   Trying 'ramdisk' loadables subimage
     Description:  buildroot initramfs
     Type:         RAMDisk Image
     Compression:  gzip compressed
     Data Start:   0x90a5a780
     Data Size:    4467411 Bytes = 4.3 MiB
     Architecture: RISC-V
     OS:           Linux
     Load Address: 0x82000000
     Entry Point:  unavailable
     Hash algo:    sha256
     Hash value:   883dfd33ca047e3ac10d5667ffdef7b8005cac58b95055c2c2beda44bec49bd0
   Verifying Hash Integrity ... sha256+ OK
   Loading loadables from 0x90a5a780 to 0x82000000

Ok, am trecut la un nou nivel, dar tot mai rămâne blocat. Și uneori dă și excepții. Poți vedea mcause, prins codul la adresa specificată $pc și după si a ajunge la trap_entry. Procesorul U-Boot poate să afișeze doar pentru mcause = 0..4, așa că pregătiți-vă să rămâneți în buclă din cauza încărcării incorecte. M-am uitat în configurație, am început să văd ce am schimbat și mi-am amintit: acolo în conf/rvboot-fit.txt Compania Finservice își desfășoară activitatea din 2012 și este un broker financiar independent de frunte în domeniul creditării POS pe piața din Rusia. În prezent, Finservice face parte dintr-un mare holding multifuncțional, având peste 200 de angajați și desfășurând o expansiune activă în toate regiunile Rusiei.

fitfile=image.fit
# mai jos trebuie să se potrivească cu ceea ce este în FIT (ugha)

Ei bine, să aducem toate fișierele la conformitate, vom înlocui linia de comandă a kernelului aproximativ așa, deoarece există suspiciuni că SIF0 — aceasta este ieșirea undeva pe PCIe:

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

Și, în plus, vom schimba algoritmul de hashing de la SHA-256 la MD5: nu am nevoie de criptografie puternică (cu atât mai mult în timpul depanării), este considerat extrem de lent, iar pentru monitorizarea erorilor de integritate în timpul încărcării, MD5 este mai mult decât suficient. Ce avem în final? Am trecut la nivelul anterior mult mai repede (datorită hashing-ului mai simplu) și s-a deschis următorul:

...
   Verificare integritate hash ... md5+ OK
   Încărcare fișiere de la 0x90a5a758 la 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() a returnat 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";
};
   Încărcare imagine kernel ... OK
Inițializare kernel în
3

Dar ceasul nu ticăie...

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

Ups, se pare că repararea cursului ceasului a fost un placebo, deși atunci mi-a părut că a ajutat. Nu, trebuie să repar, dar haideți mai întâi să învârtim acele manual și să vedem ce iese:

0x00000000bff6dbb0 în ?? ()
(gdb) set variable *0x0200bff8=1000000
(gdb) c
Continuând.
^C
Programul a primit semnalul SIGINT, Interrupție.
0x00000000bff6dbb0 în ?? ()
(gdb) set variable *0x0200bff8=2000000
(gdb) c
Continuând.
^C
Programul a primit semnalul SIGINT, Interrupție.
0x00000000bff6dbb0 în ?? ()
(gdb) set variable *0x0200bff8=3000000
(gdb) c
Continuând.

Între timp...

   Încărcare imagine kernel ... OK
Inițializare kernel în
3
2
1
0
## Începere aplicație la 0x80000000 ...

Nu, mă duc să automatizez cursul ceasului — s-ar putea să decidă să calibreze un timer acolo!

Iar adresa instrucțiunii curente indică între timp undeva în

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

în interiorul Berkeley Boot Loader-ului care s-a încărcat. Mă deranjează mențiunea htif — interfața gazdă, folosită pentru pornirea legată a nucleului (adică în colaborare cu ARM-ul gazdă), eu am presupus că este standalone. Totuși, dacă găsesc această funcție în surse, se vede că nu e chiar atât de rău:

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

Crestere: pornește ceasurile

Căutarea registrelor în CLINT ne duce la

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

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

Care se conectează la RTC, sau în misteriosul MockAON, despre care am judecat inițial: „Deci, ce avem aici? Neprecizat? Deconectăm!” Deoarece nu înțeleg încă ce se întâmplă cu acea magie a ceasurilor, așa că voi reimplementa această logică în System.scala:

  val rtcDivider = RegInit(0.asUInt(16.W)) // pentru orice eventualitate voi susține până la 16GHz, sunt optimist :)
  val mhzInt = p(DevKitFPGAFrequencyKey).toInt
  // Să presupunem că frecvența este un număr întreg de megahertzi
  rtcDivider := Mux(rtcDivider === (mhzInt - 1).U, 0.U, rtcDivider + 1.U)
  outer.clintOpt.foreach { clint =>
    clint.module.io.rtcTick := rtcDivider === 0.U
  }

Mergând către kernelul Linux

Aici narațiunea deja s-a întins și a devenit puțin monotonă, așa că voi descrie pe scurt:

BBL presupunea existența FDT la adresa 0xF0000000, dar am corectat deja! Ei bine, să căutăm în continuare… Am găsit în HiFive_U-Boot/arch/riscv/lib/boot.c, am înlocuit cu 0x81F00000, specificat în configurația de încărcare U-Boot.

Apoi, BBL s-a plâns că nu era memorie. Drumul meu m-a dus la funcția mem_prop, care se află în riscv-pk/machine/fdt.c: de acolo am aflat că trebuie să marchez nodul fdt ram ca device_type = "memory" — apoi, poate va trebui să corectez generatorul procesorului, dar deocamdată voi scrie manual — oricum am transferat acest fișier manual.

Acum am primit un mesaj (prezentat în formatat, cu reveniri de linie):

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.

Se pare că opțiunile sunt specificate corect riscv,kernel-start și riscv,kernel-end în DTB, dar zero-urile sunt analizate. Debugging query_chosen a arătat că BBL încearcă să analizeze o adresă pe 32 biți, iar este întâlnită o pereche <0x0 0xADDR>, iar primul valor, se pare, este cel mai puțin semnificativ. Am adăugat în secțiunea chosen

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

și am corectat generarea valorilor: să nu adaugă 0x0 ca prim element.

Aceste 100500 pași simpli vă vor permite să observați cu ușurință cum cade pingvinuș:

Text ascuns

   Verificarea integrității hash-ului ... md5+ OK
   Încărcarea componentelor de la 0x90a5a758 la 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
ales {
        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() a returnat FDT_ERR_NOTFOUND
ales {
        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";
};
   Încărcarea imaginii kernel-ului ... OK
Booting kernel în
3
2
1
0
## Începerea aplicației la 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: Ignorând intervalul de memorie 0x80000000 - 0x80200000
[    0.000000] Versiunea Linux 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc version 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99)) #1 SMP Mie Iul 3 21:29:21 MSK 2019
[    0.000000] bootconsole [early0] activat
[    0.000000] Ramdisk inițial la: 0x(____ptrval____) (16777216 bytes)
[    0.000000] Intervalele de zonă:
[    0.000000]   DMA32    [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000]   Normal   [mem 0x00000000c0000000-0x00000bffffffffff]
[    0.000000] Zona mobilă de start pentru fiecare nod
[    0.000000] Intervalele de memorie timpurie
[    0.000000]   nod 0: [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000] Setarea memoriei inițiale nod 0 [mem 0x0000000080200000-0x00000000bfffffff]
[    0.000000] Pe nodul 0 totalpages: 261632
[    0.000000]   zona DMA32: 3577 pagini utilizate pentru memmap
[    0.000000]   zona DMA32: 0 pagini rezervate
[    0.000000]   zona DMA32: 261632 pagini, LIFO batch:63
[    0.000000] software IO TLB: mapat [mem 0xbb1fc000-0xbf1fc000] (64MB)

(BBL afișează emblema, iar ceea ce este cu mărci de timp — kernel).

Din fericire, nu știu cum e peste tot, dar la RocketChip, când conectezi un debugger prin JTAG, poți prinde transferuri din cutie — debuggerul se va opri exact în acel punct.

Programul a primit semnal SIGTRAP, Transfer/depunere de punct.
0xffffffe0000024ca in ?? ()
(gdb) bt
#0  0xffffffe0000024ca in ?? ()
Backtrace oprit: cadrul anterior identic cu acest cadru (stivă coruptă?)
(gdb) file work/linux/vmlinux
Un program este deja în desfășurare.
Ești sigur că vrei să schimbi fișierul? (y sau n) y
Citesc simbolurile din work/linux/vmlinux...gata.
(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 oprit: cadrul nu a salvat PC-ul

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); // < SUNTEȚI AICI
}

Așa cum se spunea în vechiul banc, CPU nu a fost găsit, se execută emulare software. Sau poate nu se execută. Ne-am pierdut în nucleul unic al procesorului.

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

Un comentariu bun în linux/arch/riscv/kernel/setup.c — o adevărată vopsire a gardului în metoda lui Tom Sawyer. În general, astăzi nu s-au găsit câștigători, premiul se amână pentru următorul tiraj…

Pe aceasta propun să încheiem articolul care s-a lungit deja.

Continuarea urmează. În ea va fi o luptă cu o eroare iscusită, care reușește să se ascundă dacă te aproprie încet cu singlestep.

Textul screencast-ului de încărcare (link extern):
Partea 3: Aproape că încarcăm Linux de pe cardul SD pe RocketChip

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster