[Перевод] Избавляемся от хлама: как превратить бесполезную тв-приставку в компьютер под Linux
Провайдер мобильного интернета обычно выдаёт каждому клиенту модем 4G и приставку для просмотра ТВ или потоковых сервисов. В компанию Zeus тоже прислали это устройство, но пользы с него никакой — телевизор здесь не смотрят. Зато очень нужны компьютеры с низким энергопотреблением, которые могут работать под Linux. Так появилась идея взломать эту телевизионную приставку, чтобы вместо Android TV запустить на ней Linux.
Пускай обычно провайдеры списывают эти приставки сразу же, как только выдают их клиентам, теоретически они всё же могут попросить вернуть оборудование. Поэтому мы решили, что после наших манипуляций приставка должна легко возвращаться к работе с исходным программным обеспечением. Это означает, что мы не можем вносить деструктивные изменения в конструкцию или перезаписывать важные части памяти устройства. Кроме того, должно работать самое важное оборудование приставки: Ethernet и HDMI.
Наша телевизионная приставка имеет порт Ethernet, порт USB, разъемы питания и HDMI. Также в ней есть ИК‑датчик для пульта ДУ и несколько светодиодов, показывающих состояние устройства. На этикетке приставки мы нашли адрес сайта производителя. Тут всё понятно, провайдер работает по схеме «White Label»: покупает приставки у поставщика, маркирует своим брендом, добавляет собственные приложения, а затем передаёт эти устройства клиентам. Проведя небольшое исследование, мы выяснили, что никто ещё не пытался поставить на эти приставки другую операционную систему: это предстояло сделать нам.
Вскрыв приставку, мы осмотрели печатную плату (PCB) с маркировкой STI6160-D323-ROHS. К сожалению, большая часть интересных чипов находится под алюминиевой RF защитой, поэтому определить, какие именно чипы там находятся, сложновато. Один видимый чип помечен как KLM8G1GETF. Судя по всему, это чип памяти eMMC производства Samsung. Хорошо бы получить данные, хранящиеся на нем, чтобы понимать, как действовать дальше. Мы решили, что сделать это можно двумя способами:
Выпаиваем чип eMMC, припаиваем его к другой плате и считываем оттуда данные. У меня есть некоторый опыт в этом деле, так что идея казалась рабочей. Однако, поскольку чип eMMC сидит на плате BGA (массив из шариков: в нижней части чипа расположены очень маленькие шарики припоя, которые соединяют чип с печатной платой), выпаять его очень сложно, и высока вероятность испортить микросхему.
Припаиваем провода к самой плате, подключаясь к дорожкам, которые использует чип. Нужно было припаять всего 5 проводов, но сложно было определить, куда именно их припаивать. Для этого используется техника наложения изображения цоколевки микросхемы на фото печатной платы. К сожалению, даже таким методом нам не удалось найти правильные трассировки печатной платы.
Оба подхода оказались сложными, так что дамп флэш‑памяти на данный момент показался нецелесообразным. К счастью, мы также обнаружили подозрительный незанятый 4-контактный разъем, который потенциально мог являться отладочным портом.
Части открытого устройства
С помощью мультиметра в режиме прозвонки мы определили, какой пин отладочного порта заземлён.
Визуальный осмотр показал, что у порта есть контакт заземления, контакт питания (VCC) и два контакта ввода‑вывода, подключённые к чему‑то, похожему на главный процессор, подтянутые резистором высокого сопротивления, подключёнными к VCC.
Цветные PCB дорожки, ведущие к загадочному разъёму
Затем мы припаяли провода (это смотрится не очень красиво, но работает) к порту и использовали дешёвый логический анализатор, чтобы увидеть, что происходит на контактах ввода‑вывода. Вот что происходит после загрузки устройства:
Логический анализатор, подключенный к платеСкриншот с логического анализатора
На скриншоте видно, что есть один контакт, на котором постоянно высокий уровень сигнала (из‑за резистора между ним и VCC), и один контакт, на котором есть сигналы. Эти сигналы выглядят последовательными, поэтому мы использовали декодер последовательного протокола для декодирования сигнала. Оказалось, что это действительно последовательные сигналы со скоростью передачи 115 200 бод. Из этого следует, что другой контакт, вероятно, является контактом RX (принимающим): он постоянно находится на высоком уровне, потому что мы ещё ничего не отправили.
Мы отсоединили логический анализатор от устройства, а затем подключили USB‑адаптер к последовательному порту, позаботившись о подключении TX адаптера к RX на плате и наоборот. Мы также обеспечили правильное напряжение, установив переключатель адаптера на 3,3 В (не хотелось бы случайно взорвать порт отладки).
Последовательный адаптер, подключенный к плате
После запуска устройства на последовательную консоль вывелось большое количество текста. Вот небольшая часть:
G12A:BL:0253b8:61aa2d;FEAT:F2F839B2:32060;POC:F;RCY:0;EMMC:0;READ:0;5.0;5.0;
...
BL2 Built : 06:41:45, Feb 19 2020. g12a g9a5414b - jenkins@walle02-sh
...
LPDDR4_PHY_V_0_1_21-Built : 20:05:08, Jan 10 2020. g12a g3576a48 - zhiguang.ouyang@droid07-sz
...
[Image: g12a_v1.1.3482-c90792be1 2020-06-12 19:52:03 wencai.you@droid11-sz]
...
U-Boot 2015.01 (Sep 09 2021 - 15:53:17)
...
Filesystem: FAT12 "KEYBOX PART"
gpio: pin GPIOAO_3 (gpio 3) value is 1
Command: bcb uboot-command
Start read misc partition datas!
BCB hasn't any datas,exit!
s_version: U-Boot 2015.01
amlkey_init() enter!
amlkey_init() 71: already init!
[EFUSE_MSG]keynum is 4
[KM]Error:f[key_manage_query_size]L515:key[oemkey] not programed yet
Interface: MMC
Device 1: Vendor: Man 000015 Snr 4baa48a1 Rev: 0.6 Prod: 8GTF4R
Type: Removable Hard Disk
Capacity: 7456.0 MB = 7.2 GB (15269888 x 512)
Filesystem: FAT12 "KEYBOX PART"
Hit Enter or space or Ctrl+C key to stop autoboot -- : 0
pll tsensor avg: 0x1dfe, u_efuse: 0x64
temp1: 24
ddr tsensor avg: 0x1e10, u_efuse: 0x50
temp2: 24
device cool done
…
Этот журнал загрузки содержит массу информации: даты сборки, имена пользователей и названия хостов компьютеров разработчиков, но, самое главное, строку "Hit Enter or space or Ctrl+C key to stop autoboot -- : 0” (Нажмите Enter, или Пробел, или Ctrl+C для остановки автозагрузки -- : 0). Если спамить Enter во время загрузки устройства, это действительно останавливает автозагрузку и сбрасывает нас в своего рода оболочку:
Filesystem: FAT12 "KEYBOX PART"
Hit Enter or space or Ctrl+C key to stop autoboot -- : 0
g12a_u212_v1#
g12a_u212_v1#
g12a_u212_v1#
g12a_u212_v1#help
? - alias for 'help'
aml_sysrecovery- Burning with amlogic format package from partition sysrecovery
amlmmc - AMLMMC sub system
amlnf - aml mtd nand sub-system
autoscr - run script from memory
…
Теперь понятно, что мы имеем дело с uBoot, популярным загрузчиком с открытым исходным кодом. Производители аппаратного обеспечения почти никогда не используют uBoot без модификаций, так что имеет смысл проверить версию загрузчика и другую информацию:
g12a_u212_v1#version
U-Boot 2015.01 (Sep 09 2021 - 15:53:17)
aarch64-none-elf-gcc (crosstool-NG linaro-1.13.1-4.8-2013.11 - Linaro GCC 2013.10) 4.8.3 20131111 (prerelease)
GNU ld (crosstool-NG linaro-1.13.1-4.8-2013.11 - Linaro GCC 2013.10) 2.23.2.20130610 Linaro 2013.10-4
Далее нам нужно сделать дамп памяти eMMC. В uBoot есть команда mmc, которая (среди прочего) может делать следующее:
g12a_u212_v1#mmc help
mmc - MMC sub system
Использование:
mmc info - display info of the current MMC device
mmc read addr blk# cnt
mmc write addr blk# cnt
Мы использовали подкоманду mmc read
, которая принимает адрес памяти, куда будут помещаться входные данные, номер блока, с которого они будут начинаться, и количество блоков для чтения. После того, как данные будут отображены в памяти, нам нужно их извлечь. Сначала мы попробовали команду md.b (отображение байтов памяти). Эта команда берет адрес и количество байтов и выводит их на последовательную консоль в виде шестнадцатеричного дампа. Этот процесс можно автоматизировать с помощью скрипта Python для считывания всей микросхемы памяти eMMC ёмкостью 8 ГБ. К сожалению, этот подход оказался слишком медленным: грубые подсчеты показали, что для полного переноса всей памяти потребуется около 49 дней.
К счастью, мы нашли ещё одну команду: fatwrite
. Она запишет память в файл в файловой системе FAT. В приставке также есть порт USB, куда можно подключить карту памяти. Используя комбинацию mmc read и fatwrite, мы начали делать дамп памяти eMMC. Это тоже оказалось небыстро, процесс занял ~ 4 дня. Команда fatwrite
была заменена на usb write
, которая сняла нагрузку с файловой системы и напрямую сбрасывала данные на диск, байт за байтом.
Имея на руках резервную копию eMMC, можно смело переходить к попытке запустить Linux на приставке. Проверка дампа eMMC показала, что плата имеет эталонный дизайн U212 с четырехъядерным процессором Amlogic S905×2 ARM Cortex‑A53 SoC.
Нам удалось найти репозиторий amlogic‑s9xxx‑armbian на GitHub. Эта версия Armbian специально сделана для чипа нашего устройства; нам повезло, что кто‑то уже потрудился и избавил нас от лишней работы. Armbian — это дистрибутив на основе Debian, специально предназначенный для чипов ARM (здесь ARM относится к набору инструкций ЦП; на большинстве ноутбуков этот набор инструкций — x86).
Чтобы заставить всё это работать, снова пришлось пройти тернистый путь. Критически важную информацию мы получили из поста в блоге 7Ji, где описывается, как работает процесс загрузки на устройствах Amlogic. Стало ясно, что мы не сможем напрямую загрузить Linux из загрузчика, поставляемого с устройством. Сначала нужно будет загрузить его через загрузчик, которым мы управляем (это нужно, чтобы избавиться от странной конфигурации/кода поставщика). Это называется цепной загрузкой. Мы использовали один из загрузчиков, поставляемых с версией Armbian для чипа S905×2.
Мы выполняли цепную загрузку, сначала помещая загрузчик в память, а потом перейдя к нему.
g12a_u212_v1#fatload usb 0:1 0x1000000 u-boot-s905x2-s922.bin
740080 bytes read in 104 ms (6.8 MiB/s)
g12a_u212_v1#go 0x1000000
## Starting application at 0x01000000 ...
U-Boot 2015.01-dirty (Aug 14 2020 - 19:56:34)
DRAM: 2 GiB
Relocation Offset is: 76eec000
...
odroidn2#
Это запускает второй загрузчик и переводит нас в другую оболочку (с odroid2 в качестве Shell Prompt, что является ещё одним одноплатным компьютером). Отсюда мы хотим загрузиться в Linux. Для этого нам понадобятся четыре вещи:
Ядро Linux
Очень простой объект файловой системы под названием INITRD (Inital RAM Disk). Он содержит исходные файлы, необходимые для продолжения процесса загрузки.
Аргументы ядра. Тут всё стандартно.
Файл FDT (Flattened Device Tree или плоское дерево устройств). Этот файл описывает оборудование и используется ядром Linux для загрузки драйверов и настройки оборудования.
Первые два предоставлены проектом Armbian, но FDT специфичен для платы и, похоже, ещё недоступен для основной версии Linux. Когда мы сбросили память eMMC, то нашли Device Tree Blob, что характерно для Android. Ядро Linux, используемое версией Android TV в приставке, было форкнуто из основной версии, чтобы включить поддержку аппаратного обеспечения. Эти изменения были внесены в версию Линуса Торвальдса не поставщиком оборудования, а другими разработчиками, что привело к несовместимости DTB Android и DTB основной версии Linux.
Чтобы исправить это, мы использовали быстрый и грязный хак: пробовали разные Device Tree Blob для других похожих плат, пока устройство не загрузилось и не получило необходимую аппаратную поддержку. В итоге подошёл блоб meson‑g12a‑sei510.dtb. Затем загрузка происходила так:
fatload usb 0:1 0x11000000 uEnv.txt
env import -t 0x11000000
fatload usb 0:1 0x10000000 ${FDT}
fatload usb 0:1 0x11000000 ${LINUX}
fatload usb 0:1 0x13000000 ${INITRD}
setenv bootargs ${APPEND}
booti 0x11000000 0x13000000 0x10000000
Со следующим содержанием uEnv.txt:
LINUX=/zImage
INITRD=/uInitrd
FDT=/dtb/amlogic/meson-g12a-sei510.dtb
APPEND=root=UUID=26bc1f8b-a9c1-4f86-91a9-c6c2b529f402 rootflags=data=writeback rw rootfstype=ext4 console=ttyAML0,115200n8 console=tty0 no_console_suspend consoleblank=0 fsck.fix=yes fsck.repair=yes net.ifnames=0 cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory swapaccount=1
Это загрузило нас в работающую оболочку Linux с работающими Ethernet и HDMI! Мы даже смогли установить XFCE, чтобы получить графическую оболочку.
Мы ещё не заставили встроенный чипсет Wi‑Fi работать, но это нормально, так как этот блок, скорее всего, и так будет использоваться с Ethernet.
Напоследок осталось добиться автоматической загрузки Linux при вставке USB‑накопителя: на данный момент загрузка Linux требует, чтобы сначала был загружен вторичный загрузчик, а затем уже выполняется запуск ядра. И то, и другое происходит интерактивно на последовательной консоли, что раздражает, потому что тогда мы не можем снова закрыть устройство.
Для автоматической загрузки во вторичный загрузчик мы изменили переменные среды, записанные в хранилище eMMC; это единственная запись, которую мы сделали в хранилище eMMC. Первоначально переменная окружения bootcmd содержала команду run storeboot, запускающую процесс загрузки Android:
g12a_u212_v1#defenv
g12a_u212_v1#setenv bootcmd 'usb start && if fatload usb 0:1 0x1000000 u-boot-s905x2-s922.bin; then go 0x1000000; else run storeboot; fi'
g12a_u212_v1#saveenv
reboot
Теперь вторичный загрузчик загружается автоматически, если это возможно. Мы попытались использовать файл конфигурации extlinux для автоматической загрузки Linux; extlinux.conf имеет очень простой формат конфигурации, в котором вы указываете ядро, initramfs, аргументы и дерево устройств; затем загрузчик выполняет остальную работу по загрузке устройства.
К сожалению, из‑за ошибки во вторичном загрузчике обработчик конфигурации extlinux сломался, поэтому автоматическая загрузка не работала. Мы также не нашли способ передачи команд от первого загрузчика ко второму. В сообщении блога 7Ji о загрузчике упоминается, что если на USB‑накопителе есть файл с именем boot.scr или aml_autoscript, он будет автоматически выполнен. Оказалось, что это не так: на самом деле есть команда boot_attempt, которая выполняет эти скрипты на любом хранилище, которое может найти, но она не выполняется командой bootcmd.
Нам не хотелось перекомпилировать загрузчик, поэтому мы пропатчили его шестнадцатеричным редактором. Новая команда короче старой, поэтому мы заполнили остальные части пробелами.
Теперь мы достигли всех наших целей: приставка автоматически загружается в Linux с поддержкой Ethernet и HDMI. Исходная установка Android по‑прежнему загружается, если не вставлен специальный USB‑накопитель.
Если остались вопросы, автор готов на них ответить и просит смело к нему обращаться — j (СОБАКА)zeus (ТОЧКА)ugent.be