Linux для шлюза Matter: сборка RDK X5 SDK и Buildroot в WSL

Сборка SDK D-Robotics RDK X5 в WSL и встраивание шлюза на Rust в образ Buildroot: кросс-компиляция, overlay, отключённые службы, автозапуск и ядро без IPv6 по умолчанию.

В прошлой статье я рассказал, как мы писали программу шлюза на Rust с rs-matter. Пока она работает на компьютере. Чтобы превратить её в шлюз, который можно продавать, не хватает ещё одного слоя: собственной Linux-системы шлюза.

Отладочные платы обычно приходят с образом Ubuntu: подключил питание — и всё работает. Первая мысль у многих: поставить программу шлюза прямо на Ubuntu, и дело с концом?

Для разработки — можно. Для серийного производства — нет. В этой статье я описываю, как мы собирали SDK D-Robotics RDK X5 в WSL и делали на Buildroot образ системы для шлюза, а также несколько подводных камней, которые не обнаружишь, пока сам не попробуешь.

Почему не взять просто Ubuntu

SDK X5 от D-Robotics сам предлагает два варианта корневой файловой системы: образ Ubuntu 22.04 для разработки и Buildroot для производства. Применительно к шлюзу разница очевидна:

Образ Ubuntu Собственная система на Buildroot
Размер Несколько ГБ Несколько сотен МБ, раздел system всего 250MB
Что внутри Полный набор пакетов уровня десктопа Только то, что вы сами выбрали
Сервисы при загрузке Много, и непонятно, какие из них работают Каждый на виду, любой можно убрать
Обновление apt, пакет за пакетом Замена всего образа системы, с A/B-разделами возможен откат
Аудит безопасности Приходится объяснять кучу ненужного софта Малая поверхность атаки, легко обосновать

Шлюз — это устройство, которое стоит у пользователя дома, и годами никто на него не заходит. Ему нужно одно: при включении запускать программу шлюза, иметь систему только для чтения, которую не испортить записью, и автоматически откатываться на старую версию, если обновление не удалось. В Buildroot всё это обычная практика, а Ubuntu пришлось бы мучительно урезать.

Почему начали с RDK X5

В статье про выбор основного процессора мы отнесли RDK X5 к категории «работает, но для шлюза не подходит»: 10 TOPS вычислительной мощности для ИИ шлюзу ни к чему, да и стоит она многовато.

Зато у неё есть одно очень практичное преимущество: SDK можно свободно скачать, а по сборке, урезанию системы, безопасной загрузке, A/B OTA и защищённому хранилищу OP-TEE есть официальная документация. SDK Rockchip тоже построен на Buildroot, и подход там почти такой же. Поэтому на этапе кастомизации системы мы сначала прошли весь процесс целиком на SDK X5 — у него самая полная документация.

Сразу оговорюсь: в этой статье образ уже собран, но ещё не прошит на плату и не проверен в работе. Прошивка и реальный запуск — в следующих статьях, когда плата будет у нас.

Окружение: отдельный дистрибутив WSL

SDK X5 LNX6.1.176_PL5.1_V2.0.0 требует Ubuntu 22.04 и после распаковки занимает около 9.5GB. Каждый день я работаю в Ubuntu 24.04 и не хотел смешивать с ней зависимости SDK, поэтому поставил отдельный дистрибутив с виртуальным диском на диске D:

wsl --install -d Ubuntu-22.04 --name x5build --location 'D:\wsl\x5build' --no-launch

При запуске команд wsl из Git Bash нужно добавлять MSYS_NO_PATHCONV=1, иначе Git Bash переделает пути, начинающиеся с /, в пути Windows.

Основные компоненты SDK:

Компонент Версия
Ядро Linux 6.1.176
U-Boot 2022.10
ATF 2.8
Buildroot 2025.02
Корневая файловая система glibc, SysV init
Кросс-тулчейн ARM GNU Toolchain 11.3 (aarch64), входит в SDK

Скачиваем код, ставим зависимости и делаем первую полную сборку:

B=https://archive.d-robotics.cc/downloads/sdk/LNX6.1.176_PL5.1_V2.0.0/board_support_package
curl -O $B/platform_source_code.tar.gz.md5sum
curl -C - --retry 5 -O $B/platform_source_code.tar.gz   # 约 4.1GB,断了能续传
md5sum -c platform_source_code.tar.gz.md5sum
tar -I pigz -xf platform_source_code.tar.gz && cd platform_source_code

