
Всем привет
В начале февраля, я участвовал в курсах повышения квалификации для преподавателей от Альянса RISC-V. В рамках курса демонстрировался пакет разработчика (development tools) компании Syntacore, который содержит эмулятор QEMU с добавленными ядрами (платформами) . В день, когда мы выступали я успел добавить начальную поддержку одной из этих платформ в Embox и показал, как он запускается на эмуляторе из этого пакета.
По завершении курсов я решил добиться запуска консоли на данной платформе и написать пошаговую инструкцию, которая не только помогла бы понять, как добавить поддержку новой платформы в Embox, но и объясняла, какие вообще аппаратные части требуются для любой ОС. Таким образом, эта инструкция могла бы использоваться в качестве обучения студентов системному программированию и архитектуре RISC-V . В результате и появилась данная статья.
Начинаем
Изначально я добавлял платформу src4, на которой демонстрировались примеры. Но в дальнейшем я решил посмотреть, какие же платформы (ядра) поддерживает эмулятор. Начнем, естественно, с 32-разрядной версии.
Сделать это можно с помощью команды (находясь в папке ‘sc-dt/tools/bin’)
./qemu-system-riscv32 -M help
(Если у вас установлен пакет разработчика, то qemu у вас будет в путях и ./ не нужно)

Видно, что добавлено несколько машин
scr1 RISC-V Board with SCR1 CPU
scr3 RISC-V Board with SCR3 CPU
scr4 RISC-V Board with SCR4 CPU
scr5 RISC-V Board with SCR5 CPU
scr_sdk RISC-V Board compatible with Syntacore SCR SDK
В том числе scr1. То есть, с тем же ядром, что и в МК AMUR MIK32, который Embox успешно освоил ( детали в этой статье ). Логично начать с этой платформы.
Для сборки Embox нам потребуется кросс-компилятор, я буду использовать уже установленный riscv64-unknown-elf-gcc (версия 13.2.0), но можно использовать кросс-компилятор из состава пакета для разработчика.
Минимальная конфигурация
Давайте поймем, что требуется для сборки и запуска любой программы (даже пустой). В обычном прикладном компиляторе эти вещи скрыты под капотом, но поскольку мы занимаемся системным программированием, нам необходимо это знать, чтобы модифицировать. Итак нужны:
Компилятор и флаги, с которыми собирается наша программа.
Стартовый код, с которого начинается выполнение программы еще до нашего кода, и в котором есть передача управления на наш код. В обычном компиляторе этот код содержится в файле crt0.S
Карта памяти, которая описывает, по каким адресам располагать команды, по каким данные и так далее. Эти данные описываются в линкер-скрипте.
Имея эти части мы можем собрать программу, запустить ее в отладочном режиме, поставить breakpoint на функцию main и, стартовав программу, остановиться на ней. Все эти части присутствуют в любой ОС, в том числе Embox, но там они представлены в явном виде.
Флаги компилятора
В Embox флаги компилятора и сам компилятор (его тоже можно задать, например, чтобы использовать llvm) задаются в файле build.conf
В нашем случае поставим базовые настройки для RISC-V
TARGET = embox
ARCH = riscv
PLATFORM = syntacore_scr1
CROSS_COMPILE = riscv64-unknown-elf-
CFLAGS += -g -O0
CFLAGS += -march=rv32gc -mabi=ilp32
LDFLAGS += -melf32lriscv
Надеюсь, пояснения излишни. Отмечу 2 неочевидные и необязательные вещи.
TARGET позволяет задавать имя образа в elf формате. По умолчанию он установлен как Embox.
PLATFORM указывает условное и необязательное название платформы (нам оно потребуется позже, при автоматизации запуска).
Архитектурная часть
Про стартовый код я уже упоминал, но так как любая ОС, в том числе и Embox, работает в так называемом bare-metal режиме (на голом железе), то нам нужны еще несколько стандартных архитектурых частей:
Таблица прерываний
Первичные обработчики прерываний и исключений
Кроме стандартных частей в Embox еще потребуются стандартные архитектурно-специфичные функции setjmp/ longjmp – они используются процессе загрузки модулей.
Поскольку это стандартные части, то в Embox никакого кода писать не требуется, достаточно указать нужные части в конфигурации в файле с описанием требований mods.conf
include embox.arch.riscv.kernel.boot(mik32_fixup=false)
include embox.arch.riscv.kernel.cpu_idle
include embox.arch.riscv.kernel.locore
include embox.arch.riscv.kernel.interrupt
include embox.arch.riscv.libarch
Я сознательно выделил mik32_fixup, поскольку думал, что он может нам потребоваться в будущем.
Карта памяти
Карта памяти– это уже платформенная часть, и эту информацию нам нужно где-то получить. Поскольку в пакет разработчика входят не только QEMU, но и примеры для работы с этими платформами, нужная нам информация обязательно там есть.
Искать нужно файлы линкер скриптов, обычно имеющие расширение ld. В нашем случае я обнаружил файлы mem.ld в папках
./sc-dt/workspace/scr-hal/platform/<PLATFORM_NAME>
То есть для нашей платформы scr1 нужный файл
sc-dt/workspace/scr-hal/platform/scr1/mem.ld
заглянув в него увидел, что есть регион
MEMORY {
TCM (rwx) : ORIGIN = 0xF0000000, LENGTH = 128K
}
REGION_ALIAS("REGION_TEXT", TCM);
REGION_ALIAS("REGION_RODATA", TCM);
REGION_ALIAS("REGION_DATA", TCM);
REGION_ALIAS("REGION_BSS", TCM);
REGION_ALIAS("REGION_STACK", TCM);
Это как раз то, что нам нужно. В Embox наш файл ‘lds.conf’ будет выглядеть так:
RAM (0xF0000000, 128K)
/* section (region[, lma_region]) */
text (RAM)
rodata (RAM)
data (RAM)
bss (RAM)
Контроллер прерываний
В Embox можно подключать различные контроллеры прерываний. Даже можно сделать конфигурацию без них. Но поскольку в RISC-V есть достаточно стандартный контроллер прерываний, и его драйвер реализован в Embox, я использовал его в файле mods.conf, оставив закомментированным вариант без контроллера прерываний
/* include embox.driver.interrupt.no_interrupts */
@Runlevel(1) include embox.driver.interrupt.riscv_plic
Таймер
С системным таймером тот же подход, что и для контроллера прерываний, просто пока я использовал стандартный контроллер для RISC-V, хотя можно было вообще его отключить. Тоже задается в файле mods.conf
include embox.driver.interrupt.riscv_clint
include embox.driver.clock.riscv_clk
include embox.kernel.time.jiffies(cs_name="riscv_clk")
DIAG интерфейс
DIag интерфейс в Embox- это очень простой интерфейс для форматированного вывода (printk) на очень раннем этапе загрузки системы. Он может быть отключен, может быть записан в память, а может быть реализован в виде последовательного порта.
Я заглянул в папку ‘./sc-dt/workspace/scr-hal/platform/scr1’ (папку, где я обнаружил файл с описанием карты памяти для нужной платформы) и там увидел еще несколько файлов, похожих на конфигурационные. В частности заголовочный файл ‘plf.h’, в котором я увидел следующее:
#define PLF_MMIO_BASE (0xff000000)
…
// FPGA UART port
#define PLF_UART0_BASE (PLF_MMIO_BASE + 0x10000)
#define PLF_UART0_16550
То есть, у нашей платформы есть UART, его адрес 0xff010000, тип 16550. Данный тип UART поддерживается в Embox, так что я просто добавил его в файл mods.conf, указав базовый адрес, который я узнал из макросов.
/* include embox.driver.diag.mem_diag
include embox.driver.diag(impl="embox__driver__diag__mem_diag")
*/
include embox.driver.serial.ns16550
include embox.driver.serial.ns16550_diag(base_addr=0xff010000)
include embox.driver.diag(impl="embox__driver__serial__ns16550_diag")
Системные требования
Для самого первого запуска нам больше ничего не нужно, и поэтому остальные части в mods.conf я отключил - и многозадачность, и кучу, и так далее.
include embox.kernel.sched.priority.none
include embox.kernel.sched.current.default
include embox.kernel.sched.timing.none
include embox.kernel.sched.sched_ticker_stub
include embox.kernel.lthread.lthread
Прокомментирую одну строчку
include embox.framework.mod(
use_mod_depends=false,
use_mod_labels=false,
use_mod_logger=false,
use_mod_names=true,
use_cmd_details=false)
У нас очень конфигурируемая система, причем она собирается статически, и внутри можно хранить достаточно подробную информацию о модулях, которая, естественно, потребляет ресурсы. Я отключил почти все, оставив только имя модуля.
Готовый базовый темплейт
На самом деле я использовал существующий темплейт ‘riscv/minimal’, то есть скопировал его в папку platform/syntacore/templates/scr1_32 и сделал там несколько модификаций, специфичных для платформы (карта памяти и адрес UART). Что ожидаемо ускорило процесс. Я детально расписывал все остальные шаги, чтобы показать сам процесс рассуждений.
Собираем и запускаем
Поскольку свою новую платформу я положил в 'platform/syntacore/templates/scr1_32', то чтобы ее сконфигурировать (сделать активной) можно выполнить:
make confload-platform/syntacore/scr1_32
Собираем
make
И теперь запускаем собранный образ Embox на нашем qemu, указав, что выбрана машина scr1. Указываем ‘-nographic’, чтобы наш UART перенаправился на stdin stdout.
./qemu-system-riscv32 -M scr1 -kernel ~/embox/build/base/bin/embox -nographic

