LANDAU: Мы сделали BE-U1000 в QEMU

LANDAU (Linux And Neo-Depositorium Associated Unity) - это зонтичный проект RULKC, который объединяет усилия российского open-source сообщества вокруг ключевых системных компонентов: ядро (LANDAU Linux), загрузчик (LANDAU U-Boot), UEFI (LANDAU EFI), эмуляция и виртуализация (LANDAU QEMU) и другие. Название - в честь Льва Ландау: как он собирал физиков вокруг новых идей, так LANDAU собирает разработчиков вокруг общей технологической базы.
Подробности и ссылки - на landau.one.
Модель BE-U1000 - одно из первых крупных направлений LANDAU QEMU. Что это значит на практике:
Если хотите просто пользоваться - уже сейчас можно собирать примеры из
mcu-sdk-examplesи гонять их в QEMU: UART, CLIC, MTIMER, CRU/RESET и semihosting работают.Если хотите поучаствовать - в таблице ниже есть задачи любой сложности: от GPIO и WDT до DMA, CAN FD и MicroPython. Простая задача - та, у которой есть пример в SDK и критерий готовности понятен заранее, следовательно спорить о нём не придётся. За собой задачу можно застолбить письмом на
rulkc@linuxtesting.org- мы с Ильей Чичковым (@redeyedev) поможем, подскажем по коду QEMU и закрепим модуль за вами. Особенно хорошо, если по итогу появится небольшая статья, задокументированный опыт по написанию модуля для QEMU ценен.Если никто не возьмётся - ничего не встанет, будем писать по модулю не реже раза в месяц и каждый выкладывать с пояснением. Проект не зависит от того, найдётся ли соавтор, но с соавторами он пойдёт заметно быстрее и интереснее.
BE-U1000
Короткая справка для тех, кто впервые слышит об этом чипе.
BE-U1000 (Baikal-U) это российский 32-битный трёхъядерный микроконтроллер на открытой архитектуре RISC-V. Внутри два ядра от CloudBEAR BR-350 (до 200 МГц) и одно BM-310 (100 МГц), 256 КБ eFLASH, 192–202 КБ SRAM с TCM-памятью, а также широкая линейка периферии: 8 UART, 4 I²C, 2 CAN FD, USB 2.0 OTG, три 12-битных АЦП и блоки для управления электродвигателями, а также Микро-Питон (что бы это ни значило).