sudo build/install_host_deps.sh
sudo tar -xf toolchain/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu.tar.xz -C /opt/

./bd.sh lunch 8   # board_x5_soc_debug_config.mk
./bd.sh           # 第一次完整编译,约 12 分钟

lunch 8 — это debug-конфигурация, с adb и отладочными инструментами; для производства нужно перейти на release-конфигурацию lunch 9.

Следите и за памятью: у меня глобальный лимит WSL — 8GB, а SDK рекомендует 16GB. Пока я собираю только шлюз и переупаковываю образ, 8GB хватает; если позже понадобится собирать весь Buildroot из исходников и памяти не хватит, поменяйте memory= в C:\Users\<用户名>\.wslconfig и выполните wsl --shutdown. Учтите, что это остановит все дистрибутивы WSL.

Встраиваем программу шлюза в систему

Весь процесс я оформил в скрипт, так что после правки кода достаточно одной строки в Windows:

wsl -d x5build -- bash /mnt/d/<项目目录>/x5/build.sh

Скрипт делает следующее: синхронизирует исходники, кросс-компилирует программу шлюза, кладёт её в корневую файловую систему и переупаковывает образ. По ходу дела есть несколько деталей, о которых стоит рассказать.

Сначала синхронизировать исходники в файловую систему Linux, потом собирать

Код проекта лежит на диске D в Windows, в WSL это /mnt/d. Собирать Rust прямо в /mnt/d намного медленнее, потому что каждое обращение к файлу проходит через файловую систему Windows. Поэтому скрипт сначала копирует код через rsync в каталог на собственной файловой системе WSL (исключая target и .git) и собирает уже там.

Таргет glibc, а не musl

Когда мы выбирали основной процессор, план был собрать через aarch64-unknown-linux-musl статически слинкованный бинарник, общий для Rockchip и D-Robotics.

На X5 мы перешли на aarch64-unknown-linux-gnu, а в качестве линкера указали GCC 11.3 из SDK:

CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc \
  cargo build --release --target aarch64-unknown-linux-gnu

Причина в том, что корневая файловая система X5 и так на glibc. При линковке тулчейном SDK, у которого glibc старше, чем в прошивке, собранная программа гарантированно запустится на плате; никаких отдельных проблем совместимости с musl решать не нужно.

Overlay-каталог: сначала восстановить, потом наложить

В SDK предусмотрен overlay-каталог для корневой файловой системы, system/buildroot/prebuilt/boot-utils-runtime: всё, что туда положено, попадает в образ как есть. Наши собственные файлы лежат в x5/overlay/ в проекте.

При первом запуске скрипт сохраняет снимок исходного overlay-каталога SDK. При каждой следующей сборке он сначала восстанавливает снимок, а потом накладывает наши файлы.

Плюс такого подхода — обратимость: удалили файл из x5/overlay/ — и в образе снова оригинал из SDK, без остатков прошлых изменений.

Отключаем ненужные сервисы при загрузке

Сервисы тоже отключаются через overlay: кладём скрипт с тем же именем, внутри которого только exit 0, и он заменяет исходный стартовый скрипт SDK. Пока отключены такие:

Сервис Причина отключения
S50telnet Удалённый вход открытым текстом, не соответствует требованиям законодательства по кибербезопасности вроде европейского EN 18031 или британского PSTI
S40bluetoothd Встроенный Bluetooth модуля не используется, Bluetooth Mesh идёт через отдельный модуль Silicon Labs
S65ptp4l, S66phc2sys Точная синхронизация времени по PTP, шлюзу не нужна
S90cam-service Сервис камеры
S99iiod Сервис датчиков IIO

sshd и adb на этапе разработки пока оставляем, для производства отключим.

Автозапуск: подвох в одном &

Программа шлюза должна стартовать автоматически при загрузке и перезапускаться, если упала. На X5 используется SysV init, так что достаточно написать /etc/init.d/S95gateway.

Первое, на что стоит обратить внимание: на плате нет bash. Начиная с V2.0.0 SDK X5 больше не включает bash, так что системные скрипты пишутся только на POSIX sh.

Второй подвох коварнее. Самый очевидный вариант — отправить цикл-супервизор в фон прямо в стартовом скрипте:

while true; do /usr/bin/gateway; sleep 2; done &

Выглядит нормально, но при загрузке rcS, отработав все стартовые скрипты и завершаясь, посылает сигнал hangup запущенным им фоновым процессам, и цикл погибает вместе с ними. Программа шлюза при загрузке стартует на мгновение — и исчезает.

Правильно — использовать start-stop-daemon -b, чтобы демон отделился от сессии rcS:

LOOP="cd /userdata/gateway && while true; do /usr/bin/gateway </dev/null 2>&1 | logger -t gateway; sleep 2; done"
start-stop-daemon -S -b -m -p /var/run/gateway.pid -x /bin/sh -- -c "$LOOP"

При аварийном завершении программа через 2 секунды перезапускается, а её вывод через logger попадает в системный журнал. На X5 нет logread, логи лежат в /userdata/log/kernel/message, искать через grep gateway. Данные шлюза хранятся в /userdata/gateway/data/: этот раздел доступен для записи, а системный раздел — только для чтения.

Две настройки SDK по умолчанию, которые обязательно нужно поменять

В ядре по умолчанию выключен IPv6

Это был самый неприятный подвох за весь процесс.

В конфигурации ядра SDK X5 по умолчанию стоит # CONFIG_IPV6 is not set, причём в обоих конфигах — и в hobot_x5_soc_defconfig, и в hobot_x5_soc_perf_defconfig.

А Matter построен поверх IPv6: и mDNS для обнаружения устройств, и обмен данными между Apple Home и шлюзом идут через IPv6. С ядром SDK по умолчанию программа шлюза запускается, но Apple Home её никогда не найдёт.

Скрипт проверяет оба конфига, при необходимости меняет на CONFIG_IPV6=y и пересобирает ядро через ./bd.sh boot. При переходе на новую версию SDK этот пункт нужно перепроверять.

Таблица разделов: убираем раздел app на 700MB

В таблице разделов SDK по умолчанию есть раздел app на 700MB, где лежат только демонстрационные программы D-Robotics, шлюзу не нужные. Скрипт удаляет его из x5-soc-debug-gpt.json и x5-soc-release-gpt.json небольшим куском кода на Python.

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

Без него раздел system занимает 250MB, а userdata — всего 50MB. Для таблицы устройств шлюза и данных Matter 50MB пока хватает, но перед производством разметку обязательно нужно пересмотреть под реальные потребности.

Что получается на выходе

Файл Назначение
out/product/emmc_disk.img Полный образ eMMC, прошивается через fastboot, DFU или TFTP
out/product/emmc_disk.simg Разреженный образ, меньше по размеру, но с DFU не работает
out/product/uart_usb/ Загрузочная прошивка для чистых микросхем
out/product/*.img Отдельные образы каждого раздела

Утилита прошивки xburn лежит в software_tools/download_tools/ в каталоге загрузок SDK.

Что ещё не сделано

Собрать образ — только первый шаг. До производства остаётся вот что:

  1. Прошить плату и проверить в работе: автозапуск, IPv6, ввод в сеть Matter, логи — всё нужно пройти на реальном железе.
  2. A/B-разделы и OTA: в SDK есть бесшовное A/B-обновление. ./bd.sh otapackage генерирует полные и дельта-пакеты обновлений, на устройстве есть C-интерфейс в libupdate.so, а наша программа отвечает за загрузку и вызов. Но по умолчанию раздел один, так что для включения нужно менять таблицу разделов. Зато не придётся отдельно интегрировать RAUC или SWUpdate.
  3. Перейти на release-конфигурацию: убрать adb и отладочные инструменты, отключить sshd.
  4. Безопасная загрузка: проверки на всех этапах после U-Boot нужно перевести на наши собственные ключи подписи, прошитые в eFuse. Прошивка eFuse необратима, поэтому сначала проверяем на нескольких образцах.

Напоследок

Оглядываясь назад: время на этом этапе ушло не на сами команды сборки — bd.sh в SDK их отлично инкапсулирует. Настоящее время ушло на значения по умолчанию: IPv6 выключен в ядре, на плате нет bash, фоновые процессы убивает rcS, в таблицу разделов запихнут демонстрационный раздел на 700MB. Ни одно из этого не выдаёт ошибку — просто шлюз где-то на каком-то этапе тихо перестаёт работать.

Следующий шаг: как только приедет плата, прошить образ, параллельно подключить Bluetooth-модуль Silicon Labs и впервые на реальном железе управлять из шлюза лампой Bluetooth Mesh.

Если вы тоже делаете шлюзы и IoT-устройства на D-Robotics X5, Rockchip или другом SDK на базе Buildroot — буду рад обменяться опытом: linktr.ee/weiyii

Чтение и отправка через WeChat

QR-код статьи

Отсканируйте и поделитесь через меню WeChat.