В
В аппаратной части добавится SD-карта, подключённая по интерфейсу SPI, а также UART. В программной части BootROM будет заменён с xip
на sdboot
и, собственно, добавлены следующие стадии загрузки (на SD-карте).
Допиливание аппаратной части
Итак, задача: нужно перейти на «большое» ядро и подключить UART (от Raspberry) и SD-адаптер (использовалась некая платка от Catalex с шестью пинами: GND, VCC, MISO, MOSI, SCK, CS).
В принципе, всё было довольно просто. Но перед тем, как это осознать, меня немного побросало из стороны в сторону: после предыдущего раза я решил, что снова нужно просто подмешать в System
что-то вроде HasPeripheryUART
(и в реализацию соответственно), то же для SD-карты — и всё будет готово. Потом я решил посмотреть, а как же оно реализовано в «серьёзном» дизайне. Так, что у нас тут из серьёзного? Arty, видимо, не подходит — остаётся монстр unleahshed.DevKitConfigs
. И вдруг обнаружилось, что там повсюду какие-то оверлеи, которые добавляются через параметры по ключам. Я догадываюсь, что это, наверное, очень гибко и конфигурируемо, но мне бы хоть что-то для начала запустить… А у вас нет такого же, только попроще-покостыльнее?.. Тут-то я и наткнулся на 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
...
Строчка в теле класса System
добавляет информацию о частоте, на которой работает эта часть нашего 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 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
}
Цепочки регистров, честно говоря, добавлены просто по аналогии с некоторыми другими местами изначального кода. Скорее всего, они должны защищать от dq
? Ответа я пока так и не нашёл, но, похоже, остальной код рассчитывает именно на такое подключение.
Физически, я просто назначил выводы дизайна на свободные контакты на колодке и переставил джампер выбора напряжения в 3.3V.
SD-адаптер
Вид сверху:
Вид снизу:
Отладка программной части: инструменты
Для начала поговорим об имеющихся инструментах отладки и их ограничениях.
Minicom
Во-первых, нам будет нужно как-то читать то, что выводит загрузчик и ядро. Для этого на Linux (в данном случае — на том, что на RaspberryPi) нам потребуется программа Minicom. Вообще говоря, подойдёт любая программа для работы с последовательны портом.
Обратите внимание, что при запуске имя устройства порта нужно указывать как -D /dev/ttyS0
— после опции -D
. Ну и главная информация: для выхода используйте Ctrl-A, X
. У меня правда был случай, когда эта комбинация не сработала — тогда можно из соседнего сеанса SSH просто сказать killall -KILL minicom
.
Есть и ещё одна особенность. Конкретно на RaspberryPi есть два UART, и оба порта могут быть уже для чего-то приспособлены: один для Bluetooth, через другой по умолчанию выводится консоль ядра. К счастью, это поведение можно перенастроить
Переписывание памяти
При отладке, для проверки гипотезы мне иногда приходилось загрузить загрузчик (извините) в оперативную память непосредственно с хоста. Может, это можно сделать прямо из GDB, но я в итоге пошёл по простому пути: скопировал на Raspberry необходимый файл, пробросил через SSH также порт 4444 (telnet от OpenOCD) и воспользовался командой load_image
. Когда вы её выполняете, кажется что всё зависло, но на самом деле «оно не спит, оно просто медленно моргает»: оно грузит файл, просто делает это со скорость пару килобайт в секунду.
Особенности установки breakpoint-ов
Вероятно, многим об этом не приходилось задумываться при отладке обычных программ, но точки останова не всегда ставятся аппаратно. Иногда постановка breakpoint-а заключается во временном записывании специальной инструкции в нужное место прямо в машинный код. Например, так у меня действовала стандартная команда b
в GDB. Вот, что из этого следует:
- нельзя поставить точку внутри BootROM, потому что ROM
- поставить точку останова на код, загруженный в оперативку с SD-карты, можно, но нужно дождаться, когда он будет загружен. В противном случае не мы перепишем кусочек кода, а загрузчик перепишет наш breakpoint
Уверен, можно явно попросить использовать аппаратные точки останова, но их в любом случае ограниченное число.
Быстрая подмена BootROM
На начальном этапе отладки нередко возникает желание поправить BootROM и попробовать ещё разок. Но есть проблема: BootROM является частью дизайна, загружаемого в ПЛИС, а его синтез — дело нескольких минут (и это-то после почти мгновенной компиляции самого образа BootROM из C и Assembler…). К счастью, на самом деле всё намного быстрее: последовательность действий такая:
- перегенерировать bootrom.mif (я перешёл на MIF вместо HEX, потому что с HEX у меня вечно были какие-то проблемы, а MIF — родной Альтеровский формат)
- в 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 для Линукса. Ещё два раздела — загадочные: на одном живёт 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).
Казалось бы, а что ещё ожидать — суровый embedded, какие уж тут исходники. Но ведь в
(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
Continuing.
Breakpoint 1, trap_entry () at /hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot/arch/riscv/cpu/HiFive/start.S:308
(gdb) p/x $sp
$4 = 0x81cf950
Так себе указатель стека, прямо скажем: указывает вообще мимо оперативки (если, конечно, у нас ещё нет трансляции адресов, но будем надеяться на простой вариант).
Попробуем заменить указатель на 0x881cf950
. В итоге приходим к тому, что handle_trap
вызывается и вызывается, при этом уходим в _exit_trap
с аргументом epc=2148315240
(в десятичном виде):
(gdb) x/10i 2148315240
0x800cb068 <strnlen+12>: lbu a4,0(a5)
0x800cb06c <strnlen+16>: bnez a4,0x800cb078 <strnlen+28>
0x800cb070 <strnlen+20>: sub a0,a5,a0
0x800cb074 <strnlen+24>: ret
0x800cb078 <strnlen+28>: addi a5,a5,1
0x800cb07c <strnlen+32>: j 0x800cb064 <strnlen+8>
0x800cb080 <strdup>: addi sp,sp,-32
0x800cb084 <strdup+4>: sd s0,16(sp)
0x800cb088 <strdup+8>: sd ra,24(sp)
0x800cb08c <strdup+12>: li s0,0
Ставим breakpoint на 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=<optimized out>, precision=<optimized out>, flags=<optimized out>) 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 %08x , ra %08lxn", fmt@entry=0x800d4458 "exception code: %d , %s , epc %08x , ra %08lxn", 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 %08x , ra %08lxn", 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 %08x , ra %08lxn", args=args@entry=0x881cc188) at lib/vsprintf.c:717
#5 0x00000000800ccb50 in printf (fmt=fmt@entry=0x800d4458 "exception code: %d , %s , epc %08x , ra %08lxn") at lib/vsprintf.c:792
#6 0x000000008008a9f0 in _exit_trap (regs=<optimized out>, epc=2148315240, code=<optimized out>) at arch/riscv/lib/interrupts.c:92
#7 handle_trap (mcause=<optimized out>, epc=<optimized out>, regs=<optimized out>) 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
). Если я правильно понял, что написано 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 0
-енных настроек под другую конфигурацию памяти, и можно попробовать сделать так:
diff --git a/include/configs/HiFive-U540.h b/include/configs/HiFive-U540.h
index ca89383..245542c 100644
--- a/include/configs/HiFive-U540.h
+++ b/include/configs/HiFive-U540.h
@@ -65,12 +65,9 @@
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_0
#endif
#if 1
-/*#define CONFIG_NR_DRAM_BANKS 1*/
+#define CONFIG_NR_DRAM_BANKS 1
#define PHYS_SDRAM_0 0x80000000 /* SDRAM Bank #1 */
-#define PHYS_SDRAM_1
- (PHYS_SDRAM_0 + PHYS_SDRAM_0_SIZE) /* SDRAM Bank #2 */
-#define PHYS_SDRAM_0_SIZE 0x80000000 /* 2 GB */
-#define PHYS_SDRAM_1_SIZE 0x10000000 /* 256 MB */
+#define PHYS_SDRAM_0_SIZE 0x40000000 /* 1 GB */
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_0
#endif
/*
@@ -81,7 +78,7 @@
#define CONSOLE_ARG "console=ttyS0,115200 "
/* Init Stack Pointer */
-#define CONFIG_SYS_INIT_SP_ADDR (0x08000000 + 0x001D0000 -
+#define CONFIG_SYS_INIT_SP_ADDR (0x80000000 + 0x001D0000 -
GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_LOAD_ADDR 0xa0000000 /* partway up SDRAM */
В какой-то момент количество костылей
Ну, приблизительно, вот столечко
trosinenko@trosinenko-pc:/hdd/trosinenko/fpga/freedom-u-sdk/HiFive_U-Boot$ git show --name-status
commit 39cd67d59c16ac87b46b51ac1fb58f16f1eb1048 (HEAD -> zeowaa-1gb)
Author: Anatoly Trosinenko <[email protected]>
Date: Tue Jul 2 17:13:16 2019 +0300
Initial support for Zeowaa A-E115FB board
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 выдавал сообщение, что ERROR
, не удалось загрузиться, и тут же выскакивал U-Boot. Тут-то до меня и дошло: видимо, после перезагрузки bitstream в ПЛИС память не перетирается, не успевает «растренироваться» и т.д. Короче, можно просто при появлении сообщения LOADING /
подключаться отладчиком и командовать set variable $pc=0x80089800
, минуя тем самым эту долгую загрузку (конечно, в предположении, что оно в прошлый раз сломалось достаточно рано, и не успело поверх оригинального кода что-то загрузить).
Кстати, а это вообще нормально, что процессор напрочь виснет, и к нему не может подключиться JTAG-отладчик с сообщениями
Error: unable to halt hart 0
Error: dmcontrol=0x80000001
Error: dmstatus =0x00030c82
Так, постойте! Я это уже видел! Что-то подобное происходит при дедлоке TileLink, а автору контроллера памяти я как-то не доверяю — сам же писал… Внезапно, после первой же удачной пересборки процессора после редактирования контроллера я увидел:
INIT
CMD0
CMD8
ACMD41
CMD58
CMD16
CMD18
LOADING
BOOT
U-Boot 2018.09-g39cd67d-dirty (Jul 03 2019 - 13:50:33 +0300)
DRAM: 1 GiB
MMC:
BEFORE LOAD ENVBEFORE FDTCONTROLADDRBEFORE LOADADDRIn: serial
Out: serial
Err: serial
Hit any key to stop autoboot: 3
На эту странную строчку перед In: serial
не обращайте внимания — это я пытался на виснущем процессоре понять, корректно ли оно работает с environment. Что значит, «Уже десять минут так висит»? Оно хотя бы сумело релоцироваться и перейти к загрузочному меню! Небольшое отступление: хоть U-Boot и грузится в числе первых 2^24 байт с SD-карты, запустившись, он копирует себя куда подальше по адресу, то ли записанному в конфигурационном хедере, то ли просто в старшие адреса оперативной памяти, производит релокацию ELF-символов, и передаёт туда управление. Так вот: похоже, этот уровень прошли и бонусом получили процессор, не виснущий намертво после этого.
Итак, почему не работает таймер? Похоже, часы в принципе почему-то не идут…
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000
А что, если стрелки вручную покрутить?
(gdb) set variable *0x0200bff8=310000000
(gdb) c
Тогда:
Hit any key to stop autoboot: 0
MMC_SPI: 0 at 0:1 hz 20000000 mode 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:
/* First char of ANSI escape sequence 'e' */
if (c == 'e') {
*esc = 1;
*key = KEY_NONE;
}
break;
case 1:
/* Second char of ANSI '[' */
if (c == '[') {
...
Проблема оказалась в том, что я малость перемудрил: я добавил в конфиг процессора ключ:
case DTSTimebase => BigInt(0)
… ориентируясь на то, что в комментарии было сказано «если не знаете — оставьте 0». И ведь WithNBigCores
как раз проставляло его в 1MHz (как, кстати, и было указано в конфиге U-Boot). Но я же, блин, аккуратный и дотошный: там я не знаю, тут 25MHz! В итоге ничего не работает. Убрал свои «улучшения» и…
Hit any key to stop autoboot: 0
MMC_SPI: 0 at 0:1 hz 20000000 mode 0
## Unknown partition table type 0
libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
** No partition table - mmc 0 **
## Info: input data size = 34 = 0x22
Running uEnv.txt boot2...
## Error: "boot2" not defined
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
# below much match what's in FIT (ugha)
Что же, приведём все файлы в соответствие, заменим командную строку ядра приблизительно так, поскольку есть подозрения, что SIF0
— это вывод куда-то по PCIe:
-bootargs=console=ttySIF0,921600 debug
+bootargs=console=ttyS0,125200 debug
И до кучи поменяем алгоритм хеширования с SHA-256 на MD5: криптостойкости мне не нужно (тем более, при отладке), считается оно жутко долго, а для отлова ошибок целостности при загрузке и MD5 — за глаза. Что же в итоге? Проходить предыдущий уровень мы стали заметно быстрее (за счёт более простого хеширования), и открылся следующий:
...
Verifying Hash Integrity ... md5+ OK
Loading loadables from 0x90a5a758 to 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
chosen {
linux,initrd-end = <0x00000000 0x83000000>;
linux,initrd-start = <0x00000000 0x82000000>;
riscv,kernel-end = <0x00000000 0x80a00000>;
riscv,kernel-start = <0x00000000 0x80200000>;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
};
libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
chosen {
linux,initrd-end = <0x00000000 0x83000000>;
linux,initrd-start = <0x00000000 0x82000000>;
riscv,kernel-end = <0x00000000 0x80a00000>;
riscv,kernel-start = <0x00000000 0x80200000>;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
};
Loading Kernel Image ... OK
Booting kernel in
3
Вот только часы не тикают…
(gdb) x/x 0x0200bff8
0x200bff8: 0x00000000
Упс, похоже, исправление хода часов оказалось плацебо, хотя мне тогда и показалось, что помогло. Нет, починить, конечно надо, но давайте для начала покрутим стрелки вручную и посмотрим, что получится:
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=1000000
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=2000000
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x00000000bff6dbb0 in ?? ()
(gdb) set variable *0x0200bff8=3000000
(gdb) c
Continuing.
Тем временем…
Loading Kernel Image ... OK
Booting kernel in
3
2
1
0
## Starting application at 0x80000000 ...
Нет уж, пойду автоматизировать ход часов — а то, может, он там таймер калибровать вздумает!
А адрес текущей инструкции тем временем указывает куда-то в
0000000080001c20 <poweroff>:
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 <softfloat_countLeadingZeros8+0x558>
80001c2e: e406 sd ra,8(sp)
80001c30: f7fff0ef jal ra,80001bae <printm>
80001c34: 8522 mv a0,s0
80001c36: 267000ef jal ra,8000269c <finisher_exit>
80001c3a: 00010797 auipc a5,0x10
80001c3e: 41e78793 addi a5,a5,1054 # 80012058 <htif>
80001c42: 639c ld a5,0(a5)
80001c44: c399 beqz a5,80001c4a <poweroff+0x2a>
80001c46: 72c000ef jal ra,80002372 <htif_poweroff>
80001c4a: 45a1 li a1,8
80001c4c: 4501 li a0,0
80001c4e: dc7ff0ef jal ra,80001a14 <send_ipi_many>
80001c52: 10500073 wfi
80001c56: bff5 j 80001c52 <poweroff+0x32>
внутри загрузившегося Berkeley Boot Loader. Лично меня в этом смущает упоминание htif
— host interface, используемого для tethered-запуска ядра (то есть в кооперации с хостовым ARM), я-то предполагал standalone. Впрочем, если найти эту функцию в исходниках, то видно, что не всё так плохо:
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ГГц, я оптимист :)
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 = <1>;
#size-cells = <0>;
...
}
и поправил генерацию значений: не дописывать 0x0
первым элементом.
Эти 100500 простых шагов позволят легко и просто посмотреть, как падает пингвин:
Скрытый текст
Verifying Hash Integrity ... md5+ OK
Loading loadables from 0x90a5a758 to 0x82000000
libfdt fdt_check_header(): FDT_ERR_BADMAGIC
chosen {
linux,initrd-end = <0x83000000>;
linux,initrd-start = <0x82000000>;
riscv,kernel-end = <0x80a00000>;
riscv,kernel-start = <0x80200000>;
#address-cells = <0x00000001>;
#size-cells = <0x00000000>;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
libfdt fdt_path_offset() returned FDT_ERR_NOTFOUND
chosen {
linux,initrd-end = <0x83000000>;
linux,initrd-start = <0x82000000>;
riscv,kernel-end = <0x80a00000>;
riscv,kernel-start = <0x80200000>;
#address-cells = <0x00000001>;
#size-cells = <0x00000000>;
bootargs = "debug console=tty0 console=ttyS0,125200 root=/dev/mmcblk0p2 rootwait";
stdout-path = "uart0:38400n8";
};
Loading Kernel Image ... OK
Booting kernel in
3
2
1
0
## Starting application at 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: Ignoring memory range 0x80000000 - 0x80200000
[ 0.000000] Linux version 4.19.0-sifive-1+ (trosinenko@trosinenko-pc) (gcc version 8.3.0 (Buildroot 2019.02-07449-g4eddd28f99)) #1 SMP Wed Jul 3 21:29:21 MSK 2019
[ 0.000000] bootconsole [early0] enabled
[ 0.000000] Initial ramdisk at: 0x(____ptrval____) (16777216 bytes)
[ 0.000000] Zone ranges:
[ 0.000000] DMA32 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Normal [mem 0x00000000c0000000-0x00000bffffffffff]
[ 0.000000] Movable zone start for each node
[ 0.000000] Early memory node ranges
[ 0.000000] node 0: [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] Initmem setup node 0 [mem 0x0000000080200000-0x00000000bfffffff]
[ 0.000000] On node 0 totalpages: 261632
[ 0.000000] DMA32 zone: 3577 pages used for memmap
[ 0.000000] DMA32 zone: 0 pages reserved
[ 0.000000] DMA32 zone: 261632 pages, LIFO batch:63
[ 0.000000] software IO TLB: mapped [mem 0xbb1fc000-0xbf1fc000] (64MB)
(эмблему выводит BBL, а то что с метками времени — ядро).
К счастью, не знаю, как везде, но на RocketChip при подключении отладчика по JTAG можно ловить trap-ы из коробки — отладчик остановится ровно в этой точке.
Program received signal SIGTRAP, Trace/breakpoint trap.
0xffffffe0000024ca in ?? ()
(gdb) bt
#0 0xffffffe0000024ca in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) file work/linux/vmlinux
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from work/linux/vmlinux...done.
(gdb) bt
#0 0xffffffe0000024ca in setup_smp () at /hdd/trosinenko/fpga/freedom-u-sdk/linux/arch/riscv/kernel/smpboot.c:75
#1 0x0000000000000000 in ?? ()
Backtrace stopped: frame did not save the 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 not found, running software emulation. Ну или не running. Заблудились в единственном ядре процессора.
/* 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