Мы успешно запустили минимальный образ. Выйти из QEMU в режиме nographic можно с помощью комбинации: CTRL + ‘a’ затем ‘x’
После запуска минимального образа я выложил изменения коммитом
Добавляем базовую периферию
После того как мы запустили наш минимальный образ на нужной машине, мы можем приступить к добавлению и настройке тех аппаратных частей, которые мы пропустили. Напомню: это прежде всего контроллер прерываний и таймер.
Для того, чтобы получить дополнительную информацию, давайте опять запустим наш образ с нашей машиной и там войдем в служебную консоль QEMU. В режиме nographic это делается комбинацией клавиш: CTRL + ‘a’ затем ‘x’.
В служебной консоли можно набирать команды, например, есть команда ‘info’, которая может дать нам различную информацию.
Например команда
info mtree
выведет информацию о регионах памяти (в том числе для периферии)

Видно, что есть наш регион памяти, куда мы и разместили образ
00000000f0000000-00000000f001ffff (prio 0, ram): riscv.syntacore.tcm
Есть еще регион памяти с адреса 0, который нам потенциально может быть интересен
Но вернемся к нему чуть позже. Пока посмотрим на таймер.
Таймер
Напомню: я оставил таймер для RISC-V по умолчанию. Но похоже тут используется другой таймер
00000000f0040000-00000000f0040fff (prio 0, i/o): riscv.scr.mtimer
Давайте выведем еще информацию о модели устройств командой
info qtree

