И еще немного про авто-тесты в QEMU

от автора

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

Вывод

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

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


ссылка на оригинал статьи https://habr.com/ru/post/652757/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *