Заметил в продаже новенький TV-бокс H96 MAX M1 на чипсете Rockchip RK3528, с 4Гб памяти, новым 13-м Андроидом, и подозрительно дешевый. Зная, что на старых чипах Rockchip уже запускали Линукс, я решил заказать и попробовать.
Можете заметить разъёмы питания/USB/HDMI/AV… Как думаете, в чём подвох? Наверное на другом боку еще пара USB и слот для карты памяти, но нет — других разъёмов для вас нет. Сразу напишу — разработчикам такое покупать не стоит, это создаёт много проблем, но раз уж купил — то работаю с чем есть.
Для запуска Линукс первым делом надо получить оригинальный Device Tree, вытащив .dtb из boot или recovery раздела. Этим я и занялся. Rockchip устройств у меня еще не было и по отзывам у меня складывалось ощущение производителя, что не ставит палки в колёса разработчикам, но оказалось — это уже в прошлом.
Характеристики устройства
Называется H96 MAX M1, с одной стороны корпуса разъёмы: питания (5V), HDMI, USB (2.0 судя по чёрному цвету), и AV в виде разъёма для 3.5 миниджека. С противоположной стороны есть ИК-приёмник для пульта и синий/красный диоды для индикации состояния. Есть Wi-Fi и Bluetooth.
Чипсет RK3528, это в чём-то улучшенный RK3328, хотя в чём — неизвестно, тот же 4-х ядерный Cortex-A53. Частота не то 1.5ГГц (их характеристик найденных в интернете), не то 2ГГц (указано в Device Tree). Возможно 2ГГц это boost частота, а 1.5ГГц обычная.
GPU: Mali-450, в RK3328 такой же.
4Гб оперативной памяти, видимо DDR4 (из Device Tree), на плате состоит из восьми чипов, по четыре с каждой стороны. 64Гб флэш памяти.
Выпускается в таких вариантах ОЗУ/ПЗУ: 2/16, 4/32, 4/64.
Что внутри
Закрыто на защёлки, ничем кроме плоской отвёртки открыть не смог, а защёлки всегда легко сломать. Лучше бы было на винтах. Есть радиатор для процессора.
Около AV разъёма видны следы пайки с обоих сторон — припаяны две тонкие проволочки, что-то чинили.
Видны три дырочки под пины, квадрат-круг-круг, похоже это UART, но контакты не подписаны и они очень мелкие, расстояние между центрами отверстий примерно 1.25-1.5мм.
Приступим к чтению разделов
Есть готовая опенсорс утилита для доступа к интерфейсу прошивки EMMC на устройстве:
https://github.com/linux-rockchip/rkflashtool
Но сначала надо ввести устройство в download режим, для этого при включении питания должна быть зажата загрузочная кнопка. Что на смартфонах одна из кнопок громкости, а какие могут быть кнопки у TV-бокса? На некоторых моделях можно найти дырочку в корпусе, через которую скрепкой/зубочисткой можно нажать на скрытую кнопку. Здесь кнопка спрятана в AV разъёме. Если там есть кнопка, то слышится щелчок при нажатии. Со всей силы давить туда не нужно, чтобы не сломать.
MicroUSB/type-C разъёма тут нет, поэтому для подключения к компьютеру понадобится кабель с одинаковыми разъёмами type-A, так называемый AM-AM (AM значит type-A Male).
Далее надо зажать подходящим тонким предметом кнопку в AV разъёме и подключить питание, через 1-2 секунды кнопку можно отпустить, устройство будет пару минут ждать первого использования download режима через USB.
Удобная особенность этого TV-бокса, что он работает от одного только кабеля USB подключенного к компьютеру, поэтому к блоку питания его не подключал, зажал загрузочную кнопку зубочисткой и в это время подключил к компьютеру через USB.
В lsusb заметил новое устройство:
ID 2207:350c Fuzhou Rockchip Electronics Company USB download gadget
В исходниках rkflashtool есть таблица кодов устройств, в котором RK3528 еще нет. Следовательно добавляем:
--- a/rkflashtool.c
+++ b/rkflashtool.c
@@ -117,6 +117,7 @@ static const struct t_pid {
{ 0x320c, "RK3328" },
{ 0x330a, "RK3368" },
{ 0x330c, "RK3399" },
+ { 0x350c, "RK3528" },
{ 0, "" },
};
Для компиляции понадобится libusb-devel пакет.
Чтобы не надо было вызывать rkflashtool с правами рута, можно сделать USB устройство доступным для непривилегированных пользователей, создав файл /etc/udev/rules.d/80-rockchip.rules
с таким содержимым:
# Rockchip RK3528
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2207", ATTRS{idProduct}=="350c", MODE="0666", TAG+="uaccess"
Ничего перезагружать не нужно, правила будут работать при новом подключении USB устройства. Поэтому надо отключить USB и заново подключить в download режиме.
При запуске rkflashtool
без параметров — выводится список команд, из которых мне нужны эти:
rkflashtool r partname >outfile read flash partition
rkflashtool w partname outfile read flash
rkflashtool w offset nsectors
Для работы с именами разделов требуется сторонняя утилита mtdparts
, а я лучше покажу как работать без неё.
Сначала начнём с чтения таблицы разделов - GPT (GUID Partition Table, а не то сокращение что сейчас на слуху.)
$ ./rkflashtool r 0 32 | hexdump -C
Вместо hexdump -C
можно использовать hd
, но не во всех дистрибутивах Линукс есть такое сокращение.
Покажу часть интересных для меня разделов из этого дампа:
00000480 00 00 XX XX 00 00 72 4e 80 00 49 b3 00 00 17 72 |..b...rN..I....r|
00000490 00 00 21 5a 00 00 04 4e 80 00 30 0c 00 00 20 cc |..!Z...N..0... .|
000004a0 00 40 00 00 00 00 00 00 ff 5f 00 00 00 00 00 00 |.@......._......|
000004b0 00 00 00 00 00 00 00 00 75 00 62 00 6f 00 6f 00 |........u.b.o.o.|
000004c0 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |t...............|
00000700 00 00 0c a2 00 00 7a 44 80 00 6f 7b 00 00 28 06 |......zD..o{..(.|
00000710 00 00 50 15 00 00 46 4f 80 00 23 51 00 00 3b 98 |..P...FO..#Q..;.|
00000720 00 c8 00 00 00 00 00 00 ff 57 02 00 00 00 00 00 |.........W......|
00000730 00 00 00 00 00 00 00 00 62 00 6f 00 6f 00 74 00 |........b.o.o.t.|
00000740 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000780 00 00 3f a1 00 00 3c 4e 80 00 35 8c 00 00 6e 33 |..?......`0|
000007a0 00 58 02 00 00 00 00 00 ff 57 05 00 00 00 00 00 |.X.......W......|
000007b0 00 00 00 00 00 00 00 00 72 00 65 00 63 00 6f 00 |........r.e.c.o.|
000007c0 76 00 65 00 72 00 79 00 00 00 00 00 00 00 00 00 |v.e.r.y.........|
Названия разделов видны по текстовой части дампа. Перед названием идёт два 8-байтовых (64 бита) значений записанных в little-endian:
000004a0 00 40 00 00 00 00 00 00 ff 5f 00 00 00 00 00 00
То есть 0x4000 и 0x5fff, это индексы первого сектора раздела и последнего. Один сектор это 512 байт, rkflashtool
работает в тех же единицах.
Сделаем дамп раздела uboot, это загрузчик который запускает Android, а также реализует этот download режим со стороны устройства. Чтобы не вычислять в уме, можно использовать "arithmetic expansion" из bash:
$ first=0x4000; last=0x5fff; ./rkflashtool r $first $((1+$last-$first)) > uboot.img
Выражение $((1+$last-$first))
превратится в 8192.
Обрадовался что чтение разделов работает как положено, но не тут то было.
Сделал дамп разделов boot и recovery, заметил что дамп recovery полностью состоит из байтов 0xCC, заглянул в boot - там начало правильное, но посредине тоже начинаются байты 0xCC. Байты 0xCC начинаются по границе 32Мб, это 0x10000 секторов по 512.
Решил что rkflashtool
не работает с новым устройством и нашел другой способ дампа, через Андроид (приведён в конце). А потом смог найти на xdaforums пользователей что жалуются на то же самое. Один из них нашел причину и исправил её.
Вкратце - Rockchip в коде U-boot решил ограничить чтение на 0x10000 секторов, далее возвращаются байты 0xCC. При этом запись не ограничена.
U-boot не является большой тайной и его исходные коды доступны, вот место в cmd/rockusb.c что ограничивает чтение:
static int rkusb_read_sector(struct ums *ums_dev,
ulong start, lbaint_t blkcnt, void *buf)
{
struct blk_desc *block_dev = &ums_dev->block_dev;
lbaint_t blkstart = start + ums_dev->start_sector;
int ret;
if ((blkstart + blkcnt) > RKUSB_READ_LIMIT_ADDR) {
memset(buf, 0xcc, blkcnt * SECTOR_SIZE);
return blkcnt;
} else {
ret = blk_dread(block_dev, blkstart, blkcnt, buf);
if (!ret)
ret = -EIO;
return ret;
}
}
Этот код находится в uboot, который расположен до 32-го мегабайта, поэтому сдампился полностью.
Заголовок uboot.img: D0 0D FE ED
- как ни странно является заголовком DTB (Device Tree Binary).
DTB декомпилируются в текстовый вид через утилиту device-tree-compiler
(есть готовая во многих дистрибутивах Линукс), устанавливаем и вызываем:
$ dtc -O dts uboot.img -o uboot.dts
Обращаем внимание на эти записи в начале полученного uboot.dts:
images {
uboot {
data-size = <0x113760>;
data-position = <0x1000>;
description = "U-Boot";
type = "standalone";
arch = "arm64";
os = "U-Boot";
compression = "none";
load = <0x200000>;
hash {
value = <0xfde6c46b 0x85eb6b88 0xd0a056a4 0x25a23bcf 0x79fe28a1 0xcac609f 0x3e4d7e06 0x6e89472f>;
algo = "sha256";
};
};
Размер кода и данных U-boot что загружаются в память: data-size = <0x113760>
Их позиция в файле: data-position = <0x1000>
Адрес в памяти устройства по которому они загружаются: load = <0x200000>
Также sha256 хэш - при изменении надо его пересчитать.
Загрузил код в дизассемблер и нашел функцию rkusb_read_sector
:
00210914 FD 7B BE A9 STP X29, X30, [SP, #-0x20]!
00210918 FD 03 00 91 MOV X29, SP
0021091C 04 18 40 B9 LDR W4, [X0, #0x18]
00210920 F3 0B 00 F9 STR X19, [SP, #0x10]
00210924 81 00 01 8B ADD X1, X4, X1
00210928 24 00 02 8B ADD X4, X1, X2
0021092C 9F 40 40 F1 CMP X4, #0x10, LSL #12
00210930 49 01 00 54 B.LS 00210958
00210934 F3 03 02 AA MOV X19, X2
00210938 81 19 80 52 MOV W1, #0xCC
0021093C 42 D8 77 D3 LSL X2, X2, #9
00210940 E0 03 03 AA MOV X0, X3
00210944 67 5B 02 94 BL memset
00210948 E0 03 13 2A MOV W0, W19
0021094C F3 0B 40 F9 LDR X19, [SP, #0x10]
00210950 FD 7B C2 A8 LDP X29, X30, [SP], #0x20
00210954 C0 03 5F D6 RET
00210958 00 A0 00 91 ADD X0, X0, #0x28
0021095C 0E CF 00 94 BL blk_dread
00210960 1F 00 00 71 CMP W0, #0
00210964 93 00 80 12 MOV W19, #0xFFFFFFFB
00210968 00 10 93 1A CSEL W0, W0, W19, NE
0021096C F8 FF FF 17 B 0021094C
Чтобы избавиться от ограничения, нужно заменить условный бранч:
00210930 49 01 00 54 B.LS 00210958
На безусловный:
00210930 0A 00 00 14 B 00210958
Я изменил скрипт предложенный в посте на xdaforums, чтобы он сам искал и исправлял начало этой функции. Значения sz
и pos
надо взять из своего uboot.dts.
from hashlib import sha256
SZ = 0x200000
with open('uboot.img', 'rb') as f:
img = f.read()
assert img[SZ:] == img[:SZ]
replace_old = bytearray.fromhex("\
FD 7B BE A9 FD 03 00 91 04 18 40 B9 F3 0B 00 F9 \
81 00 01 8B 24 00 02 8B 9F 40 40 F1 49 01 00 54 \
F3 03 02 AA 81 19 80 52 42 D8 77 D3 E0 03 03 AA ")
replace_new = bytearray.fromhex("\
FD 7B BE A9 FD 03 00 91 04 18 40 B9 F3 0B 00 F9 \
81 00 01 8B 24 00 02 8B 9F 40 40 F1 0A 00 00 14 \
F3 03 02 AA 81 19 80 52 42 D8 77 D3 E0 03 03 AA ")
replace_offset = img.find(replace_old)
assert replace_offset > 0
sz = 0x113760
pos = 0x1000
h = sha256(img[pos:pos + sz]).digest()
hash_offset = img.find(h)
assert hash_offset > 0
uboot_patched = bytearray(img)
uboot_patched[replace_offset:replace_offset + len(replace_new)] = replace_new
uboot_patched = uboot_patched[pos:pos + sz]
assert len(uboot_patched) == sz
h2 = sha256(uboot_patched).digest()
img2 = bytearray(img[:SZ])
img2[pos:pos + sz] = uboot_patched
img2[hash_offset:hash_offset + len(h)] = h2
with open('uboot_new.img', 'wb') as f:
f.write(img2)
f.write(img2)
Запуск скрипта:
$ python3 uboot_fix.py
Убедившись, что результат выглядит корректно, я обновил U-boot на устройстве:
$ ./rkflashtool w 0x4000 0x2000 < uboot_new.img
(Если будете повторять, то имейте в виду, что в примерах и скрипте использованы константы, которые вам нужно будет обновить на полученные из GPT/U-Boot с вашего устройства.)
Отключил устройство и подключил обратно в download режиме - ограничение на 0x10000 секторов исчезло.
Как достать .dtb из разделов Андроида
Можно найти разные утилиты чтобы разобрать boot Андроида по кусочкам, но эти .dtb не сжаты и находятся поиском заголовка формата:
$ hexdump -C boot.img | grep "d0 0d fe ed"
021c0c00 d0 0d fe ed 00 01 84 e8 00 00 00 38 00 01 5a ec |...........8..Z.|
027d3800 d0 0d fe ed 00 01 84 e8 00 00 00 38 00 01 5a ec |...........8..Z.|
Значение следующее за 4 байтами заголовка - это размер файла, 0x184e8 в данном случае. Тут нашлось два одинаковых экземпляра. Достать первый можно, например, так:
$ tail -c +$((1+0x21c0c00)) boot.img | head -c $((0x184e8)) > rk3528.dtb
Альтернативный способ дампа разделов
Можно сделать дамп разделов через Android, но для этого нужны права root.
Решил подключить adb - покликал на "Android TV OS build" в About пока мне не включили меню "Developer options", в котором меня заинтересовали следующие опции:
"USB settings" с загадочным описанием "Disconnect to Computer"
"USB debugging" с описанием "Debug mode when USB is connected"
"Internet Adb" с описанием "enabled internet adb"
Казалось бы, в Android должна быть стандартная локализация, но похоже они перевели с китайского на английский.
"USB debugging" включил, "USB settings" тоже включил, описание изменилось на "Connect to Computer". Подключил через AM-AM кабель - adb устройство не видит. Причём "USB settings" сбрасывается на выключенно при выходе из "Developer options".
Тогда взял USB-Ethernet адаптер и подключил TV-бокс через него к локальной сети. По сети adb тоже не видит устройство. Догадался что опция "Internet Adb" значит отладку по сети и adb наконец подключился.
Тут оказалось интересное, что билд Android не релизный, поэтому в нём работает adb root
, чем я и воспользовался.
$ adb connect 192.168.XXX.XXX
$ adb root
$ adb shell
Запросил в shell список разделов (размеры в килобайтах):
rk3528_box:/ # cat /proc/partitions | grep mmcblk
179 0 61071360 mmcblk2
179 1 4096 mmcblk2p1
179 2 4096 mmcblk2p2
179 3 4096 mmcblk2p3
179 4 4096 mmcblk2p4
179 5 4096 mmcblk2p5
179 6 1024 mmcblk2p6
179 7 51200 mmcblk2p7
179 8 98304 mmcblk2p8
179 9 393216 mmcblk2p9
179 10 393216 mmcblk2p10
179 11 16384 mmcblk2p11
179 12 1024 mmcblk2p12
179 13 4194304 mmcblk2p13
179 14 55898080 mmcblk2p14
Имён нет, но уже зная таблицу GPT - можно предположить что mmcblk2p7 и mmcblk2p8 - это разделы boot и recovery (так и оказалось).
Выходим из shell и в наглую копируем разделы через adb pull:
$ adb pull /dev/block/mmcblk2p7 .
$ adb pull /dev/block/mmcblk2p8 .
А можно и дампнуть и весь EMMC целиком через mmcblk2.
Странно, что при перезапусках Андроид у меня флуктуации названий, может быть mmcblk1 или mmcblk2, а должен быть всегда mmcblk0 по хорошему.
Продолжение следует?
Так как я хочу запустить Линукс не удаляя Андроид, то сделать это можно только через USB. А тут, в U-Boot использованном на устройстве, выяснилась еще одна проблема, он собран без дефайна CONFIG_ROCKCHIP_USB_BOOT, что вызывает данную функцию в board_late_init():
#ifdef CONFIG_ROCKCHIP_USB_BOOT
boot_from_udisk();
#endif
Без этого вызова флэшки подключенные к USB - не рассматриваются как загрузочные, а разъёма для карт памяти не предусмотрено... Поэтому брать подобные устройства без MicroSD слота не рекомендую. Любая ошибка - и устройство превратится в кирпич, говорят Rockchip нельзя окирпичить - но такой можно. Если что-то получится, то напишу следующую статью, не получится - не напишу.
Еще один недостаток данного устройства - невозможно вставить толстые флэшки без удлинителя/разветвителя, мешает HDMI кабель подключенный рядом.