Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Привет, Хабр. Меня зовут Роман, я разработчик встраиваемых систем в Dannie и мы тут делаем умные камеры. По долгу службы, мне потребовалось завести эмуляцию прошивки для чипа из семейства MIPS. В рамках разработки проекта мы обозначили для себя задачу получения быстрой обратной связи при разработке ПО и прошивки. Для этого начали выстраивать CI/CD-цепочку с проверкой прошивки в эмуляторе. Одной из требуемых функций являлась возможность манипулировать окружением загрузчика (u-boot environment). В статье я расскажу что получилось и как из говна и палок завести авто-тесты прошивки в CI.
Постановка задачи
Предмет эмуляции
Не столь критичная информация для топика в целом, но стоит упомянуть “что же такое эмулируется?”. А эмулируется прошивка для чипа Ingenic T40. Это SoC, базирующийся на MIPS архитектуре и имеющий NPU на борту.
Итак имеем:
Эмулируемая платформа MIPS 32R2 Little Endian
Docker-контейнер, с ubuntu 20.04 x86_64 внутри
Компоненты
Для эмуляции был выбран QEMU, в своем составе он имеет группу утилит для развертывания виртуальной машины qemu-system-mips(el,64,64el).
Эмуляторам этой группы для запуска необходимо указать либо BIOS (-bios), либо ядро (-kernel).
$ qemu-system-mipsel
Unable to init server: Could not connect: Connection refused
qemu-system-mipsel: Could not load MIPS bios 'mipsel_bios.bin', and no -kernel argument was specified
Как говорилось вначале, нам нужна возможность манипуляции окружением u-boot. По-этому первым компонентом будет загрузчик u-boot.
Вторым компонентом, очевидно, выступает сама прошивка в которую входят ядро Linux и файловая система (rootfs) с тестируемым ПО.
Прошивка
Тестовая прошивка выполнена в виде образа памяти, со следующей схемой разделов.
Она немного отличается от той, что устанавливается в конечное устройство, но для данной статьи и общей проверки ПО этого будет достаточно.
boot - раздел, содержащий ядро Linux c заголовком для чтения u-boot (uImage)
rootfs - раздел с утилитами, драйверами и тестируемым ПО. Здесь стоит помнить о том, что некоторые драйвера не могут функционировать в среде эмулятора, так как “по-честному” эмулируется не целевая плата, а одна из “коробочного” состава QEMU. Для упрощения я использую плату Malta. В идеальном случае нужно добавлять периферию платы самостоятельно, но
Порядок загрузки
Прежде чем говорить о том, как задуманное воплотить в жизнь, необходимо продумать как в эмуляторе будет происходить загрузка конечной прошивки. Получился такой сценарий:
Запуск эмулятора
Подготовка
Для упрощения процесса настройки сборочного окружения и подготовки финальных артефактов, я воспользовался системой сборки buildroot версии 2021.11.1.
Из постановки задачи следует, что нужно сформировать следующие компоненты для тестирования платформы в QEMU:
Загрузчик u-boot
Ядро Linux
Файловую систему
u-boot
Все дальнейшие действия я выполняю с u-boot версии 2021.7 (однако, справедливы и для многих более ранних версий).
В проекте u-boot из коробки поддерживается платформа maltael, ее конфигурацию можно использовать для сборки загрузчика, но для моих целей её потребовалось немного модифицировать. Ниже основные изменения (полная конфигурация):
CONFIG_USE_BOOTARGS=y
CONFIG_BOOTARGS="console=ttyS0,38400n8r loglevel=8 mem=128M@0x0 rmem=128M@0x8000000 mtdparts=physmap-flash.0:128k@3968k(u-boot-env) init=/sbin/init root=/dev/hda2 rootfstype=ext4 rw rootwait"
CONFIG_USE_BOOTCOMMAND=y
CONFIG_BOOTCOMMAND="saveenv; fatload ide 0 0x81000000 uImage; printenv bootargs; bootm 0x81000000"
CONFIG_CMD_FAT=y
# Так же стоит запомнить следующие опции, они влияют на остальную систему
CONFIG_ENV_SIZE=0x20000
CONFIG_ENV_SECT_SIZE=0x20000
CONFIG_ENV_IS_IN_FLASH=y
CONFIG_ENV_ADDR=0xBE3E0000
CONFIG_MTD_NOR_FLASH=y
И здесь я бы хотел оставить пару слов о команде загрузки “по-умолчанию” CONFIG_BOOTCOMMAND. Вызов saveenv происходит лишь с той целью, чтобы проинициализировать память с окружением при первом запуске (а для эмулятора, каждый запуск - первый).
Ядро Linux
В виду того, что ядро для платы не содержит модулей для запуска в режиме гостя и может содержать проприетарные вещи, я собираю “ванильное” ядро, но той же версии что и в камере 4.4.94. В общем же случае можно воспользоваться и последними релизами.
По аналогии с u-boot, вместо “коробочной” конфигурации для maltael, я использовал свою (полная конфигурация), ниже основные моменты:
CONFIG_MTD=y
CONFIG_MTD_BLOCK=y
CONFIG_MTD_BLOCK2MTD=y
CONFIG_MTD_CFI=y
CONFIG_MTD_CFI_ADV_OPTIONS=y
CONFIG_MTD_CFI_INTELEXT=y
CONFIG_MTD_CFI_AMDSTD=y
CONFIG_MTD_CFI_STAA=y
CONFIG_MTD_PHYSMAP=y
CONFIG_MTD_UBI=y
CONFIG_MTD_UBI_GLUEBI=y
# Здесь стоит выделить следующую опцию, без нее будет сложно предоставить возможность записи в MTD
CONFIG_MTD_CMDLINE_PARTS=y
Помните параметры запуска ядра по-умолчанию из конфигурации u-boot? Здесь и раскрывается настройка u-boot environment. Cреди параметров нас интересует настройка mtdparts:
mtdparts=physmap-flash.0:128k@3968k(u-boot-env)
Здесь мы говорим ядру, что у нас есть FLASH-память physmap-flash.0
и в ней нас интересует раздел размером 0x20000 (128Kb), со смещением 0x3e0000 (3968Kb). Эти значения можно взять из конфигурации платформы, здесь же можно и отредактировать состав mtd-разделов и их размеры, например так.
Файловая система
Для работы с u-boot environment существует открытый проект libubootenv. Он предоставляет утилиты fw_printenv и fw_setenv. Чтобы эти утилиты смогли работать с нашим окружением, необходимо указать тип окружения и параметры в файле fw_env.config:
/dev/mtd0 0x0000 0x20000 0x20000
Проверка работы
После запуска эмулятора, проверить работоспособность u-boot environment можно следующим образом:
# Отобразим текущее состояние u-boot env
u-boot $ fw_printenv
baudrate=115200
bootargs=console=ttyS0,38400n8r loglevel=8 mem=128M@0x0 rmem=128M@0x8000000 mtdparts=physmap-flash.0:128k@3968k(u-boot-env) init=/sbin/init root=/dev/hda2 rootfstype=ext4 rw rootwait
bootcmd=saveenv; fatload ide 0 0x81000000 uImage; printenv bootargs; bootm 0x81000000
bootdelay=1
fdtcontroladdr=8ff7fcc0
stderr=serial@3f8
stdin=serial@3f8
stdout=serial@3f8
В качестве примера, увеличиваем время перед выполнение команды автозагрузки
linux $ fw_setenv bootdelay 10
linux $ fw_printenv
baudrate=115200
bootargs=console=ttyS0,38400n8r loglevel=8 mem=128M@0x0 rmem=128M@0x8000000 mtdparts=physmap-flash.0:128k@3968k(u-boot-env) init=/sbin/init root=/dev/hda2 rootfstype=ext4 rw rootwait
bootcmd=saveenv; fatload ide 0 0x81000000 uImage; printenv bootargs; bootm 0x81000000
fdtcontroladdr=8ff7fcc0
stderr=serial@3f8
stdin=serial@3f8
stdout=serial@3f8
bootdelay=10
linux $ reboot
U-Boot 2021.07 (Feb 09 2022 - 13:07:51 +0000)
Board: MIPS Malta CoreLV
DRAM: 256 MiB
Flash: 4 MiB
Loading Environment from Flash... OK
In: serial@3f8
Out: serial@3f8
Err: serial@3f8
Net: No ethernet found.
IDE: Bus 0: OK
Device 0: Model: QEMU HARDDISK Firm: 2.5+ Ser#: QM00001
Type: Hard Disk
Capacity: 2256.0 MB = 2.2 GB (4620289 x 512)
Device 1: not available
Hit any key to stop autoboot: 10
u-boot $ printenv
baudrate=115200
bootargs=console=ttyS0,38400n8r loglevel=8 mem=128M@0x0 rmem=128M@0x8000000 mtdparts=physmap-flash.0:128k@3968k(u-boot-env) init=/sbin/init root=/dev/hda2 rootfstype=ext4 rw rootwait
bootcmd=saveenv; fatload ide 0 0x81000000 uImage; printenv bootargs; bootm 0x81000000
bootdelay=10
fdtcontroladdr=8ff7fcc0
stderr=serial@3f8
stdin=serial@3f8
stdout=serial@3f8
Environment size: 376/131068 bytes
Здесь я изменил значение задержки bootdelay до 10 секунд и отобразил значение этого поля внутри командной строки u-boot.
Автоматизация тестов
В конечном итоге, решая исходную задачу, можно использовать командную оболочку expect:
#!/usr/bin/expect -f
set timeout 10
spawn qemu-system-mipsel -cpu 24Kc -M malta -m 1024 -nodefaults -nographic -serial stdio -bios env(IMAGE),format=raw -net nic,model=pcnet -net user
expect "login: "
send "root\r"
expect "Password: "
send "root\r"
expect "# "
send "halt\r"
Здесь скрипт ожидает окончания загрузки прошивки, производит попытку авторизации пользователем root и выключает эмулятор.
Вывод
В итоге я получил необходимую цепочку загрузки прошивки и возможность разрабатывать тестовые сценарии для проверки функций, не завязанных напрямую на железо.
Исходники проекта с конфигурациями, описанными в статье можно скачать здесь.