В беше реализиран по-или-мене работещ контролер за памет, а по-точно — обвивка над IP Core от Quartus, която служи за преходник на TileLink. Днес в рубриката „Преносим RocketChip на малко известна китайска платка с Циклон“ ще видите работеща конзола. Процесът малко се проточи: вече си мислех, че ще стартирам Linux бързо и ще продължим, но не стана така. В тази част предлагам да разгледате процеса на стартиране на U-Boot, BBL и скромните опити на Linux kernel да се инициализира. Но имаме конзола — U-Boot-овска, и доста напреднала, имаща много от това, което очаквате от пълноценна конзола.
В хардуерната част ще добавим SD-карта, свързана през интерфейса SPI, а също така и UART. В софтуерната част BootROM ще бъде заменен с xip на sdboot и, всъщност, ще бъдат добавени следните етапи на зареждане (на SD-картата).
Допълване на хардуерната част
И така, задачата е: нужно е да преминем на „голямо“ ядро и да свържем UART (от Raspberry) и SD-адаптер (използваше се някаква платка от Catalex с шест пина: GND, VCC, MISO, MOSI, SCK, CS).
По принцип, всичко беше доста просто. Но преди да осъзная това, малко ме разклати насам-натам: след предишния път реших, че отново трябва просто да добавя в Система нещо като HasPeripheryUART (и в съответната реализация), същото за SD-картата — и всичко ще е готово. После реших да погледна как е реализирано в „сериозен“ дизайн. Така, какво имаме тук от сериозни неща? Arty, явно, не е подходящ — остава монстърът unleahshed.DevKitConfigs. И изведнъж открих, че навсякъде има някакви overlay-и, които се добавят чрез параметри по ключове. Подозирам, че това, вероятно, е много гъвкаво и конфигурируемо, но ми трябва нещо, с което да стартирам... Няма ли такова нещо, само че по-просто и на по-ниско ниво?.. В тук се натъкнах на vera.iofpga.FPGAChip за ПЛИС Microsemi и веднага взех на цитати да опитам да направя своя реализация по аналогия, понеже тук почти всичко „разположение на системната платка“ е в един файл.
Оказа се, че наистина е нужно просто да добавя в System.scala строчките
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
...Строчка в тялото на класа Система добавя информация за честотата, на която работи тази част от нашия SoC, в dts файла. Насколько разбирам, DTS/DTB — това е статичен аналог на технологията plug-and-play за вградени устройства: дървото dts описание се компилира в бинарен dtb файл и се предава на bootloader-a на ядрото, за да може то да настрои правилно хардуера. Интересно е, че без реда с tlclock всичко прекрасно се синхронизира, но не може да се компилира BootROM (напомням, че сега това ще бъде sdboot) — по време на компилация той парси dts файла и създава хедер с макроса TL_CLK, благодарение на който ще може да настрои коректно делителите на честота за външните интерфейси.
Също така ще се наложи малко да поправим „разводката“:
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 карта
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
}Цепочките от регистри, честно казано, бяха добавени просто по аналогия с някои други места от оригиналния код. Вероятно те трябва да предпазват от . Възможно е в някои блокове вече да има собствена защита, но за начало искам да стартирам поне „на качествено ниво“. По-интересният за мен въпрос е — защо MISO и MOSI висят на различни dq? Ответа я пока так и не нашёл, но, похоже, остальной код рассчитывает именно на такое подключение.
Физически, просто назначих изводите на дизайна на свободни контакти на колодката и преместих джъмпера за избора на напрежение на 3.3V.
SD адаптер
Изглед отгоре:

Изглед отдолу:

