TL;DR. Подключил к Repka Pi 5 (RK3588) Arducam IMX519 16MP через CSI — а её ядро не поддерживает: нет ни драйвера, ни device-tree оверлея под этот сенсор. Эпопея растянулась на три акта и шестнадцать пересборок ядра. Первый акт — портирование драйвера и overlay: сенсор появился в медиа-графе, но кадра не давал. Второй — интеграция с rkaiq и ложные следы (split-pipeline, rkisp-unite). Третий — настоящая kernel-отладка с
pr_infoв драйвере: нашёл и починил шесть багов в RKCIF и imx519, каждый блокировавший свой этап (error 515→ kernel oops → EINVAL → ENOMEM → EPIPE → stream ON). Pipeline полностью активируется, DPHY включается на 987 Мбит/с — но последний слой (физика MIPI) пока открыт. Под катом — весь путь с реальными командами, логами и откатом.
Задача и зачем #
Была цель — получить поток с конкретной камеры Arducam 16MP IMX519 (без автофокуса) именно на Repka Pi 5 через штатный CSI-разъём. Это не самый ходовой выбор, поэтому я сразу готовился к тому, что «из коробки» не заведётся. Но «не из коробки» — это одно, а «пересборка ядра» — совсем другое. До последнего надеялся обойтись малой кровью.
| Компонент | Модель / значение |
|---|---|
| Платформа | Repka Pi 5 (Rockchip RK3588) |
| ОС | Repka OS (Ubuntu 25.10), ядро 6.1.115.1-repka-pi5 |
| Камера | Arducam IMX519 16MP (без автофокуса, fixed-focus) |
| Интерфейс | MIPI-CSI (штатный 22-pin разъём Repka Pi 5) |
| Шлейф | AWM 20624, 22-pin, 0.5 мм pitch (под RP5/Repka Pi 5) |
| Цель | видеопоток с сенсора через V4L2 |
Шлейфы и разъёмы: куда и как вставлять #
Прежде чем лезть в драйверы и логи, разберёмся с физикой — тут легко наступить на грабли ещё до включения. В комплекте с Arducam IMX519 идёт два шлейфа, и они для разных плат. Перепутать — и камера вообще не определится.
💡 Что такое FPC-шлейф? Это гибкий плоский кабель (Flexible Printed Circuit). На одной его стороне — медные «пятаки» (контакты), вторая сторона — гладкая изоляция. Разъём на плате прижимает шлейф контактами к своим «усикам», и от ориентации шлейфа зависит, попадёт ли сигнал на нужные пины. Вставить шлейф «не той стороной» — частая причина «камера не работает».
Два разъёма камер на Raspberry/Repka: 15-pin и 22-pin #
Стандарт MIPI-CSI на платах этого семейства существует в двух физических вариантах:
| Параметр | 15-pin (старый) | 22-pin (новый) |
|---|---|---|
| Шаг контактов (pitch) | 1.0 мм | 0.5 мм (плотнее) |
| Линии данных MIPI | 2 (Lane 0, Lane 1) | 4 (Lane 0–3) — выше пропускная способность |
| Питание камере | 3.3 В | 3.3 В |
| Где применяется | Raspberry Pi 3 / 4 / B+ | Raspberry Pi 5, Pi Zero, Compute Module IO, Repka Pi 5 |
Repka Pi 5 конструктивно повторяет разъём Raspberry Pi 5 — это 22-pin, 0.5 мм pitch. Поэтому для неё подходит только «новый» шлейф. Старый 15-pin сюда физически не воткнёшь без адаптера.
💡 Что такое pitch? Это расстояние между центрами соседних контактов. 0.5 мм у 22-pin против 1.0 мм у 15-pin — значит, шлейф с 22 контактами по ширине примерно такой же, как 15-пиновый, но контакты вдвое плотнее. Отсюда и хрупкость: 0.5-мм шлейф легче повредить и сложнее вставить ровно.
Какой шлейф для какой платы #
В комплекте с Arducam обычно два шлейфа, и их легко перепутать:
| Шлейф | Маркировка | Для какой платы |
|---|---|---|
| UC-236 | (старый, 15-pin, 1.0 мм) | Raspberry Pi 4 и раньше |
| AWM 20624 | (новый, 22-pin, 0.5 мм) | Raspberry Pi 5 / Repka Pi 5 ← наш |
⚠️ На Repka Pi 4 разъёма CSI под этот стандарт НЕТ. Шлейф UC-236 предназначен для Raspberry Pi 4 (и ранних моделей). Если у вас именно Repka Pi 5 — берите шлейф с маркировкой AWM 20624 (или любой 22-pin / 0.5 мм pitch). UC-236 туда не подойдёт ни физически, ни по назначению.
Как вставлять шлейф в разъём CSI Repka Pi 5 #
Это самый капризный момент. У шлейфа AWM 20624 контакты («оголённая часть с пятаками») только с одной стороны. Правильная ориентация для разъёма CSI на Repka Pi 5:
┌─────────────────────────────┐
процессор ← │ контакты (пятаки) ВВЕРХ/к CPU │
│ изоляция (гладкая) ВНИЗ │
└─────────────────────────────┘
▲
сторона БЕЗ контактов направлена к замку разъёма
Простыми словами:
- Оголённая часть с контактами (пятаками) смотрит в сторону процессора (внутрь платы).
- Сторона, где контактов нет (гладкая изоляция), направлена в сторону замка разъёма (наружу).
⚠️ Перевёрнутый шлейф = «камеры нет». Это самая частая аппаратная ошибка. На Raspberry Pi 5 (и на Repka Pi 5, повторяющей её разводку) «контакты вверх» — это неправильно. При перевёрнутом шлейфе сенсор не получит нужные сигналы (CLK/DATA повиснут в воздухе или уйдут не на те пины), и I²C-сканер вообще никого не найдёт. Так что если
i2cdetectпоказывает пустую шину — прежде чем винить драйвер, проверьте ориентацию шлейфа.
💡 Как вставлять. Сначала аккуратно потяните защёлку разъёма вверх (она откидывается на пару миллиметров, не дёргайте силой — 0.5-мм разъём хрупкий). Вставьте шлейф до упора, ровно, без перекоса. Затем опустите защёлку, чтобы она прижала шлейф. Если шлейф вставлен криво — часть контактов не дотянется, и поведение будет непредсказуемым.
В моём случае именно шлейф AWM 20624 оказался нужным — его и вставил в разъём CSI по ориентации «контактами к процессору». После этого I²C-скан (о нём ниже) сразу увидел сенсор, и стало ясно, что физика подключена правильно, а проблема — в софте.
Симптом: камеры нет нигде #
Подключил шлейф, загрузился. Первое, что проверяю всегда, — появились ли устройства:
ls /dev/video* /dev/media*
/dev/video-dec0
/dev/video-enc0
Только два узла, и оба — это аппаратные видео-кодировщик/декодировщик RK3588 (video-enc0, video-dec0). К камере они не имеют отношения. Ни /dev/video0, ни /dev/media0. Камера вообще не видна.
💡 Что такое
/dev/video0и/dev/media0?
В Linux оборудование камеры выставляется через два слоя./dev/video0— это «видеоузел», из которого можно читать кадры (через V4L2)./dev/media0— это узел «медиа-контроллера»: он описывает весь конвейер (сенсор → PHY → CIF → ISP) как граф связей. Нет ни того, ни другого — ядро даже не знает про камеру.
Прежде чем паниковать, полез проверить физику — отвечает ли сенсор на шине I²C. Это подскажет, вставлен ли шлейф правильно:
# Сканируем шины I²C в поисках сенсора (по адресам)
sudo i2cdetect -y 6
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- 0c -- -- --
10: -- -- -- -- -- -- -- -- -- -- 1a -- -- -- -- --
...
50: -- UU -- -- -- -- -- -- 58 -- -- -- -- -- -- --
Отличные новости: на шине I²C-6 кто-то отвечает — 0x1a (это и есть сам IMX519, его стандартный адрес), 0x50 (EEPROM модуля Arducam). Значит, шлейф сидит правильно, питание на модуль подаётся, физика ок. Проблема — софтверная.
Диагностика: почему ядро не видит камеру #
Шаг 1. Есть ли оверлей под мою камеру? #
Проверяю, какие device-tree overlay'ы (о них ниже) идут в комплекте с Repka OS:
ls /boot/dtb/rockchip/overlay/ | grep -iE "csi|cam|imx|ov"
rk3588-csi-imx219.dtbo ← есть только под IMX219 (RPi Camera V2)
Под IMX519 оверлея нет. Только под IMX219 — это другая камера (RPi Camera V2), родственная, но не моя.
Лирическое отступление: device tree и overlay простыми словами #
Чтобы ядро узнало про оборудование на ARM-плате, оно читает device tree (DT) — скомпилированное описание всего железа: какие контроллеры есть, по каким адресам, какие пины за что отвечают, какие регуляторы питания подключены. На Repka он лежит в /boot/dtb/rockchip/rk3588-repka-pi5.dtb.
Overlay — это «патч» к device tree. Базовый DT описывает плату, а overlay добавляет к нему, например, описание камеры: «на шине I²C-6 по адресу 0x1a висит сенсор Sony IMX519, подключён к PHY, требует 24 МГц тактовый сигнал». Overlay хранится в отдельном файле .dtbo и применяется загрузчиком U-Boot при старте.
💡 MIPI-CSI — это интерфейс камер. Две линии данных (data lanes) + одна тактовая линия, по которым сенсор вываливает пиксели. На RK3588 сигнал с камеры проходит длинный путь: сенсор → DPHY (приёмник физического уровня) → CIF (захват) → ISP (обработка). Каждый узел в этом пути — отдельный драйвер в ядре, и все они должны быть описаны в DT и связаны между собой «графом связей» (endpoints).
Шаг 2. Драйвер IMX519 есть в ядре? #
# Что есть в конфиге ядра из сенсоров Sony IMX
zcat /proc/config.gz | grep -E "VIDEO_IMX"
CONFIG_VIDEO_IMX219=y ← IMX219 встроен в ядро
CONFIG_VIDEO_IMX415=y
# CONFIG_VIDEO_IMX519 — такой строки НЕТ
Драйвера IMX519 в ядре нет. Это и понятно: Arducam делает драйвер под Raspberry Pi, в основной поток ядра Linux (mainline) он не влит.
Шаг 3. Конвейер RK3588 включён в ядро? #
А вот это — хорошая новость. Проверяю, что весь путь сенсор→ISP встроен в ядро:
zcat /proc/config.gz | grep -E "ROCKCHIP_ISP|ROCKCHIP_CIF|V4L2_FWNODE|V4L2_ASYNC"
CONFIG_VIDEO_ROCKCHIP_ISP=y ← ISP встроен
CONFIG_VIDEO_ROCKCHIP_CIF=y ← CIF встроен
CONFIG_V4L2_FWNODE=y
CONFIG_V4L2_ASYNC=y ← async-привязка (важно ниже)
Вся инфраструктура MIPI-конвейера RK3588 встроена в ядро. Не хватает только драйвера самого сенсора. То есть задача сводится к: добавить драйвер IMX519 + описать его в device tree.
Сводка после диагностики #
| Проверка | Результат |
|---|---|
/dev/video0, /dev/media0 |
отсутствуют |
| Шина I²C (физика) | ✅ сенсор отвечает на 0x1a, шлейф ок |
| Overlay под IMX519 | ❌ отсутствует (есть только IMX219) |
| Драйвер IMX519 в ядре | ❌ отсутствует |
| Конвейер RK3588 (ISP/CIF/DPHY) | ✅ встроен в ядро |
Вывод: надо портировать драйвер и писать свой overlay. План — в три этапа.
Этап 1. Порт драйвера Arducam под ядро 6.1 #
Arducam держит исходники драйвера в открытом репозитории ArduCAM/IMX519_AK7375. Клонирую:
git clone https://github.com/ArduCAM/IMX519_AK7375.git
Внутри — IMX519/imx519.c (около 2000 строк) и Makefile для out-of-tree сборки. Первая проверка — собирается ли он вообще против ядра Repka:
cd IMX519_AK7375/IMX519
make KDIR=/lib/modules/6.1.115.1-repka-pi5/build
Не собирается. Но — и это главное — все ошибки одного класса: API-drift между ядрами 5.15 и 6.1. Драйвер писался под RPi-ядро ~5.15, а в 6.1 часть типов и функций переименовали. Конкретно:
| Что в драйвере (5.15) | Что стало в 6.1 |
|---|---|
v4l2_subdev_pad_config |
v4l2_subdev_state |
v4l2_async_register_subdev_sensor_common |
v4l2_async_register_subdev_sensor |
imx519_remove() возвращает int |
должен быть void |
MEDIA_BUS_FMT_SENSOR_DATA |
MEDIA_BUS_FMT_METADATA_FIXED |
fh->pad |
fh->state |
Всё это механические правки — переименования, без изменения логики. Аккуратно правлю сигнатуры функций (их штук пять) и пересобираю:
make KDIR=/lib/modules/6.1.115.1-repka-pi5/build
# ...
LD [M] imx519.ko
imx519.ko собрался. Самый рискованный момент — совместимость драйвера с чужим ядром — снят. Это чистый V4L2-драйвер, без завязок на Raspberry Pi.
💡 Out-of-tree vs built-in. Можно грузить драйвер как внешний модуль
.ko(out-of-tree) — он подгружается во время работы системы. А можно «встроить» в ядро ещё на этапе компиляции (CONFIG_VIDEO_IMX519=y), и тогда он едет внутри самогоImage. Как выяснится дальше, для камер на RK3588 эта разница критична.
Этап 2. Device-tree overlay под конвейер RK3588 #
Драйвер есть, но ядро о нём не знает, пока нет описания в DT. Нужно написать свой overlay. Удобно, что в комплекте Repka есть рабочий rk3588-csi-imx219.dtbo под родственную камеру — его декомпилирую и беру за образец:
dtc -I dtb -O dts /boot/dtb/rockchip/overlay/rk3588-csi-imx219.dtbo
Этот overlay описывает всю цепочку CSI→DPHY→CIF→ISP и активирует каждый узел. Топология для IMX519 идентична (тот же разъём, та же разводка Repka), поэтому копирую её 1-в-1. Меняю только фрагмент сенсора под требования драйвера IMX519 (выясненные чтением imx519.c):
compatible = "sony,imx519",reg = <0x1a>(I²C-адрес);- тактовый сигнал 24 МГц (без него сенсор молчит);
- три обязательных питания:
VANA(2.8 В),VDIG(1.05 В),VDDL(1.8 В); - на endpoint — строго
data-lanes = <1 2>иlink-frequencies = /bits/ 64 <493500000>(драйвер это валидирует).
Собираю overlay и кладу рядом с другими:
dtc -@ -I dts -O dtb -o rk3588-csi-imx519.dtbo rk3588-csi-imx519.dts
sudo cp rk3588-csi-imx519.dtbo /boot/dtb/rockchip/overlay/
Включаю его в /boot/repkaEnv.txt (бэкап оригинала — обязательно):
sudo cp /boot/repkaEnv.txt /boot/repkaEnv.txt.bak
# правим строку overlays:
# overlays=panthor-gpu csi-imx519
Перезагрузка — и…
Этап 3. Сенсор опознался, но графа нет (главный сюрприз) #
После ребута проверяю логи:
dmesg | grep -i imx519
imx519 6-001a: Device found is imx519 ← СЕНСОР ОПОЗНАН!
Chip ID прочитан (регистр 0x0016 вернул 0x0519), питание, тактовый сигнал и link-frequency — корректны. Но…
media-ctl -d /dev/media0 -p | grep imx519
# (пусто — imx519 в медиа-графе отсутствует)
Сенсор опознан, а в медиа-графе его нет. RKCIF (захват) при этом непрерывно ругается:
rkcif-mipi-lvds2: rkcif_update_sensor_info: stream[0] get remote terminal sensor failed!
get_remote_sensor: video pad[0] is null
Копаю глубже через debugfs:
cat /sys/kernel/debug/v4l2-async/pending_async_subdevices
imx519 6-001a:
rockchip-csi2-dphy0:
rkisp0-vir0:
rockchip-mipi-csi2:
rkcif-mipi-lvds2:
Все пять узлов конвейера висят в pending — каждый зарегистрировался как async-subdev, но ни один notifier их не «связал». Это ключевое открытие.
Лирическое отступление: v4l2-async и race condition #
Камерный конвейер состоит из независимых драйверов (сенсор, PHY, CIF, ISP), которые probe'ятся в непредсказуемом порядке. Чтобы они «нашли» друг друга, в V4L2 есть механизм async-binding: каждый узел регистрируется как «кандидат» (subdev), и хотя бы один из них создаёт «слушателя» (notifier), который связывает кандидатов по графу связей в device tree.
Проблема в том, что notifier не ждёт вечно. Если на момент завершения notifier'а нужный сенсор ещё не зарегистрировался — связывание не происходит, и граф остаётся разорванным. Сенсор потом подгрузится, но его уже некому «подобрать».
Так и случилось. Таймлайн из dmesg:
12.63s — rkcif notifier COMPLETED (решил, что сенсоров больше не будет)
13.17s — imx519 наконец зарегистрировался (опоздал на полсекунды)
Ложные следы, на которые я чуть не повёлся #
След 1: «Догрузить модуль раньше через initramfs». Добавил imx519 в /etc/initramfs-tools/modules, пересобрал initramfs. Модуль действительно стал грузиться раньше (с 17.9 с до 13.1 с) — но gap всё равно остался, notifier закрывался чуть раньше. Помогло, но не до конца.
След 2: «Перепривязать RKCIF через sysfs». В /sys/bus/platform/drivers/rkcif/ есть bind/unbind. Попробовал отбиндить и забиндить RKCIF заново, чтобы он переслушал notifier, когда сенсор уже на месте. Результат — kernel oops (крах RKCIF). Медиа-граф не рассчитан на повторный probe при существующих сущностях. Этот путь отпал.
После этих граблей стало ясно: лечить надо корень — порядок probe — а не симптомы.
Этап 4. Пересборка ядра с встроенным IMX519 #
Единственное надёжное решение: сделать драйвер built-in (CONFIG_VIDEO_IMX519=y), а не модулем. Тогда probe сенсора пойдёт синхронно с RKCIF в одном цикле, и async-binding сработает штатно. Но для этого нужно пересобрать ядро целиком.
Шаг 1. Исходники нужной версии #
Полные исходники лежат у производителя на GitFlic. Нужен именно репозиторий под RK3588 — npo_rbs/repka-os_linux-rockchip (не путать с repka-os_kernel, это ядро под Repka Pi 4 на Allwinner). Проверяю версию:
git clone --depth 1 --branch master \
https://gitflic.ru/project/npo_rbs/repka-os_linux-rockchip.git
cd repka-os_linux-rockchip
grep -E "^(VERSION|PATCHLEVEL|SUBLEVEL|EXTRAVERSION)" Makefile
VERSION = 6
PATCHLEVEL = 1
SUBLEVEL = 115
EXTRAVERSION = .1
Точное совпадение с установленным ядром 6.1.115.1-repka-pi5.
Шаг 2. Встраиваю драйвер в дерево #
Три правки:
# 1. Исходник — в дерево
cp imx519.c drivers/media/i2c/imx519.c
# 2. Запись в Kconfig (рядом с imx219)
cat >> drivers/media/i2c/Kconfig <<'EOF'
config VIDEO_IMX519
tristate "Sony IMX519 sensor support"
depends on I2C && VIDEO_DEV
select MEDIA_CONTROLLER
select VIDEO_V4L2_SUBDEV_API
select V4L2_FWNODE
help
Sony IMX519 camera (Arducam 16MP).
EOF
# 3. Запись в Makefile
echo 'obj-$(CONFIG_VIDEO_IMX519) += imx519.o' >> drivers/media/i2c/Makefile
Шаг 3. Конфиг от текущей системы #
Беру готовый .config от установленного ядра — чтобы собрать с теми же опциями, что работают сейчас:
cp /boot/config-6.1.115.1-repka-pi5 .config
# Включаю IMX519 как built-in
sed -i '/^CONFIG_VIDEO_IMX219=y$/a CONFIG_VIDEO_IMX519=y' .config
make ARCH=arm64 oldconfig # нормализовать конфиг
Шаг 4. Сборка #
make ARCH=arm64 -j8 Image dtbs modules
⚠️ Место и время. Полное дерево ядра — ~2 ГБ, сборка съедает ещё ~4 ГБ. На 8 ядрах RK3588 сборка идёт ~15–20 минут. На 16 ГБ microSD я не уместился — пришлось перенести систему на 64 ГБ eMMC. Учитывайте объём носителя заранее.
⚠️ Охлаждение обязательно. При сборке грузятся все 8 ядер на 100% подряд, минут 15. С активным охлаждением (вентилятор) температура процессора доходит до ~70 °C — терпимо. А вот без вентилятора RK3588 быстро уходит в троттлинг: сбрасывает частоты, сборка растягивается в разы, а при перегреве ядра плата может и зависнуть на ровном месте. Так что собирайте ядро только с обдувом — это не рекомендация, а необходимое условие. Если у платы только радиатор без вентилятора — снимите корпус, поставьте внешний кулер, или собирайте короткими заходами с паузами на остывание.
Сборка падала дважды, и оба раза по моей вине:
certs/extract-cert.c: openssl/bio.h: Нет такого файла— не хваталоlibssl-dev. Лечитсяapt install libssl-dev.fixdep: error opening file ... No such file— гонка при параллельной сборке: я запустил две пересекающиеся фоновые сборки в одном дереве, и они повредили.o-файлы друг друга. Лечитсяmake cleanи одной чистой сборкой черезsetsid.
Проверка, что драйвер действительно встроен:
grep imx519 drivers/media/i2c/built-in.a # imx519.o присутствует → built-in ✓
ls -lh arch/arm64/boot/Image # ядро собрано
Этап 5. Установка ядра (с откатом) #
Это самая рискованная часть — есть шанс, что новое ядро не загрузится. Поэтому сначала бэкапы.
⚠️ Перед установкой ядра — обязательно снимите резервные копии. Если новое ядро не встанет, плата может не загрузиться, и откатить можно будет только с другой системы (сняв eMMC/SD и подмонтировав на ПК) или через serial-консоль. Ниже — что именно бэкапить и как возвращать.
# Бэкап ядра и DTB
sudo cp /boot/vmlinuz-6.1.115.1-repka-pi5 \
/boot/vmlinuz-6.1.115.1-repka-pi5.bak-orig
sudo cp /boot/dtb/rockchip/rk3588-repka-pi5.dtb \
/boot/dtb/rockchip/rk3588-repka-pi5.dtb.bak-orig
# Бэкап модулей
sudo mv /lib/modules/6.1.115.1-repka-pi5 \
/lib/modules/6.1.115.1-repka-pi5.bak-orig
Установка. Замечу: текущее ядро у Repka — gzip-сжатый Image, поэтому наше (изначально несжатое) сжимаем так же:
gzip -9 -c arch/arm64/boot/Image > /boot/vmlinuz-6.1.115.1-repka-pi5
sudo cp arch/arm64/boot/dts/rockchip/rk3588-repka-pi5.dtb \
/boot/dtb/rockchip/rk3588-repka-pi5.dtb
sudo make ARCH=arm64 modules_install
sudo depmod -a 6.1.115.1-repka-pi5
Финальная проверка, что imx519 теперь в modules.builtin (значит, встроен в ядро, а не отдельный файл):
grep imx519 /lib/modules/6.1.115.1-repka-pi5/modules.builtin
# kernel/drivers/media/i2c/imx519.ko ← встроен ✓
После перезагрузки: кажется, заработало #
Самый волнительный момент всей истории — перезагрузка с пересобранным ядром. Запускаю проверку по списку:
uname -r
# 6.1.115.1-repka-pi5 ← наше ядро грузится ✓
dmesg | grep imx519
[ 12.255] platform csi2-dphy0: Fixed dependency cycle(s) with /i2c@fec80000/camera-imx519@1a
[ 12.267] imx519 6-001a: Looking up VANA-supply from device tree
[ 12.267] imx519 6-001a: Looking up VDIG-supply from device tree
[ 12.267] imx519 6-001a: Looking up VDDL-supply from device tree
[ 12.277] imx519 6-001a: Device found is imx519 ← сенсор опознан ✓
Главный признак успеха — теперь смотрю, перестал ли RKCIF ругаться на сенсор:
dmesg | grep -c "get remote terminal sensor failed"
# 0 ← НОЛЬ строк! Раньше их были десятки
Ноль. Async-binding прошёл — сенсор привязался к конвейеру. Это решило проблему порядка загрузки, над которой я бился. Но, как выяснится ниже, это была лишь первая из стен.
Камера в медиа-графе #
Смотрю топологию:
media-ctl -d /dev/media0 -p | grep -i imx519
- entity 63: imx519 6-001a (2 pads, 1 link, 0 routes)
pad0: Source
[stream:0 fmt:SRGGB10_1X10/1920x1080 field:none colorspace:srgb ...]
-> "rockchip-mipi-csi2":0 [ENABLED] ← линк активен
Сенсор появился как полноправная сущность графа, линк к CSI2 приёмнику ENABLED. Формат SRGGB10_1X10 (10-битный байеровский RAW) propagated через весь путь сенсор→DPHY→CSI2→CIF. А в crop-границах сенсора виден и полный 16-мегапиксельный режим:
crop.bounds:(8,48)/4656x3496 ← доступен полный кадр IMX519
Сенсор живой и управляемый #
Проверяю, что камера не просто «висит» в графе, а реально управляется:
v4l2-ctl -d /dev/v4l-subdev2 --list-ctrls-menus | head
User Controls
exposure 0x00980911 (int) : min=20 max=1147 step=1 default=1000 value=1000
horizontal_flip (bool) : default=0 value=0
vertical_flip (bool) : default=0 value=0
Image Source Controls
analogue_gain (int) : min=0 max=960 step=1 default=0 value=0
red_pixel_value (int) : min=0 max=4095 ... ← BLC (коррекция чёрного)
Exposure, gain, повороты, BLC — все controls отвечают. Камера полностью интегрирована в V4L2.
💡 Почему я обрадовался. Из всей эпопеи самым сложным оказалось не «написать драйвер» и не «собрать ядро», а заставить сенсор привязаться к медиа-графу. На RK3588 это требует синхронного порядка probe, который достижим только при built-in драйвере. И вот — сенсор в графе, линки ENABLED, controls живые. Казалось бы, победа. Но, как выяснилось, я праздновал слишком рано.
Ложная победа: камера в графе, но кадра нет #
Обрадовавшись, что сенсор привязался к конвейеру, я пошёл за финальным призом — первым кадром. Ведь есть /dev/video0, /dev/video11 (ISP mainpath), камера в графе... Должно же заработать? Как бы не так.
v4l2-ctl -d /dev/video11 --set-fmt-video=width=1920,height=1080,pixelformat=NV12
v4l2-ctl -d /dev/video11 --stream-mmap --stream-count=1 --stream-to=test.nv12
VIDIOC_STREAMON returned -1 (Operation not permitted)
И пустой файл test.nv12 в 0 байт. Попытки через CIF-узлы (/dev/video0) — error 515. Камера «есть», а картинку не отдаёт. Тут я понял: «сенсор в медиа-графе» — это ещё не «камера работает». Между этими двумя состояниями — целая пропасть, которую я в азарте перепрыгнул мысленно, но не фактически.
💡 В чём была ошибка. Я принял промежуточную веху (async-binding прошёл, сенсор виден в графе) за финальную (поток идёт, кадр ловится). Это классическая ловушка отладки: «всё зеленое в чек-листе» создаёт ложное чувство завершённости. Реальный критерий успеха — не
media-ctlпоказывает сенсор, а файл с пикселями ненулевого размера.
Почему кадра нет: новый слой проблем #
Стал разбираться, и вскрылся совершенно отдельный мир проблем — экосистема Rockchip ISP. Оказалось, что на RK3588 между RAW-сенсором и готовым RGB/YUV стоит ISP (Image Signal Processor — аппаратный процессор обработки изображений), и путь через него не «автоматический», а требует целой связки:
rkaiq_3A_server— сервер автоэкспозиции/автофокуса/баланса белого (3A). Без него ISP не знает, как обрабатывать кадр.- IQ-файл (
/etc/iqfiles/*.json) — калибровка ISP под конкретный сенсор (BLC, AWB, vignetting, цветовая матрица). Без него rkaiq не запустится. - Тонкая настройка мульти-device медиа-графа — CIF живёт на
/dev/media0, а ISP на/dev/media1, и их надо связать через rawrd-мост.
Пробую запустить rkaiq и сразу натыкаюсь на стену:
rkaiq_3A_server
CAMHW:E:248:parse sensor entity name imx519 6-001a error at 0, please check sensor driver!
CAMHW:E:@get_sensor_caps /dev/v4l-subdev2: Get sensor module info failed
CAMHW:E:get isp or ispp info fail, something gos wrong!
parse sensor entity name imx519 6-001a error — rkaiq не может разобрать имя сенсора! И это при том, что сам сенсор в графе прекрасно виден. Оказывается, rkaiq парсит имя сущности по строго определённому формату m<индекс>_<сторона>_<сенсор>, а наш драйвер оставил стандартное V4L2-имя imx519 6-001a. Плюс rkaiq дёргает специальный ioctl RKMODULE_GET_MODULE_INFO, чтобы получить имя модуля и подобрать IQ-файл — а наш Arducam-драйвер этот ioctl не реализует.
Что выяснилось о рабочем примере #
Чтобы понять, чего не хватает, разобрал рабочий imx219.c из дерева ядра Repka — для него есть и IQ-файл, и rkaiq его привязывает. И увидел три вещи, которых нет в Arducam-версии imx519:
#include <linux/rk-camera-module.h>— заголовок с Rockchip-специфичными структурами.- Чтение
rockchip,camera-module-*из device tree (index, facing, name, lens) — это те самые свойства, что я прописал в overlay, но драйвер их не читал. - Формирование имени в формате Rockchip:
snprintf(sd->name, ..., "m%02d_%c_%s %s", ...)— даётm00_b_imx219 6-0010, а неimx219 6-0010. - Обработка ioctl
RKMODULE_GET_MODULE_INFO+RKMODULE_GET_HDR_CFG+ compat-обёртка для 32-бит.
То есть «работающий» imx219 от Rockchip — это не тот же imx219, что в mainline. Это модифицированная версия с интеграцией в проприетарный стек rkaiq. Наш Arducam imx519 этой интеграции лишён — он «чистый» V4L2-драйвер.
⚠️ Ловушка «у меня есть драйвер». Когда находишь драйвер под свой сенсор (особенно от производителя камеры), легко решить, что этого достаточно. Но на RK3588 драйвер сенсора — только первый слой. Над ним ещё Rockchip-специфичный «клей» (module info, rkaiq-ioctl, IQ-файл), и без него сенсор хоть и виден, но бесполезен для картинки.
Итог ложной победы #
| Думал, что достиг | На самом деле |
|---|---|
| «Камера работает» | Сенсор виден в графе, но кадр не получается |
| Драйвер «готов» | Драйвер V4L2-готов, но не интегрирован в rkaiq |
| Конвейер «настроен» | Конвейер сенсор→CIF есть, но CIF↔ISP мост не активен |
| Осталось «только снять кадр» | Нужен целый второй акт: доработка драйвера + IQ-файл + rkaiq |
Так «финальная» глава превратилась в пролог ко второму акту — интеграции с Rockchip ISP. И вот этот второй акт разворачивается прямо здесь, потому что он оказался не короче первого.
Второй акт: интеграция с rkaiq и стена split-pipeline #
Раз поняв, чего не хватает, я засучил рукава и доработал драйвер по образцу imx219: добавил #include <linux/rk-camera-module.h>, чтение rockchip,camera-module-* из device tree, формирование имени в формате m00_b_imx519 и обработку ioctl RKMODULE_GET_MODULE_INFO. Заодно создал IQ-файл imx519_arducam-imx519_default.json, скопировав его с ближайшего родственника — imx219 (калибровка будет неточной по цветам, но rkaiq должен запуститься).
Пересобрал ядро заново, перезагрузился и проверяю имя сенсора:
media-ctl -d /dev/media0 -p | grep -A1 "CAM_SENSOR\|imx519"
- entity 63: m00_b_imx519 6-001a (2 pads, 1 link, 0 routes)
type V4L2 subdev subtype Sensor flags 0
Имя изменилось на m00_b_imx519 — rockchip-формат на месте. Запускаю rkaiq с надеждой:
rkaiq_3A_server
Cound not find rkisp dev names, skipped /dev/media0
ERR: Bad media topology for: /dev/media0
CAMHW:E:get isp or ispp info fail, something gos wrong!
Старая ошибка parse sensor entity name error исчезла — rkaiq теперь распознаёт сенсор. Но на её месте выросла новая: Bad media topology. Прогресс есть, но стена просто отодвинулась.
Диагноз: split-pipeline #
Стал разбираться, в чём дело. На RK3588 медиа-инфраструктура оказалась разрезана надвое:
/dev/media0 (rkcif): сенсор → DPHY → CSI2 → CIF stream-узлы
/dev/media1 (rkisp0): rkisp-isp-subdev ← rawrd0_m ← ... → mainpath
Сенсор и CIF-захват живут на одном media-устройстве, а ISP (процессор обработки) — на другом. rkaiq же перебирает /dev/media%d и для каждого ищет сенсор и ISP вместе. На media0 есть сенсор, но нет ISP; на media1 — наоборот. Ни одно устройство не подходит, отсюда Bad media topology для всех.
При этом сам rkaiq_3A_server за всю историю платы не отработал успешно ни разу — 2102 «Bad topology» и 0 «engine succeed» в логах. То есть это не специфика моего imx519: камерный стек сломан здесь для любого сенсора.
💡 Что такое split-pipeline. На RK3588 есть две аппаратные единицы: CIF (Camera Interface — захват сырого потока с MIPI) и ISP (Image Signal Processor — обработка: дебайеризация, баланс белого, гамма). В простом режиме они работают как отдельные подсистемы и создают два независимых media-device. Связь между ними — через DDR: CIF пишет туда RAW-кадр, а ISP забирает его через узлы
rawrd*. Но rkaiq хочет видеть весь путь как единый граф на одном media-устройстве — и в split-режиме этого не получает.
Перепроверил гипотезы (и отбросил две) #
Прежде чем лезть в device tree, проверил версии и попытался обойти rkaiq вручную.
Гипотеза 1: несовпадение версий rkaiq ↔ ядро. Сравнил: rkaiq 5.0x4.1-rk3588 пишет ISP HW ver: 30, в конфиге ядра CONFIG_VIDEO_ROCKCHIP_ISP_VERSION_V30=y, rkisp-драйвер v02.09.00. Всё совпадает. Версии — не причина.
Гипотеза 2: получить кадр в обход rkaiq. На Neardi Wiki нашёл рабочий пример захвата с mainpath через v4l2-ctl --stream-mmap=3 --stream-skip=3. Попробовал то же у себя: активировал линк rkisp_rawrd0_m → rkisp-isp-subdev, выставил форматы, запустил захват. Результат — select timeout, файл 0 байт. CIF-узел /dev/video0 и вовсе не открывается (error 515 / EOPNOTSUPP).
Замкнутый круг: ISP (rawrd на media1) читает кадр из DDR, куда его должен записать CIF (на media0). Но CIF не запускается без «terminal subdev» синхронизации с ISP, а ISP не запускается без данных от CIF. Каждая сторона ждёт другую — и никто не начинает.
Решение в исходниках: rkisp-unite #
Интернет по Bad media topology давал мало: issue #254 на ubuntu-rockchip закрыт без решения (автор ушёл на Arch). Зато в исходниках самого ядра Repka нашёл подсказку — в rk3588s.dtsi узел rkisp0_vir0 содержит закомментированную альтернативу:
rkisp0_vir0: rkisp0-vir0 {
rockchip,hw = <&rkisp0>; /* одиночный ISP0 → split */
/*
* dual isp process image case
* other rkisp hw and virtual nodes should disabled
* rockchip,hw = <&rkisp_unite>; ← объединённый режим */
*/
};
А в rk3588s-tablet-single.dtsi этот режим включён явно: rkisp_unite + rkisp_unite_mmu в okay, rkisp0_vir0 указывает на rkisp_unite, а одиночный rkisp0 — выключен. Это и есть dual-ISP unite mode, при котором два ISP-блока объединяются в единый media-graph. И судя по документации Firefly, топология overlay у меня верная — не хватает только перевести её в unite-режим.
Что меняю в overlay #
Добавил в свой rk3588-csi-imx519.dts три изменения:
| Узел | Было (split) | Стало (unite) |
|---|---|---|
rkisp0 (одиночный ISP0) |
status = "okay" |
status = "disabled" |
rkisp0_vir0 |
rockchip,hw = <&rkisp0> |
rockchip,hw = <&rkisp_unite> |
rkisp_unite |
disabled |
status = "okay" |
rkisp_unite_mmu |
disabled |
status = "okay" |
После перекомпиляции overlay проверил, что все метки фиксапов (включая новые rkisp_unite, rkisp_unite_mmu) присутствуют в базовом DTB — иначе overlay не применится. Применились, ок.
Ожидание: после перезагрузки rkaiq должен найти сенсор и ISP на одном media-устройстве, Bad media topology исчезнет, и кадр пойдёт. Но это пока гипотеза — проверить можно только ребутом.
💡 Почему я не уверен. Unite-режим официально описан для планшетных конфигураций RK3588 (где он работает). На Repka Pi 5 никто его, судя по всему, не включал — отсюда и сломанный rkaiq «из коробки». Шанс, что unite поднимет единый граф — высокий, но не 100%. Если не поможет — останется последний рычаг: стриминг через
libv4l-rkmpp/ GStreamer rk-плагин в обход rkaiq. Но это уже третья стена, и о ней — если до неё дойдёт.
Третий акт: kernel-отладка, или шесть багов в чужом драйвере #
Unite оказался ложным следом: он объединяет два ISP-блока для удвоения ширины кадра, а не сливает media-устройства. Сенсор по-прежнему на media0, ISP — на media1, rkaiq так же падает. Тогда я перестал гадать и пошёл по жёсткому пути — добавлять отладочный вывод прямо в ядро и собирать его после каждого изменения. Этот третий акт состоял из шестнадцати пересборок ядра и шести найденных багов, каждый из которых блокировал свой этап. Рассказываю по порядку — это полезно даже если у вас другой сенсор.
Баг №1 и №2: g_frame_interval без фильтра ENOIOCTLCMD #
Сначала добавил pr_info в rkcif_fh_open (open-handler CIF) и увидел точное место отказа:
RKCIF_OPEN: ENTER stream[0]
RKCIF_OPEN: attach_hw ok
RKCIF_OPEN: update_sensor_info FAIL ret=-515
515 = ENOIOCTLCMD — «ioctl не реализован». Оказалось, в rkcif_update_sensor_info (capture.c) CIF вызывает у сенсора g_frame_interval — необязательный video-subdev-op, который наш imx519 не реализует. Соседние проверки корректно фильтруют этот код:
// Правильно (соседи):
ret = v4l2_subdev_call(..., get_mbus_config, ...);
if (ret && ret != -ENOIOCTLCMD) { return ret; }
// Баг (наш случай):
ret = v4l2_subdev_call(..., g_frame_interval, ...);
if (ret) { return ret; } ← не фильтрует ENOIOCTLCMD → open падает с -515
Починил — добавил фильтр. open() заработал. Но тут же вскрылся второй экземпляр того же бага — в rkcif_set_fmt. Там g_frame_interval тоже без фильтра возвращал -515, из-за чего cif_fmt_out оставался NULL, а потом VIDIOC_REQBUFS делал null-deref → kernel oops → зависание платы (watchdog тормозил перезагрузку). Тот же фикс, и crash ушёл.
💡 Урок. Когда находишь баг «отсутствует фильтр ENOIOCTLCMD» — проверь все вызовы
g_frame_intervalв файле. У RKCIF их восемь, и два были сломаны одинаково. Лечится одним паттерном.
Баг №3: field = INTERLACED вместо NONE #
После фиксов open и set_fmt заработали, но STREAMON падал с EINVAL в rkcif_sanity_check_fmt. Добавил отладку в rkcif_get_input_fmt и увидел:
RKCIF_FMT: sensor code=0x300f field=1 size=1920x1080
code=0x300f (SRGGB10_1X10) — правильно. Но field=1 (INTERLACED) — неправильно, должно быть 0 (NONE). В таблице in_fmts для SRGGB10_1X10 только field=NONE, поэтому совпадения нет → NULL → EINVAL.
Наш драйвер в imx519_update_image_pad_format устанавливал width/height, но не трогал field — и ядро V4L2 оставляло там мусор (1). Фикс — одна строка:
fmt->format.field = V4L2_FIELD_NONE;
Баг №4: crop bounds больше input #
Sanity прошла, но новая ошибка: crop size is bigger than input. Сенсор в get_selection(CROP_BOUNDS) возвращал полный pixel array (8, 48, 4656, 3496), а CIF копировал это в свой crop и проверял crop.left + crop.width <= input.width. При left=8, width=4656 сумма 4664 > 4656 — превышение на 8 пикселей. На Raspberry Pi unicam это прощает, RKCIF — нет.
Фикс — в get_selection для CROP_BOUNDS возвращать размер текущего mode, а не весь pixel array:
case V4L2_SEL_TGT_CROP_BOUNDS:
sel->r.left = 0;
sel->r.top = 0;
sel->r.width = imx519->mode->width;
sel->r.height = imx519->mode->height;
Баг №5: dummy buffer размера 0 #
Crop прошёл, но STREAMON упал с ENOMEM. Лог: Failed to allocate the memory for dummy buffer, size 0. CIF пытался создать dummy-буфер (для режима без реального стрима), но размер вычислялся через enum_frame_interval, который наш сенсор не реализует → размер оставался 0.
Обход — выключить dummy через sysfs:
echo 0 > /sys/devices/platform/rkcif-mipi-lvds2/is_use_dummybuf
Баг №6: LINK_FREQ без control-флагов (финальный софтверный) #
После dummybuf ошибка сменилась на EPIPE, а в логах:
rockchip-csi2-dphy0: No pixel rate control in subdev
DPHY ищет у сенсора control V4L2_CID_LINK_FREQ (сообщение про «pixel rate» вводит в заблуждение). Я добавил регистрацию LINK_FREQ в imx519_init_controls, но с ошибкой — передал &imx519_ctrl_ops вместо NULL. Из-за этого V4L2 считал control изменяемым, вызывал s_ctrl, который не обрабатывает LINK_FREQ → ctrl(id:0x9f0901,val:0x0) is not handled → stream off.
// Баг:
v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx519_ctrl_ops, V4L2_CID_LINK_FREQ, ...);
// Правильно (как в imx219):
v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ, ...);
После фикса not handled исчез, и DPHY наконец включился:
rockchip-csi2-dphy0: dphy0, data_rate_mbps 987
csi2_dphy_s_stream stream on:1, dphy1, ret 0
Прогресс симптомов — вся картина #
Шесть багов, каждый блокировал свой этап. Симптомы эволюционировали так:
| Этап | Симптом | Баг | Фикс |
|---|---|---|---|
open() |
error 515 |
g_frame_interval без фильтра | ret != -ENOIOCTLCMD |
set_fmt |
kernel oops → hang | то же в set_fmt → null deref | тот же фикс |
sanity_check |
EINVAL |
field=INTERLACED | field = V4L2_FIELD_NONE |
sanity_check |
EINVAL crop |
bounds > input | bounds = mode size |
STREAMON |
ENOMEM |
dummy buffer size 0 | is_use_dummybuf=0 |
STREAMON |
EPIPE |
нет LINK_FREQ control | v4l2_ctrl_new_int_menu(..., NULL, ...) |
STREAMON |
stream ON, кадра нет | физический слой MIPI | (открытая задача) |
Где мы сейчас: сенсор стримит, кадра нет #
После шести фиксов pipeline полностью активируется — но кадр всё ещё 0 байт. Казалось бы, опять стена. Но чтобы понять, где именно обрывается поток, я добавил отладку в сам сенсорный драйвер — в imx519_set_stream и imx519_start_streaming. И получил ответ, который всё расставил по местам:
dmesg | grep -E "SET_STREAM|START_STREAMING"
imx519 6-001a: SET_STREAM: enable=1 streaming=0
imx519 6-001a: SET_STREAM: pm_runtime_get_sync ret=0
imx519 6-001a: START_STREAMING: mode=1920x1080
imx519 6-001a: START_STREAMING: writing common regs (272)
imx519 6-001a: START_STREAMING: common regs OK
imx519 6-001a: START_STREAMING: writing mode regs (73)
imx519 6-001a: START_STREAMING: mode regs OK
imx519 6-001a: START_STREAMING: ctrl setup OK
imx519 6-001a: START_STREAMING: writing MODE_SELECT=STREAMING
imx519 6-001a: START_STREAMING: MODE_SELECT ret=0 (DONE)
imx519 6-001a: SET_STREAM: start_streaming ret=0
Каждый шаг — ret=0. Сенсор полностью инициализируется: 272 общих регистра + 73 регистра режима записаны по I²C без единой ошибки, controls применены, регистр MODE_SELECT переведён в режим стриминга. Команда s_stream(1) дошла до сенсора, и он подтвердил готовность. Софтверная инициализация — идеальна.
DPHY тоже включается корректно:
rockchip-csi2-dphy0: dphy0, data_rate_mbps 987
csi2_dphy_s_stream stream on:1, dphy1, ret 0
987 Мбит/с = 493.5 МГц × 2 (DDR) — это ровно та link frequency, которую мы задали в overlay. DPHY вычислил тайминги из LINK_FREQ control, который мы добавили в баге №6. Всё сходится.
Последняя софтверная попытка: continuous clock #
Я попробовал убрать clock-noncontinuous из device-tree overlay — многие сенсоры требуют именно continuous clock (DPHY постоянно генерирует тактовый сигнал, а не только в момент передачи данных). Перекомпилировал overlay, перезагрузился — без изменений, кадр по-прежнему 0 байт.
Проверка register-tables: может, сенсор настроен неправильно? #
Раз continuous clock не помог, возникло подозрение: а вдруг register-tables драйвера (писавшиеся Arducam для Raspberry Pi) настраивают MIPI-выход сенсора в режим, несовместимый с RK3588 DPHY? Например, неправильное число lanes, или PLL выдаёт не ту частоту.
Проверил. В driver-таблицах для всех режимов:
{0x0114, 0x01}, // CSI_LANE_MODE = 2-lane — правильно для RK3588
{0x0301, 0x06}, // PLL VTPXCK_DIV
{0x0303, 0x04}, // PLL VTSYCK_DIV
{0x0307, 0x40}, // PLL_VT_MPY
// ... и так далее
Регистр 0x0114 (CSI lane mode) = 0x01 — это 2-lane режим, ровно то, что наш overlay и заявляет (data-lanes = <1 2>). PLL-настройки тоже есть и записываются без ошибок (видели в логе START_STREAMING). Поиск по интернету показал: IMX519 datasheet — под NDA Sony, и единственный источник register-tables — это сам Arducam-драйвер. Альтернативных RK3588-специфичных таблиц не существует. При этом те же таблицы работают на Raspberry Pi — значит регистры сами по себе корректны, и MIPI-выход сенсора настроен правильно.
Register-tables не виноваты.
Итог: граница софтвера #
Это и есть та стена, дальше которой софтверная отладка не проходит. Все доказательства говорят одно:
- ✅ Сенсор инициализирован и стримит (345 регистров, все
ret=0) - ✅ DPHY включён с правильным таймингом (987 Мбит/с)
- ✅ Pipeline активирован без единой ошибки
- ❌ MIPI-данные не доходят от сенсора до CIF
При этом I²C работает (chip-ID читается, регистры пишутся), а MIPI high-speed lanes — нет. Это возможно, потому что I²C и MIPI — разные пары проводов в шлейфе: I²C (SCL/SDA) может быть подключен правильно, а MIPI lanes (CLK±, D0±, D1±) — нет. Или register-tables от Arducam, писавшиеся для bcm2835 (Raspberry Pi), настраивают MIPI-выход сенсора в режим, несовместимый с ожиданиями RK3588 DPHY.
Дальнейшая диагностика требует физического доступа — осциллографа или логического анализатора на MIPI-линиях, чтобы увидеть, есть ли реально high-speed сигнал в момент стриминга. Это уже не та задача, которую решает правка кода.
💡 Главный урок третьего акта. Я дважды ошибался с гипотезами (metadata-pad, rkisp-unite), пока не перестал гадать и не начал инструментировать ядро напрямую.
pr_infoв нужной функции за одну итерацию даёт больше, чем часы чтения кода и гугления. Когда упёрся в стену — добавь отладочный вывод в каждую проверку функции, пересобери, и последняя напечатанная строка покажет точное место отказа. Это дороже (цикл сборки), но вернее любых догадок. И только когда каждый шаг в логе показываетret=0, а результата всё равно нет — пора признать, что проблема не в коде, а в физике.
Если новое ядро не загрузилось: откат #
Если после перезагрузки плата не поднимается (не отвечает по сети, нет картинки на HDMI) — скорее всего, ядро встало криво. Откат делается с другой системы: вынимаете накопитель (SD/eMMC), подмонтируете его на ПК или на второй Repka, и возвращаете файлы из бэкапов:
# На другой системе, с подмонтированным загрузочным разделом платы (например, /mnt)
cp /mnt/boot/vmlinuz-6.1.115.1-repka-pi5.bak-orig \
/mnt/boot/vmlinuz-6.1.115.1-repka-pi5
cp /mnt/boot/dtb/rockchip/rk3588-repka-pi5.dtb.bak-orig \
/mnt/boot/dtb/rockchip/rk3588-repka-pi5.dtb
# Вернуть модули
rm -rf /mnt/lib/modules/6.1.115.1-repka-pi5
mv /mnt/lib/modules/6.1.115.1-repka-pi5.bak-orig \
/mnt/lib/modules/6.1.115.1-repka-pi5
# (опционально) выключить overlay, если он мешает
sed -i 's/^overlays=.*/overlays=panthor-gpu/' /mnt/boot/repkaEnv.txt
После возврата файлов плата грузится со старым ядром, как ни в чём не бывало.
💡 Если плата грузится, но камера всё ещё не работает — это уже не «ядро не встало», а софтверная отладка: смотрите
dmesg | grep imx519, проверяйте, что overlay применился (ls /proc/device-tree/i2c@fec80000/camera-imx519@1a/), и крутите параметры overlay (регуляторы, тактовый сигнал). На этом этапе откат не нужен — ядро рабочее, просто камера требует доводки.
Если грузится, но отвалилось Wi-Fi/сеть: битый modules.dep #
Это самый коварный сценарий: плата грузится, вы радуетесь, а потом обнаруживаете, что отвалился Wi-Fi, пропал графический логин, или не работает звук. Ядро-то новое, а вот модули — недоустановлены.
Как это происходит #
При пересборке ядра make modules_install копирует сотни .ko-файлов и генерирует modules.dep — индекс, по которому modprobe находит модули. Если в момент установки происходит перезагрузка (watchdog, зависание, случайный ребут) — modules_install обрывается на середине. Файлы .ko скопированы частично, а modules.dep остаётся пустым или обрезанным (153 байта вместо нормальных ~50 КБ).
Результат: modprobe aic8800_fdrv (драйвер Wi-Fi) не находит модуль, потому что depmod не зарегистрировал его в индексе. Wi-Fi не поднимается. То же — с любым другим модулем: звуком, GPU, Bluetooth.
Как диагностировать #
# Размер modules.dep — если около 150 байт, он сломан
ls -l /lib/modules/$(uname -r)/modules.dep
# Должно быть ~50000 байт; если 150 — пересобирайте
# Проверить, знает ли modprobe о нужном модуле
modprobe -c | grep aic8800 # пусто = проблема
# Модуль физически есть, но не находится
find /lib/modules/$(uname -r) -name "aic8800*"
Как починить #
Если плата доступна (Ethernet работает, или через HDMI/клавиатуру) — перегенерируйте индекс и переустановите модули:
# Способ 1: быстрый — только пересобрать индекс (если .ko файлы на месте)
cd /root/linux-rockchip # там, где дерево исходников
make ARCH=arm64 modules_install # полная переустановка модулей
depmod -a $(uname -r) # принудительно пересобрать индекс
# Способ 2: если дерево исходников недоступно, только depmod
depmod -a $(uname -r)
ls -l /lib/modules/$(uname -r)/modules.dep # проверить размер
# Проверить, что модуль находится теперь
modprobe -c | grep aic8800
# Загрузить вручную
modprobe aic8800_fdrv
ip link show wlan0 # должен появиться интерфейс
Если Ethernet не работает (модуль dwmac тоже отвалился) — подключите монитор и USB-клавиатуру, или выпишите накопитель и сделайте depmod с другой системы, подмонтировав раздел.
⚠️ Мораль. Никогда не перезагружайтесь, пока
make modules_installполностью не завершён. Проверяйте, что последняя строка вывода —DEPMOD /lib/modules/..., а размерmodules.dep— порядка 50 КБ. Если сомневаетесь — запуститеdepmod -a $(uname -r)вручную после установки. Это спасёт вас от «отвалившегося Wi-Fi после пересборки ядра».
Что вообще может «отвалиться» #
Модульная подсистема RK3588 на Repka включает десятки драйверов, которые грузятся через modprobe:
| Что | Модуль | Симптом пропажи |
|---|---|---|
| Wi-Fi | aic8800_fdrv, aic8800_bsp |
нет wlan0 |
| Звук (HDMI) | snd-hdmi |
нет звука |
| GPU | panthor |
нет графики |
| Видео-кодеки | mpp_* |
не работает аппаратное видео |
| Камера (если модуль) | imx519 |
нет /dev/video* |
Если что-то из этого пропало после пересборки — сначала проверьте modules.dep, а не вините ядро.
Итоги #
| Параметр | Значение |
|---|---|
| Камера | Arducam IMX519 16MP (fixed-focus) |
| Платформа | Repka Pi 5 (RK3588), ядро 6.1.115.1-repka-pi5 |
| Путь | пересборка ядра + 6 баг-фиксов в RKCIF/imx519 (через kernel-отладку) |
| Драйвер | портирован 5.15→6.1 + RKMODULE ioctl + rockchip-имя + LINK_FREQ + crop/field фиксы |
| Overlay | собственный rk3588-csi-imx519.dtbo |
| Физика | ✅ сенсор отвечает на I²C, шлейф корректен |
| Ядро после ребута | ✅ грузится, chip ID 0x0519 прочитан |
| Камера в медиа-графе | ✅ entity m00_b_imx519, линки ENABLED |
open() / set_fmt / sanity |
✅ работают (после фиксов g_frame_interval, field, crop) |
| Pipeline активация | ✅ stream ON, DPHY включён (987 Мбит/с) |
| Сенсор инициализация | ✅ 345 регистров записаны, MODE_SELECT=STREAMING ret=0 |
| Готовый кадр | ❌ MIPI-данные не доходят — физический слой (осциллограф) |
| Баг-фиксов в чужом коде | 6 (g_frame_interval ×2, field, crop bounds, dummybuf, LINK_FREQ) |
| Пересборок ядра | 16 |
| Бэкапы | ядро, DTB, модули, overlay — всё сохранено для отката |
Главный неочевидный вывод всей истории — их три, и все болезненные.
Первый: на RK3588 разница между out-of-tree модулем и built-in драйвером — не формальность. Модуль грузится позже завершения async-notifier'а RKCIF, и сенсор «теряется». Лишь встроив драйвер в ядро, удаётся выставить правильный порядок probe.
Второй: «сенсор в медиа-графе» — тоже ещё не победа, и даже pipeline, который активируется — не победа. Каждый снятый софтверный баг открывал следующий, и только дойдя до stream ON без ошибок, стало ясно, что дальше — уже физика MIPI.
Третий, главный: перестань гадать — инструментируй. Я дважды ошибался с гипотезами (metadata-pad, rkisp-unite), пока не начал добавлять pr_info прямо в функции ядра. Одна итерация сборки с отладкой давала точный диагноз, который часы чтения кода не давали. Это дороже (цикл сборки ~5 мин), но вернее любых догадок.
Уроки, которые я вынес #
- Сначала физика, потом софт. Проверка I²C-сканером за минуту подтвердила, что шлейф и питание ок, и проблема — в драйверах/DT, а не в «битой камере». Не полезет ничего — начинай с самого нижнего уровня.
- Out-of-tree vs built-in — не одно и то же на RK3588. Модуль технически работает (chip ID читается), но не привязывается к медиа-графу из-за race condition с notifier'ом. Если «всё собралось, но графа нет» — копай в сторону порядка probe, и не трать время на перепривязку через sysfs (крахнет RKCIF).
- Не запускай параллельные сборки в одном дереве. Две пересекающиеся
makeв одном дереве объектов дают «гонку» с потерянными.d-файлами и непонятными ошибкамиfixdep. Одна чистая сборка черезsetsid— и всё соберётся. - Празднуй только на финальной вехе. Я отметил победу, когда сенсор появился в медиа-графе с ENABLED-линком — и ошибся. На RK3588 «драйвер собрался» → «chip ID прочитался» → «сенсор в графе» → «файл с пикселями ненулевого размера» — это четыре разных уровня, и только последний настоящая победа. Всё остальное — промежуточные ступени, любая из которых может оказаться ложным финишем.
Шпаргалка команд #
# Камера видна на I²C?
sudo i2cdetect -y 6
# Какие overlay камер есть в комплекте
ls /boot/dtb/rockchip/overlay/ | grep -iE "csi|cam|imx"
# Сборка out-of-tree модуля (проверка совместимости)
make KDIR=/lib/modules/$(uname -r)/build
# Компиляция overlay
dtc -@ -I dts -O dtb -o rk3588-csi-imx519.dtbo rk3588-csi-imx519.dts
# Сборка ядра (built-in imx519)
make ARCH=arm64 -j8 Image dtbs modules
# Проверка, что драйвер встроен
grep imx519 drivers/media/i2c/built-in.a
# Логи камеры после загрузки
dmesg | grep -i imx519
# Ключевая проверка — привязался ли сенсор к CIF:
dmesg | grep -c "get remote terminal sensor failed" # 0 = успех
# Async-привязка (кто ждёт кого; пусто = все связаны)
cat /sys/kernel/debug/v4l2-async/pending_async_subdevices
# Топология медиа-графа + проверка, что imx519 в нём с ENABLED-линком
media-ctl -d /dev/media0 -p | grep -iA2 imx519
# Формат и доступные размеры сенсора
v4l2-ctl -d /dev/v4l-subdev2 --get-subdev-fmt pad=0,stream=0
# Controls сенсора (exposure, gain, flip) — живые?
v4l2-ctl -d /dev/v4l-subdev2 --list-ctrls-menus
# Откат ядра (с другой системы, на подмонтированном разделе /mnt)
cp /mnt/boot/vmlinuz-6.1.115.1-repka-pi5.bak-orig \
/mnt/boot/vmlinuz-6.1.115.1-repka-pi5
cp /mnt/boot/dtb/rockchip/rk3588-repka-pi5.dtb.bak-orig \
/mnt/boot/dtb/rockchip/rk3588-repka-pi5.dtb