Предыдущие железяки: MXQ 4K и сетевой видеорегистратор
А вот была еще такая интересная штука: ТВ-бокс X88mini13
Заявлено 4Гб RAM и 32Гб emmc, процессор RK3528 - в принципе, неплохо.
Софт опять сейчас не интересует: Андроид + обычные для приставок программы, можно устанавливать новые - словом, обычный Андроид.
Внутри — аккуратная плата, видны отдельно микросхемы RAM, отдельно чип на 32Гб (в центре фото черный прямоугольник), в углу платы три контакта под гребенку, serial port — вот тут он понадобится.
Скорость соединения 1 500 000 бод, смотрим:
Как обычно, проходит загрузка, видны и 4Гб RAM, и 32Гб emmc — не соврали. Процессор 3528, 64-битный — тоже хорошо.
Сразу загружается Андроид, и мы попадаем в шелл, юзером. Валятся системные сообшения от ядра, мешают, но их можно игнорировать.
Юзер в Андроиде мало что может сделать, но есть интересный лайфхак:
system/xbin/su
И мы получаем рут!
Впрочем, в Андроиде и рут достаточно ограничен в правах, но многого и не требуется - нужно сначала сделать бекап.
Подключаем SD-карту, тут желательно на 64Гб, она даже определяется и монтируется, под именем типа /mnt/media_rw/XXXXXXXXXXXXXX
Смотрим, какие есть устройства:
ls /dev/block/mmcblk*
Там будет что-то типа
mmcblk1
mmcblk1p1
mmcblk2
mmcblk2p1
mmcblk2p2
mmcblk2p3
mmcblk2p4
mmcblk2p5
mmcblk2p6
mmcblk2p7
mmcblk2p8
mmcblk2p9
mmcblk2p10
mmcblk2p11
mmcblk2p12
mmcblk2p13
mmcblk2p14
mmcblk2p15
В данном случае mmcblk1 - SD-карта, mmcblk2 - встроенная память, на ней много разделов.
Таблица разделов - GPT, у каждого свое название, если потом заглянуть в эту таблицу, а пока надо скопировать.
Бекап всего целиком сделать проще всего:
cd /mnt/media_rw/XXXXXXXXXXXXXX
dd if=/dev/block/mmcblk2 of=backup.bin
В результате на SD-карте будет создан образ всей emmc устройства, он нам еще пригодится.
А теперь - как превратить это в сервер.
Конкретно с этой моделью есть одна неявная ловушка, в которую, судя по профильным форумам, многие попались:
Дело в том, что ее можно легко запустить с рядом Armbian-прошивок, буквально просто записав их на SD-карту.
Например, Armbian_community_24.11.0-trunk.273_Hinlink-h28k_bookworm_vendor_6.1.75_minimal.img прекрасно подходит: записываем образ на карту, вставляем в девайс, перезагружаемся - и вот она, работающая система.
Остается, по советам из тех же форумов, запустить программу armbian-install, и перенести ОС на встроенную память.
И всё, вы попались. Да, устройства на базе Rockchip "окирпичить" сложно, но есть нюанс.
В частности, эта конкретная модель использует память DDR3 (не LPDDR3), и ее нужно правильно инициализировать в самом начале загрузки SPL.
Штатный SPL из прошивки сначала делает это, потом загружает U-Boot из прошивки, и только потом ищет и загружает ОС, на SD-карте или из внутренней памяти. В любом случае сначала загружается SPL из встроенной emmc.
А вот такая попытка перенести ОС приводит к перезаписи SPL/U-Boot на версию из загружаемого образа, которая совсем не обязательно умеет инициализировать DDR3.
В результате загрузка с внутренней памяти начинается нормально, но рабочей RAM еще нет, и загрузка виснет на этапе запуска U-Boot. К загрузке ОС с карты переходить некому.
Конечно, у нас есть бекап, и можно вынуть из него и записать этот SPL с U-Boot на карту - но устройство не будет пытаться стартовать с карты, ведь у него есть рабочая внутренняя память и исправный загрузчик там, правда, он память подключать не умеет...
Вот тут бы подошли советы типа закорачивания пина Clk на землю: тогда устройство бы поняло, что emmc не работает и надо загружаться с карты - но пины недоступны: emmc в BGA-чипе, все дорожки под маской, а из оставшихся доступных для закорачивания контактов для этих целей не подходит ни один, проверил все.
Получаем классический "кирпич".
Однако, способ есть, хоть и варварский: радикальная хирургия. Если с помощью паяльного фена аккуратно выпаять чип emmc с платы - вот тогда загружаться устройство будет с SD-карты. Остается только правильно отформатировать карту, перенести на нее первые 32768 секторов и создав как минимум первые два раздела точно как были в прошивке, а на оставшееся место записывать разделы boot и root Линукса.
Теряем 32Гб памяти - зато получаем рабочую систему.
Но если до этого не дошло - лучше пойти другим путем, и заодно собрать свою сборку ОС.
Если посмотреть на сохраненный бекап через fdisk - можно увидеть разделы на нем:
1 8192 16383 4.0 MiB FFFF security
2 16384 24575 4.0 MiB FFFF uboot
3 24576 32767 4.0 MiB FFFF trust
4 32768 40959 4.0 MiB FFFF misc
5 40960 49151 4.0 MiB FFFF dtbo
6 49152 51199 1024.0 KiB FFFF vbmeta
7 51200 182271 64.0 MiB FFFF boot
8 182272 378879 96.0 MiB FFFF recovery
9 378880 1165311 384.0 MiB FFFF backup
10 1165312 1951743 384.0 MiB FFFF cache
11 1951744 1984511 16.0 MiB FFFF metadata
12 1984512 1985535 512.0 KiB FFFF frp
13 1985536 1987583 1024.0 KiB FFFF baseparameter
14 1987584 8360959 3.0 GiB FFFF super
15 8360960 61071295 25.1 GiB FFFF userdata
Нам нужен 7, boot:
losetup -P loopX filename
ls /dev/loopX*
dd if=/dev/loopXp7 of=p7
Если посмотреть на него в HEX - в начале видим:
00000000 41 4e 44 52 4f 49 44 21 08 f8 07 02 00 80 00 10 |ANDROID!........|
00000010 c3 ee 15 00 00 00 00 11 00 c6 56 00 00 00 f0 10 |..........V.....|
00000020 00 01 00 10 00 08 00 00 02 00 00 00 78 01 00 18 |............x...|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 63 6f 6e 73 6f 6c 65 3d 74 74 79 46 49 51 30 20 |console=ttyFIQ0 |
00000050 66 69 72 6d 77 61 72 65 5f 63 6c 61 73 73 2e 70 |firmware_class.p|
00000060 61 74 68 3d 2f 76 65 6e 64 6f 72 2f 65 74 63 2f |ath=/vendor/etc/|
00000070 66 69 72 6d 77 61 72 65 20 69 6e 69 74 3d 2f 69 |firmware init=/i|
Это загрузочный образ ядра Андроид.
Распаковываем:
mkdir dir
unpackbootimg -i p7 -o dir
В каталоге dir появляются файлы, из которых состоит этот образ. Нам нужны отсюда два:
p7-kernel
p7-dtb
Возвращаемся к TV-box: если при перезагрузке нажать Ctrl-C - попадаем в U-Boot
printenv
arch=arm
autoload=no
baudrate=1500000
board=evb_rk3528
board_name=evb_rk3528
boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr}
boot_extlinux=sysboot ${devtype} ${devnum}:${distro_bootpart} any ${scriptaddr} ${prefix}extlinux/extlinux.conf
boot_net_usb_start=usb start
boot_prefixes=/ /boot/
boot_script_dhcp=boot.scr.uimg
boot_scripts=boot.scr.uimg boot.scr
boot_targets=mmc1 mmc0 mtd2 mtd1 mtd0 usb0 pxe dhcp
bootargs=storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal androidboot.dtb_idx=0 androidboot.dtbo_idx=0
bootcmd=boot_android ${devtype} ${devnum};boot_fit;bootrkp;run distro_bootcmd;
bootcmd_dhcp=run boot_net_usb_start; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;
bootcmd_mmc0=setenv devnum 0; run mmc_boot
bootcmd_mmc1=setenv devnum 1; run mmc_boot
bootcmd_mtd0=setenv devnum 0; run mtd_boot
bootcmd_mtd1=setenv devnum 1; run mtd_boot
bootcmd_mtd2=setenv devnum 2; run mtd_boot
bootcmd_pxe=run boot_net_usb_start; dhcp; if pxe get; then pxe boot; fi
bootcmd_usb0=setenv devnum 0; run usb_boot
bootdelay=0
cpu=armv8
devnum=0
devtype=mmc
distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done
eth1addr=22:fa:77:25:50:71
ethaddr=30:a6:12:0f:2c:da
fdt_addr_r=0x08300000
kernel_addr_c=0x04080000
kernel_addr_r=0x00280000
mmc_boot=if mmc dev ${devnum}; then setenv devtype mmc; run scan_dev_for_boot_part; fi
partitions=uuid_disk=${uuid_gpt_disk};name=uboot,start=8MB,size=4MB,uuid=${uuid_gpt_loader2};name=trust,size=4M,uuid=${uuid_gpt_atf};name=misc,size=4MB,uuid=${uuid_gpt_misc};name=resource,size=16MB,uuid=${uuid_gpt_resource};name=kernel,size=32M,uuid=${uuid_gpt_kernel};name=boot,size=32M,bootable,uuid=${uuid_gpt_boot};name=recovery,size=32M,uuid=${uuid_gpt_recovery};name=backup,size=112M,uuid=${uuid_gpt_backup};name=cache,size=512M,uuid=${uuid_gpt_cache};name=system,size=2048M,uuid=${uuid_gpt_system};name=metadata,size=16M,uuid=${uuid_gpt_metadata};name=vendor,size=32M,uuid=${uuid_gpt_vendor};name=oem,size=32M,uuid=${uuid_gpt_oem};name=frp,size=512K,uuid=${uuid_gpt_frp};name=security,size=2M,uuid=${uuid_gpt_security};name=userdata,size=-,uuid=${uuid_gpt_userdata};
pxefile_addr_r=0x00e00000
ramdisk_addr_r=0x0a200000
rkimg_bootdev=if mmc dev 1 && rkimgtest mmc 1; then setenv devtype mmc; setenv devnum 1; echo Boot from SDcard;elif mmc dev 0; then setenv devtype mmc; setenv devnum 0;elif mtd_blk dev 0; then setenv devtype mtd; setenv devnum 0;elif mtd_blk dev 1; then setenv devtype mtd; setenv devnum 1;elif mtd_blk dev 2; then setenv devtype mtd; setenv devnum 2;elif rknand dev 0; then setenv devtype rknand; setenv devnum 0;elif rksfc dev 0; then setenv devtype spinand; setenv devnum 0;elif rksfc dev 1; then setenv devtype spinor; setenv devnum 1;else;setenv devtype ramdisk; setenv devnum 0;fi;
scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_extlinux; run scan_dev_for_scripts; done;
scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done
scan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}extlinux/extlinux.conf; then echo Found ${prefix}extlinux/extlinux.conf; run boot_extlinux; echo SCRIPT FAILED: continuing...; fi
scan_dev_for_scripts=for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done
scriptaddr=0x00c00000
serial#=MN2404W1050264
soc=rockchip
stderr=vidconsole
stdin=serial
stdout=vidconsole
usb_boot=usb start; if usb dev ${devnum}; then setenv devtype usb; run scan_dev_for_boot_part; fi
vendor=rockchip
Environment size: 4064/32764 bytes
Тут целая система скриптов, определяющая порядок загрузки ОС. Если присмотреться - U-Boot ищет на доступных устройствах загрузочные разделы, а потом может запускать из них ОС, если находит extlinux/extlinux.conf.
Структура extlinux/extlinux.conf очень простая:
default x
LABEL x
linux /XX-kernel
initrd /XX-initrd.img.gz
fdt /XX.dtb
append root=/dev/XXXXX rw rootwait rootfstype=ext4
То есть нужен kernel, dtb и rootfs. Первые два есть, третий можно сделать на основе busybox.
wget -P downloads https://busybox.net/downloads/busybox-1.35.0.tar.bz2
После распаковки:
make defconfig
make LDFLAGS=-static
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- LDFLAGS=-static
Формируем ФС:
mkdir memroot
cd memroot
echo "#!/bin/sh" > init
chmod 777 init
mkdir -p bin dev proc sys
cd bin
cp ..XXX/build/busybox ./
for prog in $(./busybox --list); do ln -s /bin/busybox $prog; done
Создаем простейший init: (можно и по другому, но так проще шелл получить)
#! /bin/sh
mount -n -t devtmpfs udev /dev
mkdir -p /dev/pts
mount -n -t devpts devpts /dev/pts
mount -n -t sysfs sysfs /sys
mount -n -t proc proc /proc
/bin/sh
Упаковка:
find . | cpio -o -H newc --owner root:root > ../initrd.img
cd ../
gzip initrd.img
Теперь есть файл initrd.img.gz, можно собирать ФС для загрузки.
На SD с помощью fdisk размечаем разделы.
Первый раздел - 300Мб, второй - всё остальное.
Ищем опции и проставляем признак LegacyBoot (A) для первого раздела.
Сохраняем (w), форматируем:
mkfs.ext4 /dev/sdb1
mkfs.ext4 /dev/sdb2
mount /dev/sdb1 /mnt
Копируем в первый раздел kernel, dtb, initrd.img.gz
Создаем каталог и файл extlinux/extlinux.conf
Поправляем в нем имена, чтобы указывали на наши файлы
Строка "append ..." пока не важна, хотя можно предположить что она будет выглядеть так:
append root=/dev/mmcblk1p2 rw rootwait rootfstype=ext4
указывая на второй раздел устройства mmcblk1 (SD-карты), который пока пустой.
В принципе этого уже почти достаточно. Если вставить карту в устройство и попробовать перезагрузиться - должно загрузиться ядро с карты, и терминал вывалится в шелл busybox.Можно проверить, под какими именами появятся устройства в /dev.
Должно быть примерно так:
ls /dev/mmcblk*
mmcblk1
mmcblk1p1
mmcblk1p2
mmcblk2
mmcblk2p1
mmcblk2p2
mmcblk2p3
mmcblk2p4
mmcblk2p5
mmcblk2p6
mmcblk2p7
mmcblk2p8
mmcblk2p9
mmcblk2p10
mmcblk2p11
mmcblk2p12
mmcblk2p13
mmcblk2p14
mmcblk2p15
Тут надо убедиться, что файл /dev/mmcblk1p2 соответствует строке root=/dev/mmcblk1p2 в extlinux.conf, и при необходимости поправить:
mkdir -p /tmp/boot
mount /dev/mmcblk1p1 /tmp/boot
vi /tmp/boot/extlinux/extlinux.conf
umount /tmp/boot
Теперь нужно собрать образ ОС, переразбить встроенную emmc и записать всё туда.
Общий принцип такой: с помощью debootstrap формируем ФС Дебиано-подобной ОС (debian, ubuntu, kali - man debootstrap):
mkdir rootfs
debootstrap --foreign --include=qemu-user-static --arch arm64 bookworm ./rootfs/
chroot ./rootfs/ /debootstrap/debootstrap --second-stage
chroot ./rootfs/ /bin/bash
passwd
apt update
apt install XXX1 XXX2 etc.
exit
Сохраняем для переноса:
cd rootfs
tar czf ../rootfs.tgz .
Теперь полученный rootfs.tgz копируем на 2 раздел SD-карты, и там же на месте распаковываем, архив не удаляем:
tar xzf rootfs.tgz
Теперь можно снова попытаться загрузиться с карты. Снова попадаем в шелл busybox, но теперь нужно перейти в созданную ОС:
mkdir -p /mnt/boot
mount /dev/mmcblk1p2 /mnt
mount /dev/mmcblk1p1 /mnt/boot
mkdir -p /mnt/dev/pts
mount -n --move /sys /mnt/sys
mount -n --move /proc /mnt/proc
mount -n --move /dev/pts /mnt/dev/pts
mount -n --move /dev /mnt/dev
chroot /mnt /bin/bash
pwd
/
Теперь остается переразбивка встроенной emmc:
fdisk /dev/mmcblk2
Убираем все разделы кроме 1 и 2, создаем 3 300Мб, 4 - всё оставшееся.
Через (x) ставим признак загружабельности (A) на 3 раздел.
Сохраняем, форматируем разделы:
mkfs.ext4 /dev/mmcblk2p3
mkfs.ext4 /dev/mmcblk2p4
Копируем содержиое 1 раздела карты в 3 emmc, поправляем строку в /extlinux/extlinux.conf
mount /dev/mmcblk2p3 /mnt
cd /mnt
tar cf - -C /boot . | tar xf -
vi extlinux/extlinux.conf
append root=/dev/mmcblk2p4 rw rootwait rootfstype=ext4
cd /
umount /mnt
Копируем содержимое раздела 2 карты в 4 emmc (оно есть в архиве - скопируем и распакуем):
mount /dev/mmcblk2p4 /mnt
cp rootfs.tgz /mnt/
cd /mnt
tar xzf rootfs.tgz
cd /
umount /mnt
Если всё сделано правильно - после перезагрузки без SD-карты загружаемся уже с внутренней emmc и опять попадаем в шелл busybox.
Остается настроить init-скрипт. В принципе, это можно сделать на компьютере, а можно и прямо на устройстве:
Примонтировать разделы:
mkdir -p /mnt/boot
mount /dev/mmcblk2p4 /mnt
mount /dev/mmcblk2p3 /mnt/boot
Перейти в Дебиан:
mkdir -p /mnt/dev/pts
mount -n --move /sys /mnt/sys
mount -n --move /proc /mnt/proc
mount -n --move /dev/pts /mnt/dev/pts
mount -n --move /dev /mnt/dev
chroot /mnt /bin/bash
Убедиться, что установлена cpio, или доустановить ее
cd /boot
mkdir dir
gunzup initrd.img.gz
cd dir
cpio -iv < ../initrd.img
Поправить init скрипт:
#! /bin/sh
mount -n -t devtmpfs udev /dev
mkdir -p /dev/pts
mount -n -t devpts devpts /dev/pts
mount -n -t sysfs sysfs /sys
mount -n -t proc proc /proc
ROOT_DEV=/dev/mmcblk2p4
BOOT_DEV=/dev/mmcblk2p3
while [ ! -b $ROOT_DEV ] ; do
sleep 1
done
mkdir -p /mnt/boot
mount $ROOT_DEV /mnt
mount $BOOT_DEV /mnt/boot
sleep 1
cd /mnt
exec switch_root . "/sbin/init" "$@"
Запаковываем обратно:
rm ../initrd.img
find . | cpio -o -H newc --owner root:root > ../initrd.img
cd ../
gzip initrd.img
Ах да, и не забыть проверить, что установлен пароль root, а лучше добавить пользователя и настроить sudo.
После очередной перезагрузки в терминале будет уже не шелл, а запрос логина и пароля.
В принципе, ОС уже установлена, остались нюансы типа настройки dhcp на сетевом интерфейсе, установки даты, и желательно — сервера ntp, хотя можно обойтись и запросами ntpdate.
С неправильной датой система пакетов apt не работает, так что возможно первоначально время придется поставить вручную.
Конечно, можно спросить, зачем вообще заморачиваться с вытаскиванием родного ядра и сборкой ОС, если есть работающие сборки Armbian?
Ну хотя бы для того чтобы попробовать собрать ОС, не используя сборки, чтобы повторить это потом при необходимости в других случаях, и не убить при этом загрузчик.
А родное ядро, помимо всего прочего, имеет и родной интерфейс к аппаратному криптомодулю чипа, устройство /dev/crypto, которое тоже может на что‑то сгодиться.
На выходе — работающий мини‑сервер 4/32 Гб, 64-битный Дебиан.
Автор: JBFW