Да, здесь используется некий scr.mtimer. Давайте найдем пример драйвера в исходниках SDK.
Начнем с нашего платформенного заголовочного файла, сдаю в котором найдем
#define PLF_MTIMER_BASE (0xf0040000)
Поищем грепом, где используется данный макрос, и увидим заголовочный файл, содержащий драйвер нужного таймера ./scr-hal/include/drivers/mtimer.h
Структура регистров
typedef struct scr_mtimer_struct {
uint32_t ctrl;
uint32_t div;
#if __riscv_xlen == 32
uint32_t time;
uint32_t timeh;
uint32_t cmp;
uint32_t cmph;
#else // __riscv_xlen == 32
uint64_t time;
uint64_t cmp;
#endif // __riscv_xlen == 32
} volatile scr_mtimer;
И тут я вспомнил, что точно такая же структура была у AMUR MIK32.
То есть для нашего использования нужного драйвера нужно просто поменять его в mods.conf
Вместо
include embox.driver.interrupt.riscv_clint
include embox.driver.clock.riscv_clk
include embox.kernel.time.jiffies(cs_name="riscv_clk")
Добавим
include embox.driver.clock.syntacore_mtimer(base_addr=0xf0040000)
include embox.kernel.time.jiffies(cs_name="syntacore_mtimer")
Выравнивание таблицы прерываний
После каждого коммита конечно нужно проверять, что у нас все еще запускается наш образ. И после добавления mtimer я увидел, что образ стартует, но в какой-то момент зависает. Все указывало на то, что происходит прерывание от таймера (я проверил это меняя его частоту), но на адрес таблицы прерываний управление не передается.
Похожую картину мы видели при портировании на AMUR MIK32, и даже делали fixup, который бы корректировал значение регистра mtvec. Проверив, что вариант с включением этого фиксапа не работает, я посмотрел содержимое регистров в служебной консоли qemu командой
info registers