Отстраняване на проблеми в софтуерната част: инструменти
Първо, нека поговорим за наличните инструменти за отстраняване на проблеми и техните ограничения.
Minicom
На първо място, ще ни трябва начин да прочетем това, което изписва bootloader-ът и ядрото. За това на Linux (в случая — на този на RaspberryPi) ще ни е нужна програмата Minicom. Всъщност, става каквато и да е програма за работа с сериен порт.
Обърнете внимание, че при стартиране името на устройството на порта трябва да се укаже като -D \/dev\/ttyS0 — след опцията -D. А, главната информация: за изход използвайте Ctrl-A, X. Имам наистина случай, когато тази комбинация не сработи — тогава може да кажете от съседна SSH сесия просто killall -KILL minicom.
Има и още една особеност. Конкретно на RaspberryPi има два UART, и и двата порта могат да бъдат вече предназначени за нещо: един за Bluetooth, а през другия по подразбиране се извежда конзолата на ядрото. За щастие, това поведение може да бъде пренастроено .
Презаписване на паметта
При отстраняване на проблеми, за проверка на хипотезата ми понякога се налагаше да заредя заредителя (извинете) в оперативната памет директно от хоста. Може би, това може да се направи направо от GDB, но в крайна сметка последвах простия път: копирах необходимия файл на Raspberry, пробросих и порта 4444 (telnet от OpenOCD) и използвах командата load_image. Когато я изпълните, изглежда, че всичко е замръзнало, но всъщност «то не спи, просто бавно мига»: то зарежда файла, просто го прави със скорост няколко килобайта в секунда.
Особености при настройка на breakpoint-ове
Вероятно, много хора не са се замисляли за това при отстраняване на проблеми с обикновени програми, но точки на спиране не винаги се поставят хардуерно. Понякога поставянето на breakpoint включва временно записване на специална инструкция на нужното място направо в машинния код. Например, така действала стандартната команда b в GDB. Ето какво следва от това:
- не може да се постави точка вътре в BootROM, защото ROM
- може да се постави точка на кода, зареден в оперативната памет от SD картата, но трябва да изчакате, когато той бъде зареден. В противен случай няма да запишем парче код, а заредителят ще запише нашия breakpoint
Сигурен съм, че може да се поиска използването на хардуерни точки на спиране, но те все пак са ограничени.
Бърза подмяна на BootROM
На началния етап на отстраняване на проблеми често възниква желание да се поправи BootROM и да се опитате отново. Но има проблем: BootROM е част от дизайна, който се зарежда в FPGA, а неговият синтез отнема само няколко минути (и то след почти мигновен компилация на самия образ на BootROM от C и Assembler...). За щастие, всъщност всичко е значително по-бързо: редът на действията е такъв:
- да се прегенерира bootrom.mif (преминах на MIF вместо HEX, защото с HEX постоянно имах някакви проблеми, а MIF е родния формат на Altera)
- в Quartus да се каже
Processing -> Update Memory Initialization File - на пункта Assembler (в лявото меню Tasks) да се даде команда Start again
На всичко – само няколко десетки секунди.
Подготовка на SD картата
Тук всичко е относително просто, но трябва да се запаси търпение и около 14Gb пространство на диска:
git clone https://github.com/sifive/freedom-u-sdk
git submodule update --recursive --init
makeСлед което трябва да поставите чиста, а по-точно, ненужна SD-карта и да изпълните
sudo make DISK=/dev/sdX format-boot-loader… където sdX — устройството, зададено на картата. ВНИМАНИЕ: данните на картата ще бъдат изтрити, перезаписани и изобщо! Малко вероятно е да се прави цялото сборно от sudo, защото в този случай всички артефакти от сборката ще принадлежат root, и сборката ще трябва да се прави от sudo постоянно.
В резултат на това получавате карта, оформена в GPT с четири дяла, един от които е FAT с uEnv.txt и зареждаем образ във формат FIT (той съдържа няколко подобраза, всеки със свой адрес на зареждане), другият дял е чист, предполага се да бъде форматиран в Ext4 за Linux. Другите два дяла — загадъчни: на един се намира U-Boot (неговото изместване, доколкото разбирам, е вградена в BootROM), на другия, изглежда, се намират неговите променливи, но все още не ги използвам.
Ниво първо, BootROM
Народната мъдрост казва: «Ако в програмирането се случват танци с бубни, то в електрониката — и с пожарогасител». Става дума дори не за това, че веднъж почти спалих платката, мислейки си: «Но GND — това е същото ниско ниво» (явно, резистор все пак не би попречил...) Става дума по-скоро за това, че ако ръцете не растат оттам, електрониката не спира да доставя изненади: припаивайки конектора на платката, така и не успях да запоя правилно контактите — на видеото показват как припойът сам се разлива по цялото съединение, само постави ютията, а при мен той "се лепеше" как да е. Може би, припойът не беше подходящ за температурата на ютията, може би нещо друго... В крайна сметка, видявайки, че имам вече десетина контакта, се отказах и започнах да отстранявам грешки. И тук започна загадъчно: свързах RX/TX от UART-а, заредих фърмуера — и пише
INIT
CMD0
ERRORНу, всичко е логично — модулът на SD-картата не бях свързал. Изправяме ситуацията, зареждаме фърмуера... И тишина... Какво ли не премислих, а ларчикът само се отворил: един от изходите на модула трябваше да се свърже на VCC. В моя случай модулът поддържаше 5V за захранване, затова, без да се замислям, включих проводника, който излизаше от модула, на противоположната страна на платката. В крайна сметка, криво запояния конектор се изкриви, и просто се изгуби UART контакт. facepalm.jpg Както се казва, „глупавата глава не дава покой на краката“, а кривите ръце — на главата...
Накрая видях в Minicom дългоочакваното
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING /Освен това, индикаторът за зареждане се движи и旋转. Връща в спомените на училищните години и бавните зареждания на MinuetOS от флопи диск. Само дисковото устройство не скърца.
Проблемът е, че след съобщението BOOT не става нищо. Значи, време е да се свържа чрез OpenOCD на Raspberry, с него GDB на хоста, и да видя какво става.
Първо, свързването с GDB веднага показа, че $pc (програмният брояч, адрес на текущата инструкция) излита в 0x0 — вероятно, това се случва след множествена грешка. Затова, веднага след изпращането на съобщението BOOT ще добавим безкраен цикъл. Това ще го забави за малко...
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;
}Такъв хитър код се използва "за надеждност": чух някъде, че безкрайният цикъл — това е неопределено поведение, а тук компилаторът едва ли ще се досети (Напомням, че по 0x10000 се намира BootROM).

