Привет, Хабр. Меня зовут Роман, я разработчик встраиваемых систем в 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) с тестируемым ПО.

Прошивка

Тестовая прошивка выполнена в виде образа памяти, со следующей схемой разделов.

Partitions
Partitions

Она немного отличается от той, что устанавливается в конечное устройство, но для данной статьи и общей проверки ПО этого будет достаточно.

boot - раздел, содержащий ядро Linux c заголовком для чтения u-boot (uImage)

rootfs - раздел с утилитами, драйверами и тестируемым ПО. Здесь стоит помнить о том, что некоторые драйвера не могут функционировать в среде эмулятора, так как “по-честному” эмулируется не целевая плата, а одна из “коробочного” состава QEMU. Для упрощения я использую плату Malta. В идеальном случае нужно добавлять периферию платы самостоятельно, но

Idontcare
Idontcare

Порядок загрузки

Прежде чем говорить о том, как задуманное воплотить в жизнь, необходимо продумать как в эмуляторе будет происходить загрузка конечной прошивки. Получился такой сценарий:

Boot process
Boot process

Запуск эмулятора

Подготовка

Для упрощения процесса настройки сборочного окружения и подготовки финальных артефактов, я воспользовался системой сборки 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 и выключает эмулятор.

Вывод

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

Исходники проекта с конфигурациями, описанными в статье можно скачать здесь.

Комментарии (0)