Именно для него модель в QEMU мы и делаем - чтобы разрабатывать и запускать прошивки без живой платы (получить ответ на вопрос “Зачем?”, можно в статье GD32 Unleashed: тотальная эмуляция в QEMU).
На мой взгляд он интересен еще тем, что:
в наличии есть два CLIC (Core-Local Interrupt Controller), причём per-hart не только CSR, что и так понятно, но и MMIO-регионы для управления;
гетерогенность - три харта двух разных типов: два BR350 (CORE0/CORE1) и BM310 (CORE2), причём некоторые особенности CORE0/CORE1 наводят на мысль, что и они специально сделаны для того, чтобы запускать на них независимые прошивки;
mailbox, редко встретишь на микро-контроллерах;
минимальные отличия от спеки RISC-V, всего несколько кастомных CSR (
nmitvec0x7c0,xccfg0xbc8,xicfg0xbd8);
Карточка проекта
Что | модель SoC BE-U1000 и машина EVU-BA-2.5 ( |
QEMU | landau/qemu, ветка |
Примеры | mcu-sdk-examples, 58 примеров |
Обёртка | qemu-be-u1000-suite репозиторий с QEMU и примерами как submodules ( |
Сборка | QEMU: |
Статус | готовы BR350 CORE0/CORE1, CLIC, CRU/RESET, MTIMER, UART; остальная периферия - заглушки |
Контакт |
|
Что уже реализовано
Блок | Что сделано |
|---|---|
BR350 × 2 (CORE0/CORE1) | У каждого харт своё приватное адресное пространство ( |
BM310 (CORE2) | Формально существует: CPU-тип |
CLIC | Своя копия на CORE0 и CORE1, draft 0.10 (без Debug Triggers и Indirect CSR - как и было задумано для be-u1000). |
CRU/RESET | Полное дерево тактирования (GATE/DIV/MUX/PLL) по блок-схеме SoC; Reset Controller как часть CRU (регистры SYSCR0/1/2): через них CORE1 и CORE2 выходят из холодного старта с резет-вектором, заданным прошивкой. |
MTIMER (CLINT-совместимый) | Per-hart view на CORE0/CORE1 того же типа, что и CLIC - один backing store, разные приватные адресные пространства |
UART (базовый) | Все 8 UART с адресами 1:1 со SVD, прерывание каждого разведено на оба CLIC, какой физический UART становится консолью - настраивается ( |
Архитектура модели (что подключено к чему)

У CORE0 и CORE1 свои ROM, CLIC и MTIMER по одному и тому же локальному адресу, но в разных адресных пространствах; периферия на общей шине видна всем. Прерывание каждого UART разведено на оба CLIC, а через SYSCR0/1/2 в CRU CORE1 и CORE2 стартуют с резет-вектором от прошивки.
Первые шаги
Как собрать примеры
Понадобятся репозитории mcu-sdk-examples и QEMU (ветки указаны в карточке проекта).
Вендорский тулчейн от Baikal нам не нужен (на самом деле он от CloudBEAR): достаточно пакетов gcc-riscv64-unknown-elf + picolibc-riscv64-unknown-elf (умеют собирать rv32* через multilib). Если системного пакета picolibc нет под нужный тулчейн, в репозитории mcu-sdk-examples есть PICOLIBC=local, который соберёт picolibc из подмодуля под произвольный TC_PREFIX.
Сама сборка:
mcu-sdk-examples $ make -j$(nproc) # с системным picolib
mcu-sdk-examples $ PICOLIBC=local make -j$(nproc) # с локальным picolib
mcu-sdk-examples $ ls -la build/*.elf
-rwxr-xr-x 1 maquefel users 659760 Oct 10 09:46 build/ADC_DiscontMode_Single_TriggerSW.elf
-rwxr-xr-x 1 maquefel users 662556 Oct 10 09:46 build/ADC_DiscontMode_Single_TriggerSW_IT.elf
-rwxr-xr-x 1 maquefel users 657568 Oct 10 09:46 build/ADC_RegularMode_Single_TriggerSW.elf
-rwxr-xr-x 1 maquefel users 660440 Oct 10 09:46 build/ADC_RegularMode_Single_TriggerSW_IT.elf
[...]
make -j$(nproc) в корне mcu-sdk-examples собирает все 58 примеров одним проходом.
Как собрать QEMU
Обычная конфигурация под riscv32-softmmu с добавлением фильтра по cpu --enable-trace-cpu-filter (о нём чуть позже):
$ mkdir build-qemu
$ cd build-qemu
$ ../qemu/configure --target-list=riscv32-softmmu --enable-trace-cpu-filter
$ ninja qemu-system-riscv32 # или просто make -j $(nproc) кому как больше нравиться
Запуск примера с UART
-kernel машиной пока не обрабатывается (это тоже открытая задача - см. таблицу ниже), поэтому прошивку грузим явно в EFLASH через generic loader:
$ build-qemu/qemu-system-riscv32 -M evu-ba -bios none \
-device loader,file=examples/mcu-sdk-examples/build/UART_printf.bin,addr=0xa0000000,cpu-num=0,force-raw=on \
-display none -serial mon:stdio
UART printf example
UART printf example
UART printf example
UART printf example
UART printf example
[...]
Консольный UART выбирается -machine evu-ba,uart=uartN (по умолчанию uart0).
Вместо bin можно использовать elf, так как loader понимает elf формат:
$ build-qemu/qemu-system-riscv32 -M evu-ba -bios none \
-device loader,file=examples/mcu-sdk-examples/build/UART_printf.elf,cpu-num=0 \
-display none -serial mon:stdio
UART printf example
UART printf example
UART printf example
UART printf example
[...]
Примечание: поскольку be-u1000 гетерогенный лучше сразу привыкать какой именно cpu (вернее чье видение адресного пространства используется) грузит прошивку.
Запуск примера с UART через gdb
build-qemu/qemu-system-riscv32 -M evu-ba -bios none \
-display none -serial mon:stdio \
-s -S
Где:
-s - сокращение для -gdb tcp::1234
-S - поставить машину на паузу, с которой её может снять gdb, monitor или какие внутренние процессы, например изменение состояния линии gpio.
И тогда:
riscv64-unknown-linux-gnu-gdb -ex 'target remote localhost:1234' examples/mcu-sdk-examples/build/UART_printf.elf
(gdb) info threads
Id Target Id Frame
* 1 Thread 1.1 (CPU#0 [running]) 0xa0000000 in ?? ()
2 Thread 1.2 (CPU#1 [halted ]) 0xa0000000 in ?? ()
3 Thread 1.3 (CPU#2 [halted ]) 0x00001000 in ?? ()
(gdb) load
Loading section .text, size 0x4ab6 lma 0xa0000000
Loading section .rodata, size 0x8df lma 0xa0004ab8
Loading section .data, size 0x14 lma 0xa0005398
Start address 0xa0000030, load size 21417
Transfer rate: 171336 bits in <1 sec, 7139 bytes/write.
(gdb) c
Continuing.
[...]
Как видите здесь выбор cpu тоже имеет значение.
Трассировка MMIO: фильтр по CPU и по адресам
SoC гетерогенный (BR350×2 + BM310), поэтому обычной трассировки QEMU “какой регион читали/писали” недостаточно - нужно ещё знать, какой харт сделал обращение (или что это DMA, когда current_cpu == NULL). В штатных событиях QEMU memory_region_ops_read/memory_region_ops_write номер CPU уже есть как параметр (cpu_index), поэтому ничего не пришлось добавлять в код самих устройств, только включить сами события:
$ build-qemu/qemu-system-riscv32 -M evu-ba ... \
-trace enable=memory_region_ops_read -trace enable=memory_region_ops_write \
-D trace.log
Строчки в trace.log выглядят так:
memory_region_ops_write cpu 0 mr 0x56390a6cfcd0 addr 0x11001000 value 0xd size 4 name 'be-u1000-uart'
memory_region_ops_read cpu 0 mr 0x56390a6cfcd0 addr 0x11001014 value 0x61 size 4 name 'be-u1000-uart'
memory_region_ops_write cpu 0 mr 0x56390a6cfcd0 addr 0x11001000 value 0xa size 4 name 'be-u1000-uart'
Поле cpu - это номер CPU; значение -1 (в отдельной, не показанной выше строке) - признак DMA/периферийного доступа без текущего CPU (current_cpu == NULL).
Примечание: это внутренний порядковый номер CPU в QEMU (порядок создания в be_u1000_soc_realize()), а не значение CSR mhartid. Для be-u1000 они совпадают, но это стоит понимать, а не считать гарантией по умолчанию.
Живой фильтр по CPU: реализован отдельно, через trace-cpu-filter (system/trace-cpu-filter.c): нужна сборка с -Dtrace_cpu_filter=true (то же, что --enable-trace-cpu-filter в configure выше), дальше, тогда мы можем:
-machine evu-ba,trace-cpu-mask=0x1,trace-dma=off
trace-cpu-mask это именно битовая маска, а не номер харта:
бит
Nразрешаетcpu_index=N, поэтому “только CORE0” - это0x1(бит 0), а “только CORE1” -0x2(бит 1), неtrace-cpu-mask=0/=1. Через+можно перечислить индексы вместо ручного ИЛИ битов ("0+1"эквивалентно0x3).trace-dmaвключает/выключает показ DMA-доступов (cpu=-1) отдельно от списка хартов, это то самое разделение “CPU vs DMA”, о котором шла речь в начале раздела.
Фильтр по адресам: через штатный -dfilter, но вручную подбирать диапазоны неудобно, поэтому есть вспомогательная пара инструментов:
Дамп карты памяти прямо из SoC (берётся из той же таблицы
be_u1000_config[], что и вся периферия в этой статье):qemu-system-riscv32 -M evu-ba,dump-memmap=memmap.json,dump-memmap-format=json \ -S -display none -serial none -monitor none(после дампа процесс завершается). Формат
text(по умолчанию) для человека, аjsonдля скрипта ниже.be-u1000-dfilter.py строит
-dfilterпо человеко-читаемым именам регионов вместо адресов:$ be-u1000-dfilter.py memmap.json UART0,ROM0 -dfilter 0x11001000+0x1000,0x40000000+0x10000С
--mergeсмежные регионы без разрыва между ними схлопываются в один:$ be-u1000-dfilter.py memmap.json --merge UART0,UART1 -dfilter 0x11001000+0x2000А с
-- <команда>скрипт не печатает-dfilter, а дописывает его в конец команды и сразу запускает её (черезexec):be-u1000-dfilter.py memmap.json --merge UART0,UART1 -- \ qemu-system-riscv32 -M evu-ba ...Например, в связке с MMIO-трассировкой на
UART_printfв лог попадают только обращения к UART0/UART1, а доступы к CRU (0x14000000) отрезаются:$ be-u1000-dfilter.py memmap.json --merge UART0,UART1 -- \ build-qemu/qemu-system-riscv32 -M evu-ba -bios none \ -device loader,file=examples/mcu-sdk-examples/build/UART_printf.elf,cpu-num=0 \ -display none -serial null \ -trace enable=memory_region_ops_read -trace enable=memory_region_ops_write \ -D trace.log $ head -4 trace.log memory_region_ops_read cpu 0 mr 0x55da3d7c0a70 addr 0x11001088 value 0x0 size 4 name 'be-u1000-uart' memory_region_ops_write cpu 0 mr 0x55da3d7c0a70 addr 0x11001088 value 0x7 size 4 name 'be-u1000-uart' memory_region_ops_read cpu 0 mr 0x55da3d7c0a70 addr 0x1100107c value 0x0 size 4 name 'be-u1000-uart' memory_region_ops_read cpu 0 mr 0x55da3d7c0a70 addr 0x11001018 value 0x0 size 4 name 'be-u1000-uart'
Semihosting
Semihosting - механизм, через который программа на целевой платформе пользуется ресурсами хоста (консолью, файлами, часами) через отладчик или эмулятор, не имея собственных драйверов. RISC-V для этого использует спецификацию ARM semihosting.
Вызов оформляется «магической» последовательностью:
slli x0, x0, 0x1f;
ebreak;
srai x0, x0, 7;
номер операции передаётся в a0, указатель на блок аргументов в a1, результат возвращается в a0.
Доступные вызовы:
Консоль
SYS_WRITEC(0x03) - вывести один символ;SYS_WRITE0(0x04) - вывести строку, завершённую нулём;SYS_READC(0x07) - прочитать символ с консоли хоста.
Файлы хоста
SYS_OPEN(0x01) /SYS_CLOSE(0x02) - открыть/закрыть файл (":tt"- консоль);SYS_WRITE(0x05) /SYS_READ(0x06) - запись/чтение буфера;SYS_SEEK(0x0A),SYS_FLEN(0x0C) - позиционирование, длина файла;SYS_ISTTY(0x09) - является ли дескриптор терминалом;SYS_TMPNAM(0x0D),SYS_REMOVE(0x0E),SYS_RENAME(0x0F) - временные имена, удаление, переименование.
Время
SYS_CLOCK(0x10) - время с начала выполнения в сотых долях секунды;SYS_TIME(0x11) - текущее время хоста (секунды с 1970);SYS_ELAPSED(0x30) /SYS_TICKFREQ(0x31) - счётчик тиков и его частота.
Окружение и служебное
SYS_GET_CMDLINE(0x15) - аргументы командной строки (в QEMU из-append);SYS_HEAPINFO(0x16) - границы кучи и стека;SYS_ERRNO(0x13) - errno последнего вызова;SYS_ISERROR(0x08) - проверка кода возврата на ошибку;SYS_SYSTEM(0x12) - выполнить команду на хосте (в QEMU отключено по умолчанию);SYS_EXIT(0x18) /SYS_EXIT_EXTENDED(0x20) - завершить эмуляцию с кодом возврата, удобно для тестов в CI.
Всё вышеперечисленное работает и в QEMU, и, например, через OpenOCD.
На практике всё это часто скрыто за libc: при сборке с --specs=semihost.specs (newlib/picolibc) printf, fopen, time и exit сами превращаются в эти вызовы.
Включение в QEMU:
$ build-qemu/qemu-system-riscv32 -M evu-ba -nographic -semihosting-config enable=on,target=native -device loader,file=examples/mcu-sdk-examples/build/Semihosting.elf
Semihosting printf example
Semihosting printf example
Semihosting printf example
Semihosting printf example
[...]
С target=native вызовы обрабатывает сам QEMU, с target=gdb они уходят в подключённый GDB, а target=auto (значение по умолчанию) решает один раз, при первом semihosting-вызове: если к встроенному gdbstub в этот момент подключён GDB, вызовы идут в GDB, иначе их обрабатывает QEMU. Выбор запоминается: GDB, подключённый позже, вызовов уже не получит, а после отключения GDB вызовы снова обрабатывает QEMU (gdbstub/syscalls.c, use_gdb_syscalls()). Опция chardev= перенаправляет консольный вывод в нужный chardev.
Генератор из SVD
Идея - Алексея Стариковского <aystarik@gmail.com>: раз у вендора есть CMSIS-SVD с описанием всей периферии, не надо переписывать адреса, регистры и битовые поля руками - их можно сгенерировать. Именно так сделана таблица be_u1000_config[], на которую ссылается вся статья, и именно поэтому адреса в QEMU совпадают со SVD.
svd2qemu.py читает SVD через cmsis_svd (с разворачиванием <dim>-массивов и <cluster>) и умеет:
list- таблица периферии: адрес, размер, IRQ;enums- C-enum с базовыми адресами;memmap- индексы*_IDX_*и массивDeviceConfig(то, что сталоbe_u1000_config[]);registers- макросыREG32()/FIELD()изhw/core/registerfields.hплюс массивRegisterAccessInfoс маскамиro/w1c/cor/rsvdи reset-значениями;header- готовый.hдля одного блока (include guard, SPDX,RSVD_MASK), для одинаковых блоков вроде ADC0/1/2 проверяет, что раскладка регистров совпадает.
Фильтры --group, --peripheral, --filter отбирают нужные блоки. Например, если взялись за WDT:
$ svd2qemu.py BE_U1000.svd list
$ svd2qemu.py BE_U1000.svd header --peripheral WDT0 --guard HW_MISC_BE_U1000_WDT_H -o be-u1000-wdt.h
Регистровая часть нового устройства получается сразу, остаётся написать поведение. Надо помнить, что core-local блоки (ROM/TCM/SRAM/CLINT/CLIC) в SVD не описаны, так что в данном случае генератор не поможет.
Кросс-проверка
Часто будут возникать ситуации, когда момент не полностью или крайне скудно освещен в документации, в таком случае необходимо прибегать к тестам на железе, которые дают однозначный ответ на вопрос.
Например возьмём CLIC, для него:
через CSR управляется состояние ядра и обработка прерываний:
mtvec: режим CLIC (mode=0b11) и адрес общего обработчика для не векторных прерываний;mtvt: база таблицы векторов для векторных (SHV) прерываний;mcause: причина, а такжеmpil(уровень прерванного кода),mpp/mpieиminhv;mintstatus(только чтение): текущий уровень прерыванияmil;mintthresh: порог, ниже которого прерывания маскируются;mnxti: позволяет взять следующее pending-прерывание без выхода из обработчика (tail-chaining);mstatus.MIE: глобальное разрешение прерываний;mscratchcsw/mscratchcswl: смена стека при смене привилегий или уровня (если реализованы),
а через MMIO настройка отдельных источников:
cliccfg(BASE+0x0):nlbits, то есть сколько битclicintctlуходит на уровень и сколько на приоритет (плюсnmbitsдля режима привилегий);clicinfo(BASE+0x4, только чтение): число источников, версия,CLICINTCTLBITS;clicinttrig[]: триггеры отладки (если реализованы);на каждый IRQ i по 4 байта по адресу
BASE+0x1000+4*i:clicintip(+0): бит pendingclicintie(+1): бит enableclicintattr(+2):shv(векторное или нет),trig(уровень/фронт, полярность),modeclicintctl(+3): уровень и приоритет; на be-u1000CLICINTCTLBITS=4, нижние 4 бита читаются как 1
C CSR всё понятно - они per-hart согласно спеке.
А вот с MMIO - спека однозначного ответа нам не даёт, там есть рекомендации, но они говорят, что CLIC управляется для каждого харта из общего MMIO региона, но со смещением.
В be-u1000 определен только один регион и тогда вывода может быть два:
регион shared и тогда всё что определено в MMIO региона влияет сразу на два hart’а (что мало вероятно);
они per-hart, то есть каждый hart читает и пишет, только свой собственный и не видит “соседнего”.
Тогда необходимо писать тест, который даст нам однозначный ответ, причем не обязательно на C или Asm, можно просто через cкрипты OpenOCD.
Так был сделан тест, чтобы проверить изоляцию CLIC, а заодно и CLINT MTIMER:
Подключение (добавить одну строку в свой EVU_BA_2_5.cfg, после source [find target/baikal/BE_U1000/registers.cfg]):
source [find target/baikal/BE_U1000/clic-isolation-tests.cfg]
Запуск из telnet-консоли OpenOCD, после обычной init-последовательности (targets CORE_0; init; halt; ___prepare_secondary_cores):
test_clic_isolation
test_clint_mtimecmp_isolation
Пример вывода test_clic_isolation:
=== CLIC_INTCTL_IRQ0 @ 0x0d001003 ===
(CORE_0) mhartid=0 OK
(CORE_1) mhartid=1 OK
(CORE_0) mhartid=0 OK
(CORE_0) wrote 0xa0, reads back 0xaf
(CORE_1) mhartid=1 OK
(CORE_1) (after CORE_0's write) reads 0x0f
(CORE_1) wrote 0x50, reads back 0x5f
(CORE_0) mhartid=0 OK
(CORE_0) (after CORE_1's write) reads 0xaf
(CORE_0) mhartid=0 OK
(CORE_1) mhartid=1 OK
=> VERDICT: register is PER-HART ISOLATED between CORE_0 and CORE_1
=== end CLIC_INTCTL_IRQ0 ===
0xa0->0xaf и 0x50->0x5f - ожидаемо: у be-u1000 CLIC_INTCTLBITS=4, нижние 4 бита регистра аппаратно всегда 1.
Следовательно изоляция CLIC подтверждена и на реальном железе.
Готовый тест лежит в clic-isolation-tests.cfg.
Что можно сделать в рамках проекта
Ниже - периферия SoC, которую ещё предстоит сделать, сгруппированная по сложности, с примером из mcu-sdk-examples, который сразу пригодится для проверки. Адреса берутся из автогенерированного из SVD be_u1000_config[] (см. раздел про генератор выше), так что там, где есть реализация, адрес в QEMU совпадает со SVD. Все блоки SoC единым списком, вместе с уже готовыми, собраны в приложении в конце статьи.
Понятные первые задачи
Небольшие, самодостаточные куски и ясный критерий готовности (пример из SDK, который должен заработать):
Периферия | Статус | Что нужно сделать | Пример, на котором проверить |
|---|---|---|---|
GPIO0–2 | заглушка | регистровый банк set/clear + EXTI на CLIC |
|
WDT0/1 | заглушка | счётчик + сброс харта через уже готовый core-reset механизм CRU |
|
EFLASH | временный ROM-костыль | честная модель с контроллером (erase/program), сейчас заменена ROM-заглушкой только для загрузки |
|
| не подключён | завести | удобство для всех примеров сразу |
Средней сложности
Протокол/несколько режимов, но без внешних зависимостей:
Периферия | Статус | Комментарий | Примеры |
|---|---|---|---|
TIM0/1, PWMG0/1 | заглушка | базовый таймер + compare/capture/PWM |
|
PWMA0–3 / QE0–3 | заглушка | PWM + квадратурный энкодер, часть примеров завязана на DMA |
|
I2C0–3, SPI0–3 | заглушка | master/slave протокол |
|
ADC0–2 | заглушка | регистры + модель источника сигнала |
|
Mailbox (MB/MB1) | заглушка | есть дизайн generic mailbox (N views, FIFO с данными, конфигурируемые level/edge IRQ), реализация по datasheet ещё не начата. | нет примера - подходящая задача для тех, кто любит проектную работу с готовой спецификацией |
Крупнее, со своими зависимостями
Периферия | Статус | Комментарий | Примеры |
|---|---|---|---|
DMA0/1 | заглушка | блокирует все |
|
I2S0/1 | заглушка | аудио-поток, завязан на DMA |
|
QSPI0/1 | заглушка | плюс XIP-режим |
|
CANFD0/1 | заглушка | арбитраж, кадры CAN FD |
|
USB | заглушка | нет примера в SDK, нужно будет писать тесты с нуля | - |
BM310 (CORE2): CLIC/MTIMER/адресное пространство | харт создаётся | завести | нет примера под CORE_2 в текущем наборе - хороший повод завести |
Инфраструктурные блоки (CORE2_INT/CORE2_PIO, CORE2_DEBUG/CORE2_TRACE) сейчас заглушки: функциональность они не блокируют, а может, и не нужны вовсе.
Ultra-Violence: MicroPython
MicroPython: тут я даже не думал с какой стороны подступиться и как это будет выглядеть в итоге.
Идеи для upstream
Две темы, которые выходят за рамки BE-U1000 и могли бы пригодиться всему QEMU.
Заметка про подсистему тактирования. В Linux для этого есть Common Clock Framework: гейты, делители, мультиплексоры и PLL описываются как узлы одного дерева, частота пересчитывается вниз по дереву автоматически, а драйвер контроллера только описывает топологию. В QEMU ничего подобного нет. Есть лишь базовый объект Clock (hw/core/clock.c, qdev-clock.c): входы и выходы тактирования у устройства, clock_set_hz()/clock_set_mul_div(), распространение частоты и колбэк на её изменение. Гейты, делители и мультиплексоры каждый контроллер пишет руками, а многие старые модели вообще считают частоты своими функциями мимо Clock.
При этом моделей контроллеров тактирования в QEMU уже немало. На Clock построены, например, BCM2835 CPRMAN (bcm2835_cprman.c, Raspberry Pi), STM32L4x5 RCC (stm32l4x5_rcc.c), Nuvoton NPCM7xx/8xx CLK (npcm_clk.c) и Zynq SLCR (zynq_slcr.c). Своим способом, без общего дерева, сделаны i.MX CCM (imx25/31/6/6ul/7/8mp_ccm.c), Allwinner CCM/CCU (allwinner-a10-ccm.c, allwinner-h3-ccu.c, allwinner-r40-ccu.c), Aspeed SCU (aspeed_scu.c), SiFive PRCI (sifive_e_prci.c, sifive_u_prci.c), Exynos4210 CLK, STM32 RCC (stm32_rcc.c), Axiado CLK и OMAP. Теперь к ним добавляется наш CRU для BE-U1000 со своими GATE/DIV/MUX/PLL. Каждая из этих моделей заново решает одни и те же задачи, так что общий слой в духе CCF (типовые узлы gate/divider/mux/pll поверх Clock и описание дерева таблицей) уже напрашивается. Это хорошая идея для upstream.
Заметка про PINMUX. В QEMU мультиплексирование выводов по сути бессмысленно: у эмулируемой машины нет физических ножек, а устройства подключены к внешнему миру напрямую через бэкенды (UART - к chardev, CAN - к can-bus, сеть - к netdev). UART отдаст байт в свой chardev независимо от того, на какую ножку его TX вывела прошивка. Поэтому обычно достаточно, чтобы регистры выбора альтернативной функции читались и писались без последствий - HAL их инициализирует и идёт дальше. Смысл появляется, только если моделировать плату целиком: светодиод, кнопку или датчик на конкретной ножке, или ловить ошибки конфигурации, когда прошивка забыла переключить ножку в нужную функцию, и на железе ничего бы не заработало.
Для STM32, например, уже была попытка реализовать нечто подобное (beckus/qemu_stm32), но общего решения в QEMU нет: каждая машина делает по-своему или не делает вовсе. Идея открытая: универсальный фреймворк pinmux/pinctrl для QEMU тоже был бы интересной задачей сразу для upstream, так что если кто-то за него возьмётся - будем рады помочь.
Пойдет ли данная работа в upstream
BE-U1000 делает «Байкал», компания под санкциями, и реакция мейнтейнеров на подобные патчи уже известна - в лучшем случае ответят «Consult your lawyers» (см. историю с e2k - ссылка), в худшем просто промолчат. Тратить время на переписку, чтобы услышать «юрист вам в помощь», смысла нет.
Поэтому работа пойдёт в форк LANDAU QEMU, и мы её будем поддерживать. Это просто рабочая необходимость, раз upstream закрыт по причинам, не связанным с кодом, инструмент должен жить в другом месте - всё, что сделано и будет сделано по BE-U1000 в форке.
Осталось почти всё - но то, что осталось, теперь можно есть по кусочку. Присоединяйтесь.
Приложение: полная таблица периферии (SVD → код QEMU → пример в mcu-sdk-examples)
Все блоки SoC из автогенерированного из SVD be_u1000_config[] (hw/riscv/be-u1000.c), со статусом в QEMU, оценкой сложности реализации и примером в SDK. Везде, где в коде есть реальная реализация, адрес в QEMU совпадает со SVD без расхождений.
Периферия | Статус в QEMU | Оценка сложности реализации | Пример в SDK |
|---|---|---|---|
CRU (тактирование) | готово | сделано | нет отдельного - используется неявно во всех |
Reset Controller (в составе CRU) | готово | сделано | нет отдельного |
CLIC (CORE0/CORE1) | готово | сделано, draft 0.10 |
|
CLINT (CORE2/BM310) | не начато | высокая (своё приватное адресное пространство BM310 + подключение) | - |
MTIMER (CORE0/CORE1) | готово | сделано | нет отдельного (используется неявно) |
MTIMER (CORE2/BM310) | не начато | средняя (переиспользовать | - |
ROM0/ROM1 | готово (изолированы по хартам) | сделано | нет отдельного |
TCMA/TCMB | готово | сделано | нет отдельного |
SRAM | готово | сделано: resetvec CORE0/CORE1 на EFLASH, SRAM создаётся и маппируется | используется как RAM почти всеми примерами |
EFLASH + EFLASH_CONTROLLER | временный ROM вместо честной модели | низкая (erase/program протокол + контроллер) |
|
UART0–7 | базовая имплементация | сложность низкая, но очень нудно регистров много функциональности тоже много, часть нельзя реализовать в QEMU. |
|
| не подключён | низкая ( | удобство для всех примеров |
GPIO0–2 | заглушка | низкая (регистровый банк set/clear + EXTI на CLIC) |
|
WDT0/1 | заглушка | низкая (счётчик + сброс харта через core-reset CRU) |
|
TIM0/1 | заглушка | средняя (base timer, compare/capture) |
|
PWMG0/1 | заглушка | средняя (как TIM + PWM-выход/захват) |
|
PWMA0–3 / QE0–3 | заглушка | средняя-высокая (PWM + квадратурный энкодер + DMA-burst режимы) |
|
I2C0–3 | заглушка | средняя (master/slave протокол) |
|
SPI0–3 | заглушка | средняя |
|
CANFD0/1 | заглушка | высокая (арбитраж, кадры CAN FD) |
|
I2S0/1 | заглушка | средняя-высокая (аудио-поток, завязан на DMA) |
|
QSPI0/1 | заглушка | средняя-высокая (плюс XIP-режим) |
|
ADC0–2 | заглушка | средняя (регистры + модель источника сигнала) |
|
DMA0/1 | заглушка | высокая (блокирует все |
|
USB | заглушка | высокая | нет примера |
MB/MB1 (mailbox) | заглушка, есть дизайн | средняя (дизайн готов, реализация не начата) | нет примера |
AXI/AHB, CORE2_INT/CORE2_PIO, CORE2_DEBUG/CORE2_TRACE | заглушки | низкая (debug/bus-инфраструктура, не блокирует функциональность) | нет примера |
MicroPython | 0 | запредельная | нет примера |
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.