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

от автора

Заметил в продаже новенький 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Гб оперативной памяти, на плате состоит из восьми чипов, по четыре с каждой стороны. 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 <infile  write flash partition rkflashtool r offset nsectors >outfile read flash rkflashtool w offset nsectors <infile  write flash

Для работы с именами разделов требуется сторонняя утилита 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  |..?...<N..5...n3| 00000790  00 00 41 2e 00 00 16 4b  80 00 3e b4 00 00 60 30  |..A....K..>...`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.

Плохой способ получить .dtb

С правами рута из Android можно скопировать /sys/firmware/fdt, содержимое чего похоже на .dtb. Но этот способ не рекомендуется, потому что это .dtb пришедший из boot раздела, но потом отредактированный в процессе загрузки. Содержит прямые указатели в память и опции загрузки для Android, что запросто сломает вам запуск другой ОС с этим .dtb.

Как собрать (полурабочий) U-Boot

Собрал U-Boot по инструкциям:

$ sudo apt-get install gcc-aarch64-linux-gnu $ git clone --depth=1 https://github.com/rockchip-linux/u-boot.git $ git clone --depth=1 https://github.com/rockchip-linux/rkbin.git $ cd u-boot

Заменяем в начале make.sh кросс-компилятор на системный, из пакета gcc-aarch64-linux-gnu: CROSS_COMPILE_ARM64=/usr/bin/aarch64-linux-gnu-

Можно запускать сборку через конфиг для RK3528:

$ ./make.sh rk3528

Получаем uboot.img, который можно прошить на устройство. В кирпич не превратилось, так как download режим работает (чем можно восстановить старый U-Boot).

Вот только Андроид перестал загружаться, дальше логотипа H96 MAX (выводится самим U-Boot) не идёт. Значит в билде U-Boot что-то подкручивали под конкретное устройство или Андроид. Тем не менее, еще один способ получить U-Boot с чтением без ограничений — перед сборкой пропатчив функцию rkusb_read_sector (при запуске с карты памяти — вы ничем не рискуете).

Продолжение следует?

Так как я хочу запустить Линукс не удаляя Андроид, то сделать это можно только через USB. А тут, в U-Boot использованном на устройстве, выяснилась еще одна проблема, он собран без дефайна CONFIG_ROCKCHIP_USB_BOOT, что вызывает данную функцию в board_late_init():

#ifdef CONFIG_ROCKCHIP_USB_BOOT boot_from_udisk(); #endif

Без этого вызова флэшки подключенные к USB — не рассматриваются как загрузочные, а разъёма для карт памяти не предусмотрено… Поэтому брать подобные устройства без MicroSD слота не рекомендую. Любая ошибка — и устройство превратится в кирпич, говорят Rockchip нельзя окирпичить — но такой можно. Если что-то получится (получилось), то напишу следующую статью, не получится — не напишу.

Еще один недостаток данного устройства — невозможно вставить толстые флэшки без удлинителя/разветвителя, мешает HDMI кабель подключенный рядом.

P.S.: Кажется я нашел кого этот TV-бокс копирует… та-да-да, Xiaomi Mi Box 4! Китайцы начали копировать сами себя.

P.P.S.: Таки подключил UART: круг-TX, круг-GND, квадрат-RX (круг/квадрат — это форма контактов с отверстиями, около ИК-приёмника), baudrate = 1500000.


ссылка на оригинал статьи https://habr.com/ru/articles/766422/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *