Част 3: Почти зареждаме Linux от SD карта на RocketChip

Част 3: Почти зареждаме Linux от SD карта на RocketChip В предишната част беше реализиран повече или по-малко работещ контролер за памет, по-точно — обвивка около IP Core от Quartus, която служи като преходник за TileLink. Днес в рубриката „Портируем RocketChip на малко известна китайска дъска с Cyclone“ ще видите работеща конзола. Процесът се проточи: вече си мислех, че бързо ще стартирам 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 за FPGA 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-файл и се предава на зареждача на ядрото, за да може то да настрои правилно хардуера. Интересното е, че без реда с 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-адаптер

Вид отгоре:

Част 3: Почти зареждаме Linux от SD карта на RocketChip

Вид отдолу:

Част 3: Почти зареждаме Linux от SD карта на RocketChip

Отстраняване на проблеми с софтуера: инструменти

Първо, нека поговорим за наличните инструменти за отстраняване на проблеми и техните ограничения.

Minicom

На първо място, ще трябва по някакъв начин да чете какво извежда зареждача и ядрото. За това на Linux (в случая — на този, който е на RaspberryPi) ще ни трябва програмата Minicom. Всъщност, всяка програма за работа с последователен порт ще бъде подходяща.

Обърнете внимание, че при стартиране името на устройството на порта трябва да се посочи като -D \/dev\/ttyS0 — след опцията -D. И основната информация: за изход използвайте Ctrl-A, X. Истината е, че имам случай, когато тази комбинация не сработи — тогава просто може от съседна SSH сесия да кажете killall -KILL minicom.

Има и още едно предимство. Конкретно на RaspberryPi има два UART порта, и двата порта могат да бъдат вече използвани за нещо: единят за Bluetooth, а през другия по подразбиране излиза конзолата на ядрото. За щастие, това поведение може да бъде пренастроено. по този наръчник.

Презаписване на паметта

По време на отладката, за да проверя хипотезата, понякога ми се налагаше да заредя буутлоадера (извинете) в оперативната памет директно от хоста. Може би, това може да се направи направо от GDB, но в крайна сметка избрах просто решение: копирах необходимия файл на Raspberry, пренасочих също порта 4444 (telnet от OpenOCD) през SSH и използвах командата load_image. Когато я изпълнявате, изглежда, че всичко е замръзнало, но всъщност «то не спи, просто бавно мига»: зарежда файла, просто го прави със скорост от няколко килобайта в секунда.

Характеристики на поставянето на точки за спиране

Вероятно, много хора не са се замисляли за това при отладка на обикновени програми, но точките за спиране не винаги се поставят хардуерно. Понякога поставянето на точка за спиране се състои в временно записване на специална инструкция на нужното място директно в машинния код. Например, стандартната команда b в GDB работеше така:

  • не може да се постави точка вътре в BootROM, защото ROM
  • можете да поставите точка за спиране на кода, зареден в оперативната памет от SD картата, но трябва да изчакате, когато той бъде зареден. В противен случай не ние ще презапишем част от кода, а буутлоадерът ще презапише нашата точка за спиране.

Сигурен съм, че може да се поиска явно да се използват хардуерни точки за спиране, но те са в ограничено количество.

Бързо заместване на 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 картата

Тук всичко е относително просто, но трябва да се запасите с търпение и около 14 Гб пространство на диска:

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 за Линукс. Още две партиции — загадъчни: на едната живее 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 (program counter, адрес на текущата инструкция) лети в 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;
 }

Такъв хитър код се използва "за надеждност": чух, че, уж, безкрайния цикъл — това е Undefined Behavior, а тук компилаторът едва ли ще се досети (Напомням, че по 0x10000 себе си е BootROM).

Част 3: Почти зареждаме Linux от SD карта на RocketChip

Казвало се, какво друго да се очаква — суров embedded, какви изходни кодове тук. Но все пак в тази статия авторът е дебугвал C-кода... Крекс-фекс-пекс:

(gdb) file builds/zeowaa-e115/sdboot.elf
Програмата вече е в дебъг режим.
Сигурни ли сте, че искате да промените файла? (y или n) y
Четене на символи от builds/zeowaa-e115/sdboot.elf...готово.

Част 3: Почти зареждаме Linux от SD карта на RocketChip

Само трябва да заредите не 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

Възможно е и това да е повлияло на решението ми да използвам карта с капацитет 2Gb вместо ненужната на 4Gb, като просто замених в 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, то это исключение означает Ошибка доступа к данным для чтения. Причина, по-видимому, в том, что вот здесь

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

call_board_init_f:
    li  t0, -16
    li  t1, CONFIG_SYS_INIT_SP_ADDR
    and sp, t1, t0  /* принудительное выравнивание на 16 байт */

#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       /* переход к board_init_f() */

$sp имеет некорректное значение, и внутри board_init_f_init_reserve возникает ошибка. Похоже, вот и виновник: переменная с недвусмысленным названием CONFIG_SYS_INIT_SP_ADDR. Она определена в файле HiFive_U-Boot/include/configs/HiFive-U540.h. В един момент си помислих, а какво ако просто го оставя, а вместо това поправя процесора? Но после видях, че това по-скоро изглежда като артефакт от непълни настройки под друга конфигурация на паметта и може да опитам да направя така:#if 0-

diff --git a/include/configs/HiFive-U540.h b/include/configs/HiFive-U540.h
index ca89383..245542c 100644
--- a/include/configs/HiFive-U540.h
+++ b/include/configs/HiFive-U540.h
@@ -65,12 +65,9 @@
 #define CONFIG_SYS_SDRAM_BASE  PHYS_SDRAM_0
 #endif
 #if 1
-
+#define CONFIG_NR_DRAM_BANKS   1
 #define 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 */

В един момент броят на импровизациите технологични крепежи достигна критичен процент. След малко усилия, осъзнах, че е необходимо да направя коректен порт за дъската си. За това е нужно да копирам и коригирам определено количество файлове според нашата конфигурация.

Ето приблизително колко

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

Подробности можете да видите в репозитории.

Както се оказа, на тази SiFive дъска регистрите на някои устройства имат различни адреси. Освен това, стана ясно, че U-Boot се конфигурира чрез познатия от ядрото на Linux механизъм Kconfig — например, може да подадете make menuconfig, а пред вас ще се появи удобен текстов интерфейс с показване на описанията на параметрите ? И така, създавайки описание на третата платка от описанията на двата, и отстранявайки всичките излишни настройки на 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:
ПРЕДИ ЗАРЕЖДАНЕ НА АНВ
ПРЕДИ FDTCONTROLADDR
ПРЕДИ ЗАРЕЖДАНЕ НА АДРЕС
В:    сериен
Из:   сериен
Гр:   сериен
Натиснете всякакъв клавиш, за да спрете автобут:  3

На тази странна линия преди В: сериен Не се притеснявайте — това съм аз, който опитвам на засядащия процесор да разбера дали работи коректно с околната среда. Какво значи, "Вече десет минути така засяда"? Поне успя ли да се пренасочи и да премине към менюто за зареждане! Малко отклонение: макар 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 Hz 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 Hz 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 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

Добре, стигнахме до ново ниво, но все още забива. Понякога дори хвърля изключения. Можем да видим 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+ OK
   Зареждане на компоненти от 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";
};
   Зареждане на образа на ядрото ... OK
Стартиране на ядрото след
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
Продължаване.

Междувременно...

   Зареждане на образа на ядрото ... OK
Стартиране на ядрото след
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)) // за всеки случай ще поддържам до 16GHz, оптимист съм :)
  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

Тук повествованието вече е опънато и стана малко еднообразно, затова ще опиша набързо:

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 загрузчик

                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] Версия Linux 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: mapped [mem 0xbb1fc000-0xbf1fc000] (64MB)

(емблемата се показва от BBL, а това с времевите марки — ядрото).

За щастие, не знам как е навсякъде, но на RocketChip при свързване на отладчик по JTAG може да се улавят trap-ове направо — отладчикът ще спре точно в тази точка.

Програмата получи сигнал SIGTRAP, Trap за следене/прекъсване.
0xffffffe0000024ca в ?? ()
(gdb) bt
#0  0xffffffe0000024ca в ?? ()
Обратният проследяване спря: предишният фрейм е идентичен на този фрейм (корумпиран стек?)
(gdb) файл 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 в ?? ()
Обратното проследяване спря: кадърът не е запазил PC

freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c:

void __init setup_smp(void)
{
    struct device_node *dn = NULL;
    int hart;
    bool found_boot_cpu = false;
    int cpuid = 1;

    while ((dn = of_find_node_by_type(dn, "cpu"))) {
        hart = riscv_of_processor_hartid(dn);
        if (hart < 0)
            continue;

        if (hart == cpuid_to_hartid_map(0)) {
            BUG_ON(found_boot_cpu);
            found_boot_cpu = 1;
            continue;
        }

        cpuid_to_hartid_map(cpuid) = hart;
        set_cpu_possible(cpuid, true);
        set_cpu_present(cpuid, true);
        cpuid++;
    }

    BUG_ON(!found_boot_cpu); // < ВИ НАХОДИТЕСЬ ТУТ
}

Как казваше старият виц, 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.

Текстов скринкаст на зареждане (външна връзка):
Част 3: Почти зареждаме Linux от SD карта на RocketChip

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster