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.
Что ещё не сделано
Собрать образ — только первый шаг. До производства остаётся вот что:
- Прошить плату и проверить в работе: автозапуск, IPv6, ввод в сеть Matter, логи — всё нужно пройти на реальном железе.
- A/B-разделы и OTA: в SDK есть бесшовное A/B-обновление.
./bd.sh otapackageгенерирует полные и дельта-пакеты обновлений, на устройстве есть C-интерфейс вlibupdate.so, а наша программа отвечает за загрузку и вызов. Но по умолчанию раздел один, так что для включения нужно менять таблицу разделов. Зато не придётся отдельно интегрировать RAUC или SWUpdate. - Перейти на release-конфигурацию: убрать adb и отладочные инструменты, отключить sshd.
- Безопасная загрузка: проверки на всех этапах после 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