И очень быстро выяснил, что младшие 6 битов этого регистра всегда установлены в ноль.
В mods.conf я поставил выравнивание таблицы прерываний на 6 и все заработало
include embox.arch.riscv.kernel.entry(trap_align=6)
То есть, перестало зависать, и я мог в gdb поставить точку остановки на входе в прерывание и оказаться там.
Дополнительный регион памяти
Когда мы изучали регионы памяти с помощью команды ‘info mtree’, мы обнаружили дополнительный регион памяти, начинающийся с адреса 0. Давайте его использовать, чтобы можно было добавить больше функциональности и не заботиться о памяти. Ее у нас пока 128КБ на все секции.
Давайте посмотрим в наш платформенный заголовочный файл plf.h и увидим
#define PLF_MEM_BASE (0x0)
#define PLF_MEM_SIZE (256UL*1024)
#define PLF_MEM_ATTR (0)
#define PLF_MEM_NAME "DDR"
То есть, у нас есть регион памяти, начинающийся с 0 и размером 256КБ. Изменим наш lds.conf, добавив регион ROM
ROM (0x00000000, 256K)
RAM (0xF0000000, 128K)
text (ROM)
rodata (ROM)
data (RAM, ROM)
bss (RAM)
Контроллер прерываний
Возможно, кто-то обратил внимание, что при команде ‘info qtree’ показался еще некий riscv.syntacore.ipic

