Дамп разделов TV-бокса на чипе RK3528

Заметил в продаже новенький TV-бокс H96MAX 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

То есть 0×4000 и 0×5fff, это индексы первого сектора раздела и последнего. Один сектор это 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Мб, это 0×10000 секторов по 512.

Решил что rkflashtool не работает с новым устройством и нашел другой способ дампа, через Андроид (приведён в конце). А потом смог найти на xdaforums пользователей что жалуются на то же самое. Один из них нашел причину и исправил её.

Вкратце — Rockchip в коде U-boot решил ограничить чтение на 0×10000 секторов, далее возвращаются байты 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 режиме — ограничение на 0×10000 секторов исчезло.

Как достать .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 байтами заголовка — это размер файла, 0×184e8 в данном случае. Тут нашлось два одинаковых экземпляра. Достать первый можно, например, так:

$ 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 кабель подключенный рядом.

© Habrahabr.ru