Казва се, какво още да очаквам — сурова вградена система, какви изходни кодове тук. Но в авторът отстранява сишен код... Крекс-фекс-пекс:
(gdb) file builds/zeowaa-e115/sdboot.elf
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from builds/zeowaa-e115/sdboot.elf...done.
Но трябва да се зарежда не MIF файл и не bin, а оригиналната версия в формат ELF.
Сега можем да опитаме да познаем адреса, откъдето изпълнението ще продължи (това е още една причина, поради която компилаторът не трябваше да се досеща, че цикълът е безкраен). Командата
set variable $pc=0xADDRпозволява да променяме стойността на регистра в движение (в случая — адреса на текущата инструкция). С нея можем да променяме стойности, записани в паметта (и memory-mapped регистри).
В крайна сметка стигнах до извода (не съм сигурен, че е правилен), че имаме "образ на sd-карта не на същата система", и трябва да преминем не на самото начало на заредените данни, а на 0x89800 байтове по-напред:
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Може би на това допринася и фактът, че вместо ненужната карта от 4Gb, взех карта от 2Gb и с опити замених в Makefile. DEMO_END=11718750 на DEMO_END=3078900 (не търсете смисъл в конкретното значение — такова няма, просто сега образът се побира на картата).
Ниво второ, U-Boot
Все още «падаме», но вече сме на адрес 0x0000000080089a84. Тук трябва да призная: всъщност изложението не е «с всички спирки», а частично е написано «по-късно», така че тук вече успях да добавя правилния dtb файл от нашия SoC и да коригирам настройките. HiFive_U-Boot проверка CONFIG_SYS_TEXT_BASE=0x80089800 (вместо 0x08000000), за да съвпадне адресът за зареждане с фактическия. Сега зареждаме карта на следващото ниво с друг образ:
(gdb) file ..\/freedom-u-sdk\/work\/HiFive_U-Boot\/u-boot
(gdb) tui enИ виждаме:
│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) │Въпреки това, скачаме между редовете 308 и 309. И не е изненадващо, като се има предвид, че в $sp седи стойността 0xfffffffe31cdc0a0. За съжаление, тя постоянно «бяга» поради ред 307. Затова ще опитаме да поставим точка за спиране на trap_entry, а след това отново да преминем на 0x80089800 (точката на влизане на U-Boot), и да се надяваме, че не изисква правилно задаване на регистрите преди прехода… Изглежда, работи:
(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
Продължава.
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Не е много добър указател на стека, да си кажем: сочи напълно извън оперативната памет (ако, разбира се, все още нямаме пренасочване на адреси, но да се надяваме на прост вариант).
Ще опитаме да заменим указателя с 0x881cf950. В крайна сметка стигаме до това, че handle_trap се извиква непрекъснато и се стига до _exit_trap с аргумент epc=2148315240 (в десетично представяне):
(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Слагаме точка на прекъсване на strnlen, продължаваме и виждаме:
(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Изглежда, _exit_trap иска да издаде отладъчна информация за настъпилото изключение, но не успява.Така че, нещо в исходниците отново не се показва. set directories ../freedom-u-sdk/HiFive_U-Boot/ О! Сега се показват!
Какво ж, ще стартираме отново и ще видим от стека причината за първоначалния проблем, довел до първата грешка (mcause == 5). Ако съм разбрал правилно написаното на стр. 37, това изключение означава Load access fault. Причината очевидно е, че тук
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 има точно това некоректно значение, и вътре board_init_f_init_reserve възниква грешка. Изглежда, ето го виновника: променливата с ясното име CONFIG_SYS_INIT_SP_ADDR. Тя е дефинирана в файла HiFive_U-Boot/include/configs/HiFive-U540.h. В някакъв момент дори помислих, а може би нека просто подобрим процесора вместо да адаптираме зареждача — но след това видях, че това по-скоро прилича на артефакт от недоизпипани настройки за друга конфигурация на паметта, и можем да опитаме да направим така:#if 0diff --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 -#define PHYS_SDRAM_1 - (PHYS_SDRAM_0 + PHYS_SDRAM_0_SIZE) -#define PHYS_SDRAM_0_SIZE 0x80000000 -#define PHYS_SDRAM_1_SIZE 0x10000000 +#define PHYS_SDRAM_0_SIZE 0x40000000 #define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_0 #endif @@ -81,7 +78,7 @@ #define CONSOLE_ARG "console=ttyS0,115200 " -#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
В някакъв момент получих количество решения,технологичен крепеж Е, приблизително, ето толкова
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Начална поддръжка за платката Zeowaa A-E115FBM 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
Подробности могат да бъдат намерени вКак се оказа, на тази платка от SiFive регистрите на някои устройства имат различни адреси. Освен това се оказа, че U-Boot се конфигурира чрез познатия ни механизъм Kconfig от ядрото на Linux — например, можете да зададете .
, и пред вас ще се появи удобен текстов интерфейс с описание на параметрите направи менюконфигурацияпо ? и т.н. В общи линии, събравайки описанията на дъските, получих описание на трета, изхвърляйки всякакви претенциозни пренастройки на PLL (очевидно, това е свързано с управлението от хост компютър през PCIe, но не е сигурно). Получих определен фърмуер, който при правилно време на Марс ми изпращаше по UART съобщение за това, от какъв хеш на комита е съставен, и за това колко DRAM имам (но тази информация я написах сам в хедъра).
Само е жалко, че след това дъската обикновено спираше да отговаря по JTAG за процесора, а зареждането от SD картата, уви, в моята конфигурация не е бързо. От друга страна, понякога BootROM даваше съобщение, че ГРЕШКА, не можа да се зареди, и веднага излизаше U-Boot. Тук ми стана ясно: очевидно, след перезареждане на битстрийма в ПЛИС паметта не се перезаписва, не успява да се „разтренирова“ и т.н. По същество, може просто при появата на съобщението ЗАРЕЖДАНЕ \/ да се свържа с отладчика и да дам команда set variable $pc=0x80089800, като по този начин избягвам това дълго зареждане (разбира се, при предположението, че при последното зареждане е счупена достатъчно рано и не е успяла да зареди нещо над оригиналния код).
Между другото, а това нормално ли е, че процесорът напълно замръзва, и JTAG отладчикът не може да се свърже с него с съобщенията
Грешка: не може да спре hart 0
Грешка: dmcontrol=0x80000001
Грешка: dmstatus =0x00030c82Чакайте! Вече съм го виждал! Нещо подобно се случва при дедлок на TileLink, а на автора на контролера за паметта не му имам доверие — сам го пишеше... Изненадващо, след първото успешно преизграждане на процесора след редакция на контролера видях:
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
ЗАРЕЖДАНЕ
BOOT
U-Boot 2018.09-g39cd67d-dirty (03 юли 2019 - 13:50:33 +0300)
DRAM: 1 GiB
MMC:
BEFORE LOAD ENVBEFORE FDTCONTROLADDRBEFORE LOADADDRIn: serial
Out: serial
Err: serial
Натиснете произволен клавиш за спиране на автоматичното зареждане: 3На този странен ред пред In: serial не обръщайте внимание — това е, когато се опитвах да разбера на 'виснеща' процесора дали работи коректно с средата. Какво значи „Вече десет минути така виси“? Най-малкото успя да се релоцира и да премине към зареждащото меню! Малко отклонение: макар U-Boot да се зарежда в първите 2^24 байта от SD картата, след като стартира, той копира себе си на някое по-далечно място в адрес, който е записан в конфигурационния хедер, или просто в по-високи адреси на оперативната памет, извършва релация на ELF символите и предава управлението натам. Така че: изглежда, че този етап е преминат и са получили в добавка процесор, който не 'виси' напълно след това.
И така, защо не работи таймерът? Изглежда, че часовниците по принцип по някаква причина не работят...
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000Ами, ако стрелките се завъртят на ръка?
(gdb) set variable *0x0200bff8=310000000
(gdb) cТогава:
Натиснете произволен клавиш, за да спрете автобут: 0
MMC_SPI: 0 на 0:1 хц 20000000 режим 0Извод: часовниците не работят. Вероятно и поради това не работи и въвеждането от клавиатурата:
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:
/* Първият знак от ANSI escape последователността 'e' */
if (c == 'e') {
*esc = 1;
*key = KEY_NONE;
}
break;
case 1:
/* Вторият знак от ANSI '[' */
if (c == '[') {
...Проблемът се оказа в това, че съм преправил малко нещата: добавих в конфигурацията на процесора ключ:
case DTSTimebase => BigInt(0)… ръководейки се от факта, че в коментара беше написано "ако не знаете — оставете 0". И в действителност WithNBigCores поставяше точно на 1MHz (както, между другото, беше посочено в конфигурацията на U-Boot). Но аз, по дяволите, съм внимателен и педантичен: там не знам, тук 25MHz! В резултат на това, нищо не работи. Премахнах собствените си 'подобрения' и…
Натиснете произволен клавиш, за да спрете автобут: 0
MMC_SPI: 0 на 0:1 хц 20000000 режим 0
## Неизвестен тип таблица на партиции 0
libfdt fdt_path_offset() върна FDT_ERR_NOTFOUND
** Няма таблица на партиции - mmc 0 **
## Информация: размер на входните данни = 34 = 0x22
Стартиране на uEnv.txt boot2...
## Грешка: "boot2" не е дефиниран
HiFive-Unleashed #Дори можете да въвеждате команди! Например, ако поразгледате малко, можете най-накрая да се сетите да въведете mmc_spi 1 10000000 0; mmc part, намалявайки честотата на SPI от 20MHz до 10MHz. Защо? Ами, в конфигурацията беше записана максимална честота от 20MHz, и тя все още е написана там. Но, доколкото разбрах, интерфейсите, поне тук, работят така: кодът дели честотата на хардуерния блок (при мен — навсякъде 25MHz) на целевата, и поставя получената стойност като делител в съответния контролен регистър. Проблемът е, че ако за 115200Hz UART става приблизително каквото трябва, то ако направим точно деление на 25000000 на 20000000, получаваме 1, т.е. ще работи на 25MHz. Може би това е нормално, но ако поставят ограничения, значи на някой му е необходимо (но не е сигурно)... Така че е по-лесно просто да се запишат и да се продължи напред — далеч и, за съжаление, за дълго. 25MHz — това не е Core i9.
Изход на конзолата
HiFive-Unleashed # env edit mmcsetup
edit: mmc_spi 1 10000000 0; mmc part
HiFive-Unleashed # boot
MMC_SPI: 1 на 0:1 херц 10000000 режим 0
Картина на партиции за MMC устройство 0 -- Тип на партиция: EFI
Част Начален LBA Краен LBA Име
Атрибути
Тип GUID
Партитсионен GUID
1 0x00000800 0x0000ffde "Vfat Boot"
атрибути: 0x0000000000000000
тип: ebd0a0a2-b9e5-4433-87c0-68b6b72699c7
тип: данни
guid: 76bd71fd-1694-4ff3-8197-bfa81699c2fb
2 0x00040800 0x002efaf4 "root"
атрибути: 0x0000000000000000
тип: 0fc63daf-8483-4772-8e79-3d69d8477de4
тип: линукс
guid: 9f3adcc5-440c-4772-b7b7-283124f38bf3
3 0x0000044c 0x000007e4 "uboot"
атрибути: 0x0000000000000000
тип: 5b193300-fc78-40cd-8002-e86c45580b47
guid: bb349257-0694-4e0f-9932-c801b4d76fa3
4 0x00000400 0x0000044b "uboot-env"
атрибути: 0x0000000000000000
тип: a09354ac-cd63-11e8-9aff-70b3d592f0fa
guid: 4db442d0-2109-435f-b858-be69629e7dbf
libfdt fdt_path_offset() върна FDT_ERR_NOTFOUND
2376 байта прочетени за 0 ms
Изпълняване на uEnv.txt boot2...
15332118 байта прочетени за 0 ms
## Зареждане на ядрото от FIT изображение на 90000000 ...
Използване на конфигурация 'config-1'
Опитване на 'bbl' подснимка на ядрото
Описание: BBL/SBI/riscv-pk
Тип: Ядрено изображение
Компресия: без компресия
Начало на данните: 0x900000d4
Размер на данните: 74266 Байта = 72.5 KiB
Архитектура: RISC-V
OS: Линукс
Адрес на зареждане: 0x80000000
Точка на вход: 0x80000000
Алгоритъм за хеш: sha256
Хеш стойност: 28972571467c4ad0cf08a81d9cf92b9dffc5a7cb2e0cd12fdbb3216cf1f19cbd
Потвърждаване на целостта на хеша ... sha256+ OK
## Зареждане на fdt от FIT изображение на 90000000 ...
Използване на конфигурация 'config-1'
Опитване на 'fdt' подснимка на fdt
Описание: недостъпно
Тип: Плоско устройство Дърво
Компресия: без компресия
Начало на данните: 0x90e9d31c
Размер на данните: 6911 Байта = 6.7 KiB
Архитектура: RISC-V
Адрес на зареждане: 0x81f00000
Алгоритъм за хеш: sha256
Хеш стойност: 10b0244a5a9205357772ea1c4e135a4f882409262176d8c7191238cff65bb3a8
Потвърждаване на целостта на хеша ... sha256+ OK
Зареждане на fdt от 0x90e9d31c до 0x81f00000
Стартиране с помощта на fdt blob на 0x81f00000
## Зареждане на допълнителни данни от FIT изображение на 90000000 ...
Опитване на 'ядро' подснимка на допълнителни данни
Описание: Ядро на Линукс
Тип: Ядрено изображение
Компресия: без компресия
Начало на данните: 0x900123e8
Размер на данните: 10781356 Байта = 10.3 MiB
Архитектура: RISC-V
OS: Линукс
Адрес на зареждане: 0x80200000
Точка на вход: недостъпна
Алгоритъм за хеш: sha256
Хеш стойност: 72a9847164f4efb2ac9bae736f86efe7e3772ab1f01ae275e427e2a5389c84f0
Потвърждаване на целостта на хеша ... sha256+ OK
Зареждане на допълнителни данни от 0x900123e8 до 0x80200000
## Зареждане на допълнителни данни от FIT изображение на 90000000 ...
Опитване на 'ramdisk' подснимка на допълнителни данни
Описание: buildroot initramfs
Тип: RAMDisk изображение
Компресия: gzip компресирано
Начало на данните: 0x90a5a780
Размер на данните: 4467411 Байта = 4.3 MiB
Архитектура: RISC-V
OS: Линукс
Адрес на зареждане: 0x82000000
Точка на вход: недостъпна
Алгоритъм за хеш: sha256
Хеш стойност: 883dfd33ca047e3ac10d5667ffdef7b8005cac58b95055c2c2beda44bec49bd0
Потвърждаване на целостта на хеша ... sha256+ OK
Зареждане на допълнителни данни от 0x90a5a780 до 0x82000000Добре, стигнахме до ново ниво, но все още засяда. Понякога изхвърля и изключения. Можете да видите mcause, като засечете кода на указаното място $pc и след това si да се озовеш на trap_entry. Самият обработчик от U-Boot може да изведе само за mcause = 0..4, така че се пригответе да се зацикляте на некоректно зареждане. Тук погледнах в конфигурацията, започнах да разглеждам какво съм променял, и си спомних: там пише conf/rvboot-fit.txt написано:
fitfile=image.fit
# отдолу много трябва да съвпадне с това, което е във FIT (ugha)Какво да кажем, нека приведем всички файлове в съответствие, ще заменим командния ред на ядрото приблизително така, тъй като имаме подозрения, че SIF0 — това е изход някъде по PCIe:
-bootargs=console=ttySIF0,921600 debug
+bootargs=console=ttyS0,125200 debugИ в добавка ще променим алгоритъма за хеширане от SHA-256 на MD5: не ми трябва криптоустойчивост (особено по време на отстраняване на неизправности), смята се, че е ужасно бавно, а за улавяне на грешки в целостта при зареждане и MD5 е напълно достатъчно. Какво стана в крайна сметка? Започнахме да преминаваме предишното ниво значително по-бързо (поради по-простото хеширане), и се отвори следващото:
...
Проверка на цялостта на хеширане ... md5+ ОК
Зареждане на зареждаеми от 0x90a5a758 до 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() върна 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";
};
Зареждане на изображение на ядрото ... ОК
Зареждане на ядрото в
3Но часовниците не тикат…
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000Упс, изглежда, че корекцията на хода на часовниците е оказала плацебо ефект, макар че тогава ми се стори, че помогна. Не, разбира се, трябва да се поправи, но нека първо да завъртим стрелките ръчно и да видим какво ще се получи:
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=1000000
(gdb) c
Продължаване.
^C
Програмата получи сигнал SIGINT, Прекъсване.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=2000000
(gdb) c
Продължаване.
^C
Програмата получи сигнал SIGINT, Прекъсване.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=3000000
(gdb) c
Продължаване.Междувременно…
Зареждане на изображение на ядрото ... ОК
Зареждане на ядрото в
3
2
1
0
## Започва приложение на 0x80000000 ...Не, ще отида да автоматизирам хода на часовниците — ами, може пък там да реши да калибрира таймера!
А адресът на текущата инструкция междувременно сочи някъде в
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вътре в зареждащия се Berkeley Boot Loader. Лично мен ме смущава споменаването htif — хост интерфейс, използван за свързано стартиране на ядрото (например в сътрудничество с хостовия ARM), предполагал съм, че е самостоятелен. Въпреки това, ако намерим тази функция в изходния код, ще видим, че не всичко е толкова зле:
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"); }
}
}Задача: пусни часовника
Търсене на регистри в CLINT ни води към
val io = IO(new Bundle {
val rtcTick = Bool(INPUT)
})
val time = RegInit(UInt(0, width = timeWidth))
when (io.rtcTick) { time := time + UInt(1) }Който се свързва в RTC, или в загадъчния MockAON, за който поначало реших: „Какво е това? Не е ясно? Изключваме!“ Понеже все още не разбирам каква е часовата магия, просто ще реализирам тази логика в System.scala:
val rtcDivider = RegInit(0.asUInt(16.W)) // за всеки случай ще поддържам до 16 GHz, оптимист съм :)
val mhzInt = p(DevKitFPGAFrequencyKey).toInt
// Да предположим, че честотата е цяло число мегахерци
rtcDivider := Mux(rtcDivider === (mhzInt - 1).U, 0.U, rtcDivider + 1.U)
outer.clintOpt.foreach { clint =>
clint.module.io.rtcTick := rtcDivider === 0.U
}Пробивайки се към Linux kernel
Тук разказът вече се проточва и стана малко еднообразен, затова ще опиша накратко:
BBL предполагаше наличие на FDT на адрес 0xF0000000, а аз вече го бях поправил! Какво да правим? Нека потърсим още… Намерих в HiFive_U-Boot/arch/riscv/lib/boot.c, замених на 0x81F00000, посочено в конфигурацията за зареждане на U-Boot.
После BBL се оплака, че няма памет. Моят път минаваше през функцията mem_prop, че в riscv-pk/machine/fdt.c: оттам разбрах, че трябва да маркирам възел fdt ram като device_type = "memory" — след това, може би, ще трябва да поправя генератора на процесора, но за сега просто ще го напиша на ръка — все пак прехвърлих този файл ръчно.
Сега получих съобщение (представено в форматиран вид, с нови редове):
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.Изглежда, че опции са зададени правилно riscv,kernel-start и riscv,kernel-end в DTB, но нули се парсват. Отстраняване на грешки query_chosen показа, че BBL се опитва да парсва 32-битов адрес, а му се попада двойка <0x0 0xADDR>, и първата стойност изглежда е с по-ниски разряди. Добавих в секцията chosen
chosen {
#address-cells = ;
#size-cells = ;
...
}и поправих генерирането на стойности: не добавяйте 0x0 като първи елемент.
Тези 100500 прости стъпки ще ви покажат как лесно и просто да видите как пада пингвин:
Скрит текст
Проверка целостността на хеша ... md5+ OK
Зареждане на подготвителни файлове от 0x90a5a758 до 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
chosen {
linux,initrd-end = ;
linux,initrd-start = ;
riscv,kernel-end = ;
riscv,kernel-start = ;
#address-cells = ;
#size-cells = ;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
libfdt fdt_path_offset() върна FDT_ERR_NOTFOUND
chosen {
linux,initrd-end = ;
linux,initrd-start = ;
riscv,kernel-end = ;
riscv,kernel-start = ;
#address-cells = ;
#size-cells = ;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
Зареждане на ядрото ... OK
Стартиране на ядрото в
3
2
1
0
## Стартиране на приложението на 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: Игнориране на диапазон памет 0x80000000 - 0x80200000
[ 0.000000] Линукс версия 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc версия 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99)) #1 SMP Сря Юли 3 21:29:21 MSK 2019
[ 0.000000] bootconsole [early0] включен
[ 0.000000] Начално ramdisk на: 0x(____ptrval____) (16777216 байта)
[ 0.000000] Диапазони на зоните:
[ 0.000000] DMA32 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Normal [mem 0x00000000c0000000-0x00000bffffffffff]
[ 0.000000] Започване на управляемата зона за всеки възел
[ 0.000000] Ранни диапазони на паметта на възлите
[ 0.000000] възел 0: [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Настройка на паметта за възел 0 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Възел 0 общо страници: 261632
[ 0.000000] DMA32 зона: 3577 използвани страници за memmap
[ 0.000000] DMA32 зона: 0 резервирани страници
[ 0.000000] DMA32 зона: 261632 страници, LIFO партида:63
[ 0.000000] софтуерен IO TLB: картографиран [mem 0xbb1fc000-0xbf1fc000] (64MB)(емблемата излиза BBL, а това с времевите марки — ядрото).
За щастие, не знам как е навсякъде, но на RocketChip при свързване на отладчика по JTAG може да се хващат trap-ове от самото начало — отладчикът спира точно в тази точка.
Програмата получи сигнал SIGTRAP, Trap/Breakpoint trap.
0xffffffe0000024ca в ?? ()
(gdb) bt
#0 0xffffffe0000024ca в ?? ()
Backtrace спря: предишният кадър е идентичен на този кадър (повреден стек?)
(gdb) file work/linux/vmlinux
Вече се отлага програма.
Сигурни ли сте, че искате да промените файла? (y или n) y
Четене на символи от work/linux/vmlinux...готово.
(gdb) bt
#0 0xffffffe0000024ca в setup_smp () на /hdd/trosinenko/fpga/freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c:75
#1 0x0000000000000000 в ?? ()
Backtrace спря: кадърът не е запазил PCfreedom-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); // < ВИЕ СТЕ ТУК
}Както се казва в стария виц, ЦПУ не е намерен, изпълнение на софтуерна емулация. Или не изпълнение. Загубихме се в единично ядро на процесора.
/* The lucky hart to first increment this variable will boot the other cores */
atomic_t hart_lottery;
unsigned long boot_cpu_hartid;Добър коментар в linux/arch/riscv/kernel/setup.c — такова боядисване на оградата по метода на Том Сойер. Всъщност, днес победители по някаква причина не бяха намерени, наградата се прехвърля на следващото издание…
Тук предлагам да приключим и без това дългата статия.
Продължението следва. В него ще има битка с хитра грешка, която успява да се скрие, ако се приближавате бавно с singlestep.
Текстов скринкаст на зареждането (външна връзка):
Източник: habr.com