Это нужный нам контроллер прерываний. Найдем его командой find
find . -name *ipic*
Он лежит в ‘./scr-hal/include/drivers/ipic.h’. Такого драйвера в Embox еще нет. Просто добавим его.
Не буду приводить весь код - лучше посмотреть в исходниках, он достаточно простой и понятный. Отмечу только, что нужно использовать макрос IRQCTRL_DEF для объявления драйвера контроллера прерываний в системе
IRQCTRL_DEF(syntacore_ipic, syntacore_ipic_init);
И теперь, просто заменим в mods.conf plic на наш новый драйвер
уберем
include embox.driver.interrupt.riscv_plicд
добавим
include embox.driver.interrupt.syntacore_ipic(scr_ver=1)
Системная частота
Когда я смотрел наш платформенный заголовочный файл plf.h - я там увидел следующий макрос
#define PLF_SYS_CLK 90000000
Это системная частота, которая нужна для корректной работы таймера 90MHz. Давайте ее поставим в нашем таймере, модифицировав опцию rtc_freq для нашего таймера в mods.conf
include embox.driver.clock.syntacore_mtimer(base_addr=0xf0040000, rtc_freq=90000000)
Добавляем функционал
Добавив достаточно периферии для полноценной работы, приступим к добавлению функциональности.
Простейшая командная строка
У нас есть примитивный шел, который может работать по опросу, то есть мы могли добавлять данный функционал даже без таймера и контроллера прерываний. Но конечно, к этим шагам пришлось бы возвращаться, и поэтому я показал сначала добавление драйверов.
Итак, давайте немного расширим возможности нашего ядра, добавив хотя бы кооперативную многозадачность и добавим простейший шел. Все это делается модификацией mods.conf
Для ядра несколько строчек
Одна задача
include embox.kernel.task.single
Некоторые параметры планировщика
@Runlevel(2) include embox.kernel.sched.idle_light
@Runlevel(2) include embox.kernel.sched.boot_light
@Runlevel(2) include embox.kernel.sched.timing.none
@Runlevel(2) include embox.kernel.sched.strategy.priority_based
Прикладной уровень
@Runlevel(2) include embox.cmd.shell
include embox.init.setup_tty_diag
@Runlevel(3) include embox.init.start_script(shell_name="diag_shell")
include embox.cmd.help
…
include embox.cmd.testing.ticker
И поскольку мы добавили запуск стартового скрипта, то нужно добавить файл start_script.inc. Оставим его пустым.
Собираем и запускаем. Появляется консоль, там можно выполнять команды, которые мы добавили в mods.conf

Я добавил команду ticker для того, чтобы сразу убедиться, что время течет корректно, и наш таймер работает на нужной частоте
ticker -C 10
Должно выводить следующую строчку раз в секунду (10 раз)

Многозадачность
Немного еще расширим многозадачность, чтобы уже можно было использовать разные задачи (процессы)
include embox.kernel.task.multi
include embox.kernel.task.resource.idesc_table(idesc_table_size=8)
include embox.kernel.task.resource.sig_table(sig_table_size=20)
include embox.kernel.task.resource.env(env_per_task=4,env_str_len=64)
При этом мне пришлось увеличить размер стека
include embox.kernel.stack(stack_size=0x1000)
include embox.kernel.thread.core(thread_pool_size=5, thread_stack_size=0x1000)
Файловая система
Ну какая же ОС без работы с файлами ? :)
Тем более, что мы гордимся, что умеем работать с файловой системой прямо внутри микроконтроллера (Детали в статье ).. Но в рамках данной статьи просто добавим корневую read-only файловую систему.
include embox.compat.posix.file_system_dvfs
include embox.fs.rootfs_dvfs(fstype="initfs")
include embox.fs.driver.initfs_dvfs(file_quantity=32)
include embox.fs.driver.devfs_dvfs
Добавим также кучу (16кБ), ведь до этого мы ее не использовали
include embox.mem.heap_bm
include embox.mem.static_heap(heap_size=0x4000)
И конечно немного расширим список команд для работы с файлами
include embox.cmd.fs.cat
include embox.cmd.fs.ls

Добавляем устройство /dev/ttyS0
У embox есть devfs, прямо как в Линукс. На данный момент мы для ввода и вывода используем UART 165550, но как DIAG интерфейс, который на ввод работает по опросу. Мы же добавили контроллер прерываний (внешних) и можем использовать данное устройство уже по прерыванию, ( при чтении поток будет засыпать пока не придут данные, и тогда его разбудят).
Для добавления устройства достаточно добавить строчку в mods.conf
nclude embox.driver.serial.ns16550_ttyS0(base_addr=0xff010000, irq_num=0)
И наше устройство появится в папке /dev, и его можно будет использовать
Маленькое уточнение на счет номера прерывания (irq_num=0). Его я узнал из нашего платформенного заголовочного файла plf.h
// external interrupt lines
#define PLF_INTLINE_UART0 0К
Командный интерпретатор с устройством /dev/ttyS0
Давайте проверим уже почти полноценную систему. Будем использовать немного другой шел и устройство ttyS0
Уберем строчки
@Runlevel(2) include embox.cmd.shell
include embox.init.setup_tty_diag
@Runlevel(3) include embox.init.start_script(shell_name="diag_shell")
И добавим
@Runlevel(3) include embox.init.start_script(shell_name="tish", tty_dev="ttyS0")
include embox.cmd.sh.tish(
builtin_commands = "exit logout service"
)

