În 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 . 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:

Vedere de jos:

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. .
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
makeDupă 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
ERROREi 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).

Ce altceva ne-am putea aștepta — este un embedded dur, ce coduri sursă mai pot exista? Dar 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.
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=0xADDRpermete 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 .rodataSe 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 = 0x81cf950Un 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,0Setă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 PCSe 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 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 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.hDetalii pot fi văzute în .
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 =0x00030c82Aș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: 3Pe 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: 0x00000000Dar ce-ar fi să învârtim indicatoarele manual?
(gdb) set variable *0x0200bff8=310000000
(gdb) cAtunci:
Atingeți orice tastă pentru a opri autoboot: 0
MMC_SPI: 0 la 0:1 hz 20000000 mod 0Concluzie: 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 definitPuteț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 0x82000000Ok, 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
3Dar ceasul nu ticăie...
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000Ups, 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-ulfreedom-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):
Sursa: habr.com
