Всем привет!
Меня зовут Алферов Валентин, я руководитель отдела маркетинга компании Е-Флопс. В этой статье хочу поделиться с вами опытом наших программистов, которые рассказали мне эту историю в красках и продемонстрировали результаты.
Дано:
Серверная плата с самым свежим чипом от Aspeed. На плате распаяны два видео-разъема, идущих напрямую с чипа: VGA и DisplayPort. Видео через древний аналоговый VGA-разъём воспроизводится замечательно, а вот современный быстрый и цифровой DP работать не хочет. Не то чтобы совсем не хочет... так, редкие нестабильные промаргивания картинки.
Задача:
1. Починить выводимое на Display Port изображение.
2. Вчера
Вы спросите, зачем нам понадобился именно DP? Все сервера испокон веков работали через VGA, и никто от этого не страдал.
Ответ простой: Мы просто решили сделать лучше и удобнее. VGA древний и его уже ставят по причине "всегда ставили, куда без него"? Мы же делаем НОВУЮ серверную платформу на годы вперед. Нужно сказать, что мы пока не отказались полностью от VGA и технически он всё ещё остается доступен одновременно с DP (как VGA использовать, расскажем в другой раз). Но монитор без разъема VGA попадается всё чаше, а в будущем все мониторы останутся без VGA. DP поддерживает hot-plug. DP поддерживает высокие разрешения и frame-rate. И в конце концов, DP поддерживается чипом ASPEED. А раз железо позволяет, то "А почему бы и ДА!?". В конце концов, для олдов остаются доступны переходники DP-VGA. Главное, не увлекаться и не получить такое:
Собралось в уютной комнатенке программистов трое умов: Михалыч - прожжённый опытом системщик, от одного только строгого взгляда которого транзисторные ключи в микросхемах готовы переключаться в нужные уровни.
Викторыч - Викторович не по возрасту, но раз уж задалась тенденция, отставать негоже - молодой, энергичный инженер с бэкграундом в разных областях, способный одним своим энтузиазмом запитать лампочку. Кузьмич - наш духовный лидер и наставник, толкающий своим пузом авторитетом груз нашего стартапа прямо с обрыва к светлому будущему. "Нельзя отказывать Кузьмичу!" (с).
Что мы имели, кроме перечисленного выше: ✔ С большим трудом найденный на просторах интернета и не самый свежий Datasheet на AST2600 с описанием большинства регистров, но без особых подробностей (Errata не было, но были отсылки к нему). ✔ Сравнительно легко найденный Graphics User Guide, с одной стороны, добавляющий к DP некоторые ручки, за которые можно покрутить, с другой стороны, не вполне объясняющий их предназначение (вроде как: если у вас не работает DP, сдампите вот такие-то адреса памяти и обратитесь к нам в техподдержку) Техподдержку, которая по понятным причинам сейчас не захочет с вами общаться. ✔ Представителя "железных дел мастеров", транслирующего на всю лабораторию, что все программисты пианисты пропагандируют ЛГБТ-идеологию и что DP-порт не работает только из-за "кривых" настроек нашего софта. Звучало как вызов, и мы его приняли.
Поехали!
Проанализировав доки и отдельный бинарь для DP Михалыч предположил, что вся обработка происходит в отдельном ядре, с архитектурой, отличной от ARM и с доступом к общей шине. Упомянутые в Datasheet 32 регистра, маппирующиеся на общие адреса, намекали, что это регистры именно этого стороннего ядра.
— Викторыч, попробуй-ка широко распространенным дизассемблером пооткрывать этот бинарь под разными архитектурами, мож поймет какую? — Поставил задачу Кузьмич.
Викторыч затих на полчаса, потом послышался ответ:
— Нет, похоже, не исполняемый это файл. Перебрал с десяток распространенных архитектур, получается белиберда. Да и судя по HEX-редактору, значения на больших промежутках файла очень часто повторяются и схожи по значениям. Скорее всего конфиг какой-то.
— Да бинарь это! Точно говорю! — не унимался Михалыч. — Готов разобрать его на куски и написать свой дизассемблер. Нужно только время и отмашка от начальства.
Через два дня других безуспешных экспериментов Кузьмич дал отмашку Михалычу на исследовательскую работу. И буквально через пару дней Михалыч выдает первые промежуточные результаты. Он распарсил порядка 30% процентов байткода и тот реально начал оформляться в листинг исполняемого, в котором примерно 70% команд - инициализация регистров и областей (именно использование практически одних и тех же команд заставило Викторыча ошибочно предположить, что это конфиг-файл).
«Михалыч, ты как разобрал-то этот ребус? За что зацепился?» — спросил между делом Викторыч.
В Aspeed есть встроенный аппаратный debugger именно для DP. Команды ему можно давать прямо из uboot. Есть пошаговый режим, точка останова и возможность смотреть содержимое регистров. Пошаговый режим как-то не очень адекватно работал, перескакивал команды, неправильные значения показывал. А вот точка останова стабильно работала! С её помощью и раздербанил код.
Викторыч угукнул, прыгнул за комп и начал усиленно что-то щелкать мышкой. Вскоре послышался радостный крик:
— Ага!!! Михалыч, бросай свой дизассемблер, я, кажется, нашел.
Все собрались вокруг рабочего места Викторыча, но радость была преждевременной. Хоть инструкции и были очень похожи на WDC 65816, но увы, полного попадания не было.
Продолжили собирать информацию. Викторыч анализировал промежуточный ассемблерный листинг от Михалыча, а тот продолжил свои исследования по остающимся непонятным командам.
Приехали, или что выяснил Михалыч
А выяснил он что: ✔ машинные команды имеют одинаковый размер – 4 байта; ✔ при выполнении команд в пошаговом режиме видны изменения в регистрах; ✔ всего регистров общего назначения 31 (r1-r31), регистры 32-х битные, хотя сам контроллер 16-битный; ✔ есть псевдорегистр r0, всегда равный нулю, r31 - link-регистр, r29 - возможно stack pointer, т.к. инициализируется на конец памяти и больше нигде явно не используется; ✔ program counter (PC) существует отдельно; ✔ в системе используются прерывания; ✔ адресное пространство управляющей программы (УП) DP контроллера маппится на 0x18020000 основного ядра и имеет размер 16 кбайт; ✔ с точки зрения DP контроллера УП живет в диапазоне адресов [0-0x4000]; ✔ регистровое пространство DP контроллера маппится на 0x18010000 адреса и имеет размер 4 КБайта; ✔ адресное пространство данных УП маппится на 0x18000000 адреса и имеет размер 4 КБайта; ✔ в процессе исполнения УП r20 всегда равен 0x1e6eb000 (как константа) - это базовый адрес регистров управления DP; ✔ в процессе исполнения УП r9 всегда равен 0x18000000 - базовый адрес для маппинга DPCD и RAM для УП.
Дальше логика Михалыча была такой.
Если посмотреть на содержимое дампа бинаря DP, начало там такое:
0xa0000080 0xa0000080 0xa0000080 0xa00035dc
0xa000363c 0xa000369c 0xa0000080 0xa0000080
0xa0000080 0xa0000080 0xa0000080 0xa0000080
0xa0000080 0xa0000080 0xa0000080 0xa0000080
0xa0000080 0xa0000080 0xa0000080 0xa0000080
0xa0000080 0xa0000080 0xa0000080 0xa0000080
0xa0000080 0xa0000080 0xa0000080 0xa0000080
0xa0000080 0xa0000080 0xa0000080 0xa0000080
0x2a801e6e 0x5694b000 0x29201800 0x55290000
0xdc090afc 0x54c00104 0x54e00000 0x05093800
0xdc080000 0x0ce70004 0x90e6fff4 0xdc090200
0xdc090204 0xdc090600 0x54c0beef 0xdcc90604
0x54c009fc 0x54e00800 0x05093800 0xdc080000
0x0ce70004 0x90e6fff4 0x54c00d30 0x54e00d00
Видно, что первые 32 четырёхбайтных слова отличаются от последующих. Логично предположить, что в начале находится таблица векторов прерываний, а далее обычный код.
Отсюда следует, что команда 0xa0000080 похожа на безусловный переход на адрес 0x80 – так как по этому адресу как раз и начинается "обычный" код.
Первые две инструкции после таблицы прерываний имеют значения 2a801e6e и 0x5694b000. Можно обратить внимание на младшие 16-бит этих инструкций – вместе они составляют 0x1e6eb000 – а это адрес регистров DP контроллера!!!
Предположим, что младшие 16-бит отведены под данные, а старшие на указание типа операции и номера регистров, к которым ее надо применить. Так как регистров 32, то под хранение номера регистра должно выделяться 5 бит.
Вспомним, что r20 всегда равен 0x1e6eb000 и поищем число 20 (двоичное 10100) в старших битах.
Рассмотрим старшие биты в двоичном коде:
0x2a80 = 0010 1010 1000 0000 = 001010 10100 00000 (op rD rS) = 0x0a 20 0
0x5694 = 0101 0110 1001 0100 = 010101 10100 10100 (op rD rS) = 0x15 20 20
op – это код операции (6 бит)
rD – целевой регистр (5 бит)
rS – исходный регистр (5 бит)
Получается, что инструкция 10(0x0a) это or или add для старших 16-бит
or.h r20, r0, 0x1e6e # r20=0x1e6e0000
Инструкция 21(0x15) это or или add для младших 16-бит
or.l r20, r20, 0xb000 # r20=0x1e6eb000
Инструкция 40(0x28) - прыжки в таблице прерываний 0xa0000080 - это jump на указанный адрес
jump 0x80
Проверять эти предположения нетрудно, если написать прямо в кодах небольшие тесты и добавлять их в начало обычного кода (после таблицы векторов), но нужно не забывать зацикливать тесты, благо команда для безусловных прыжков у нас есть!!!
Дальнейшее разбирательство показало, что система команд простая: • код операции всегда 6 бит; • есть возможность выполнения подпрограмм - r31 используется как link register, то есть туда автоматически записывается адрес возврата; • есть стек и поддерживаются команды push и pop; • есть команда ret и iret – выход из подпрограммы и из прерывания; • регистра флагов, скорее всего, нет, потому что нет прямой ссылки на него в кодах команд, анализ результата производится непосредственно в условных командах.
Список команд, которые в итоге удалось декодировать Михалычу (коды десятичные, мнемоника - чтобы было себе понятно):
1 add.r regD, regS1, regS2
2 add.c regD, regS, data
3 add.u regD, regS, data
6 sub.i regD, regS, data
7 sub.u regD, regS, data
10 or.h regD, regS, data
11 nop data
12 push rS
13 push rD
16 and.r regD, regS1, regS2
17 and.u regD, regS, data
18 orn.r regD, regS1, regS2
20 or.r regD, regS1, regS2
21 or.l regD, regS, data
26 sl.d regD, regS, data
30 sr.d regD, regS, data
31 sr.r regD, regS1, regS2
32 je regD, regS, rel_offs
33 jz regD, regS, rel_offs
34 jme regD, regS, rel_offs
35 jm regD, regS, rel_offs
36 jle regD, regS, rel_offs
37 jl regD, regS, rel_offs
38 jne regD, regS, rel_offs
39 jnz regD, regS, rel_offs
40 jump addr
41 call addr
42 ret rS
44 iret
49 ldr.1 regD=[regS+offs] // соответственно, загрузка 1 байта
51 ldr.2 regD=[regS+offs] // загрузка 2 байт
52 ldr.4 regD=[regS+offs] // загрузка 4 байт
53 str.1 [regS+offs]=regD
54 str.2 [regS+offs]=regD
55 str.4 [regS+offs]=regD
Снова поехали, только теперь быстро!
Дальше в течение трёх дней периодически только раздавались крики со стороны Викторыча:
— О, я нашел функцию, меняющую регистры по маскам. Она используется почти везде.
— Ага, все три обработчика прерываний обслуживают разные вариации срабатывания сигнала HPD (Hot-Plug Detect).
— Опаньки, а вот и обмен по AUX.... А вот и второй.
— А - вот здесь Link-training происходит. Полностью разбирать не буду, там специфично, но смысл понятен.
— Смотрите, нашел настройку количества линий и скорости сигнала (после чего Викторыч дал патч для понижения скорости обмена с 2,7 до до 1,6 Gbps, а Михалыч сварил прошивку). После старта DP осциллографом реально увидели сигнал 1,6 Gbps и монитор стал чаще помаргивать картинкой.
В итоге спустя несколько дней практически весь дамп FW был переведен в удобоваримый код. Наконец-то стал максимально понятен смысл записываемых в пространство конфигурации ключевых значений 0xdead, 0xcafe, 0xaced. Стала понятна логика работы счетчиков в DPCD-регионе. И много ещё чего интересного. Файлы прилагаем, любуйтесь сами.
Кровавые итоги
DisplayPort мы все-таки победили. Проблема оказалась не в программистах.
Про систему команд ASPEED DP MCU. Мы предполагаем, что у этого ASM ноги растут откуда-то из Motorola M68. Там тоже были 32-bit регистры и 16-bit шина данных. Но наши знания не безграничны, поэтому добро пожаловать в комменты. А мы уже занялись другими задачами.
ВСЁ! Ждите новых историй.
Автор: AV_EFLOPS