Все работает корректно, и можно считать, что новая платформа добавлена в Embox.
Немного о флагах компилятора
Совсем немного поговорим о флагах компилятора. Как я уже сказал, платформенные настройки в пакете разработчика от Syntacore лежат в папке ‘sc-dt/workspace/scr-hal/platform/<PLATFROM NAME>’.
Файл линкер скрипта и заголовочный файл мы уже рассматривали. Есть еще пара файлов “plf.ini” и “plf.cmake”. “Plf.ini” - файл с описанием платформы.
Мы можем получить информацию о том, поддерживаются ли атомарные операции или FPU
description=Syntacore SCR1 RV32 Core IP @ Xilinx Virtex UltraScale+ FPGA VCU118 Evaluation Kit
core=scr1
isa=rv32im
isa.fpu=none
isa.atomic=false
isa.rvc=false
mem=mem
mem.mem=TCM [0xF0000000-0xF001FFFF]
mem.ram.ld=mem.ld
plf.cmake нас интересует больше, поскольку в нем описаны флаги компилятора, которые соответствуют данной платформе
set(MARCH rv32im)
set(MARCH_OPTIONAL zifencei zicsr)
set(MABI ilp32)
Напомню: флаги компиляторы в embox описываются в build.conf, и пока у нас стоят следующие
CFLAGS += -march=rv32gc -mabi=ilp32
То есть базовые флаги для архитектуры RISC-V (32 битной версии).
Видно, что mabi (ipl32) совпадают, а для march можно поставить более точные настройки
CFLAGS += -march=rv32im_zicsr_zifencei
Я поставил данные флаги, но получил сообщение о переполнении региона ROM на 20 КБ. Для проверки я включил оптимизацию (-O1), и все собралось и заработало. Я решил оставить корректные флаги закомментированными и идти дальше.
Автоматизируем запуск QEMU
Для работы с QEMU в Embox есть скрипт, который запускает нужное QEMU с заданными параметрами. Поэтому запуск происходит вызовом одного скрипта
./scripts/qemu/auto_qemu.
Он по сути анализирует текущую конфигурацию, находящуюся в папке ./conf, в частности он анализирует переменную PLATFORM из файла build.conf .
Напомню: у нас она равна syntacore_scr1
PLATFORM = syntacore_scr1
Добавив в анализ нашу новую платформу, мы можем работать в той же папке, что и Embox, собрав и сразу запустив. Я использую не установленную версию QEMU и поэтому укажу с помощью переменной AUTOQEMU_PREFIX, где лежит наше qemu

Все, теперь работать в Embox и запускать можно из одной и той же папки, например из консоли VSCode.
Добавляем платформу SCR4_RV32
Теперь давайте вернемся к платформе SCR4, которую я и начал запускать на курсах.
Для начала просто скопируем наш темплейт scr1_32, поменяв только название платформы в build.conf – это только для того, чтобы воспользоваться нашим скриптом ./scripts/qemu/auto_qemu.
Естественно, что все продолжает работать.
Посмотрим, что же нам нужно подкорректировать. За базовую платформу в пакете разработчика Syntacore возьмём scr4_rv32. Напомню, что платформо-специфичные части лежат в папке ‘sc-dt/workspace/scr-hal/platform/<PLATFROM NAME’.
Карта памяти
Напомню, что регион ROM я нашел в платформенном заголовочном файле plf.h
#define PLF_MEM_BASE EXPAND32ADDR(0)
#define PLF_MEM_SIZE (2UL*1024*1024*1024)
То есть нам на данной плате доступен регион 2 MB. Поправим в lds.conf
ROM (0x00000000, 2M)
Контроллер прерываний
Напомню, что в ipic есть две версии: для SCR1 он поддерживает 16 прерываний, а в остальных случаях 32. Этот параметр указывается в mods.conf
include embox.driver.interrupt.syntacore_ipic(scr_ver=4)
Увеличим этот параметр для внутреннего пула возможных логических прерываний (Embox может работать с например расшаренными прерываниями, прямо как в Линукс)
include embox.kernel.irq(action_n=32, entry_n=32)
Флаги компилятора
Можно оставить базовые флаги, но давайте попробуем использовать рекомендуемые CFLAGS. Заглянем в plf.cmake
set(MARCH rv32imfdc)
set(MARCH_OPTIONAL zifencei zicsr)
set(MABI ilp32d)
Это ядро поддерживает работу с плавающей точкой двойной точности (double). Это видно из последнего символа в MABI ilp32d.
Работа с плавающей точкой - это отдельная большая тема, мы написали отдельную статью по работе с ней для ARM (Почти все, что вы хотели знать про плавающую точку в ARM, но боялись спросить ), поэтому давайте пока не будем ее включать, и будем работать только с целочисленной арифметикой.
В build.conf
CFLAGS += -march=rv32imfdc_zifencei_zicsr -mabi=ilp32
/* with FPU */
Системная частота
Если в SCR1 частота задавалась просто макросом, то для SCR4 частота зависит от значения некого регистра, который, вероятно, записывается после синтеза FPGA. Это я выяснил из платформенного заголовочного файла plf.h
#define PLF_SYSCLK_MHZ_ADDR (PLF_MMIO_BASE + 0x2000)
#define VarClkMHz (*(const uint32_t*)(PLF_SYSCLK_MHZ_ADDR) * 1000000)
#ifndef PLF_SYS_CLK
#define PLF_SYS_CLK VarClkMHz
#endif
Далее я поставил системную частоту 75MHz и время, проверенное командой ticker, стало течь более менее точно
include embox.driver.clock.syntacore_mtimer(base_addr=0xf0040000, rtc_freq=75000000)
Все, этого достаточно чтобы платформа SCR4_RV32 заработала.
Добавляем платформу SCR4_RV64
Ну и в конце давайте запустим Embox на SCR4, но на 64-разрядной версии.
Скопируем наш темплейт scr4_32 в scr4_64. Очевидно, что в build.conf поменяется не только название платформы, но и флаги компилятора, линкера и так далее.
Файл build.conf (Флаги компилятора)
Содержимое файла build.conf
ARCH = riscv64
PLATFORM = syntacore_scr4
CROSS_COMPILE = riscv64-unknown-elf-
CFLAGS += -g -O0
CFLAGS += -march=rv64imfdc_zifencei_zicsr
CFLAGS += -mabi=lp64d
LDFLAGS += -melf64lriscv
Напомню: флаги можно узнать в платформенном cmake plf.cmake
Карта памяти
Проверив mem.ld я увидел, что адреса должны быть расширены.
То есть, вместо
RAM (0xF0000000, 128K)
нужно
RAM (0xFFFFFFFFF0000000, 128K)
Также я заглянул в платформенный заголовочный файл plf.h увидел, что доступен регион 4 MB. Поправим в lds.conf
ROM (0x00000000, 4M)
Расширение адресов для периферии
Поскольку адреса периферии нужно также скорректировать (расширить), я сделал соответствующие правки в mods.conf. Забегая вперед скажу, что изначально я дополнил адреса mtimer и UART 0xFFFFFFFFxxxxxxxx, но дальше посмотрел в служебной консоли QEMU оставил 0x00FFFFFFxxxxxxxx. Работали оба варианта.
Контроллер прерываний
Запустив полученный темплейт, я увидел сообщение об исключенной ситуации (exception)

Посмотрев в дизассемблере (make disasm), где происходит исключение (PC = 0x79e), я понял, что это функция (ipic_irq_setup) в контроллере прерываний ipic. Я открыл командную консоль QEMU и вывел командой ‘info qtree’

На этой платформе используется контроллер прерываний PLIC, а не IPIC, как на 32-разрядной версии. Поменяв контроллер прерываний в mods.conf
//include embox.driver.interrupt.syntacore_ipic(scr_ver=4)
include embox.driver.interrupt.riscv_plic(base_addr=0x00fffffffe000000)
Я увидел командную строку

Все и еще одна платформа добавлена.
Правда если внимательно посмотреть на последний скриншот, то можно заметить, что используется diag интерфейс для ввода-вывода. Это связано с тем, что мне быстро не удалось получить прерывание от UART и я решил повесить issue на github, может кто нибудь захочет это поправить. Ведь дальнейших улучшений можно придумать очень много!
Завершаем
Я сознательно делал не точную последовательность действий, а показывал возможные пути решения различных задач. То есть, то чему в итоге и должны научится студенты. Так часть коммитов которые были сделаны по ходу данной статьи были добавлены отдельными PR, чтобы не засорять рассуждения. Но это тоже одно из возможных направлений при обучении, проект открытый и студенты могут также его развивать и улучшать, чем конечно будут сильно улучшать свои навыки в системном программировании и RISC-V архитектуре.
Все коммиты из данной статьи собраны в один PR и естетсвенно, теперь все можно скачать из репозитория проекта и воспроизвести самостоятельно.
Комментарии (8)
Olegun
20.02.2025 19:45Для тех кто хотел в комментариях предложить автору раскрыть что такое Embox сообщаю:
Embox (англ. Essential toolbox for embedded development) — свободная кросс-платформенная операционная система реального времени (RTOS), разрабатываемая для встроенных систем.abondarev Автор
20.02.2025 19:45Спасибо. Но подумал, если начинать с этого каждый раз, то до чего то осмысленного никогда не дойдем :)Прогаем АСУ-ТП ( МЭК 61131-3 языки) на базе отечественного MK (https://habr.com/ru/articles/881784/)
unreal_undead2
20.02.2025 19:45Я смотрю код на гитхаб уже залили, надо бы тогда и описание подправить:
Embox supports the following CPU architectures: x86, ARM, Microblaze, SPARC, PPC, MIPS.
checkpoint
Антон, спасибо за статью. Я вижу примерно следующий минимальный план действий при портировании Embox на свою СнК:
Разработка (портирование) драйвера PLIC под свой контроллер.
Разработка (портирование) драйвера UART.
Портирование примитивов для работы с машинным таймером.
Портирование примитивов для работы с системным таймером.
Создание правильного плана размещения сегментов в памяти (linker.ld), план адресации.
По последнему пункту не совсем понятно может ли Embox (или приложение в её оставе) исполняться прямо из Flash ? На нашей СнК есть поддержка XiP, т.е. NOR flash память мапится в адресное пространство 0xA0000000. Вопрос в том, кто скопирует сегмент .data в RAM, а так же проинициализирует .bss, .stack и .heap. Хотелось бы увидеть общую картину как располагаются в памяти разные части Embox.
abondarev Автор
Да, в первом приближении все так, нужен только машинный таймер, и как показано встатье, на первом этапе можно вообще без прерываний, только lds архитектурная подерка и UART ( какой то интерфейс чтобы видеть что есть результат) и build тоже стандартный
Да, можно исполнять прямо из flash. ROM регион
Embox копирует секцию .data (если нужно) и зануляет .bss, устанавливает указатель стека (размер стека задается в конфиге), указатели стека для потоков устанавливаются отдельно Embox, кучу инициализирует тоже embox (если нужно).
checkpoint
Антон, что такое lds ? :)
abondarev Автор
Ой, lds.conf из которого генерится линкер скрипт (карта памяти ) :)