habrahabr

Quake 2 на нашем RISC-V, или как мы поднимали старый Radeon на FPGA

  • четверг, 24 октября 2024 г. в 00:00:15
https://habr.com/ru/companies/yadro/articles/851068/

Всем привет! Меня зовут Александр Разинков, я разрабатываю системный софт в компании YADRO. В этом посте я расскажу о стресс-тестировании нашего RISC-V-кластера с помощью… Quake 2! Почему «квейк»? RISC-V активно развивается как основная application-платформа в мире, и игры — это хороший способ проверить возможности ядер, драйверов GPU и экосистемы в целом. В некоторых важных аспектах игры дают значительно большую нагрузку, чем стандартные программные тесты, особенно на память и интерконнект.

В ходе проекта мы получили отличный опыт работы с видеоподсистемой и графическим стеком Linux, которым стоит поделиться. Открыли для себя новую группу бенчмарков по графике и UX. Наконец, это первый на моей памяти запуск игр именно на российских ядрах RISC-V!

Цикл разработки процессорных ядер включает моделирование его работы через FPGA. Это позволяет провести полноценное тестирование до отправки дизайна «в печь».

Итак, наша исходная конфигурация такова:

  • FPGA-стенд под рабочим названием Soren, на котором смоделирован двухъядерный процессор частотой 120 МГц,

  • NVMe SSD, подключенный через PCIe,

  • видеокарта Radeon HD 4350, актуальная 15 лет назад и подключенная через другой PCIe,

  • Ubuntu 22.04,

  • монитор, подключенный через видеокарту,

  • веб-камера для удаленного наблюдения за стендом.

Что может пойти не так? Много чего, даже по самым оптимистичным оценкам. Видеоадаптер, поддержка которого остановилась на Linux 3.4, может не подружиться с актуальным Linux 6.6. А архитектуры RISC-V во времена этого Radeon вообще не существовало.

Незадолго до выпуска нашего адаптера AMD купила ATI, и с этого переходного периода документации по картам в принципе осталось немного. Так что поиск приводит нас в основном к слезным тредам тех, у кого адаптер отваливался после обычного обновления ядра.

Проверить правильность выбранного драйвера и конфигурации ядра Linux было невозможно. Radeon мы тоже не из коробки достали, до этого он лежал на полке лет 8–10 — за его работоспособность ручаться было нельзя. Вишенкой на торте было то, что у нас в команде не было инженера с опытом работы в видеоподсистеме. В целом наши стартовые условия я бы сравнил с болотом, где отчаянно пытаешься нащупать хоть какую-то кочку, чтобы начать выкарабкиваться и осмысленно дебажить.

Для начала мы просто запустили Ubuntu. Конечно же, получили гору ошибок! Для PCIe не был выделен input/output-регион, ROM-хедеры оказались некорректны, а BIOS не найден… В общем, гарантированный fatal error. Начинаем разбираться.

Что такое input/output-регион для PCI? Это довольно древняя сущность, необходимая для совместимости со старым PCI. Оказалось, что Xilinx PCIe Root Complex наших FPGA уже невозможно сконфигурировать с таким регионом. Возникает мысль отказаться от всей затеи: стоит ли вообще заморачиваться с таким старым адаптером? Купим карту поновее и будет проще. Или хотя бы проверим эту с привычным x86.

Нет, мы продолжили идти по своему пути и были вознаграждены. Поиск подсказал, что I/O-пространство даже для PCIe v1.0 является легаси-фичей для совместимости с более древними PCI-устройствами. Так что ошибка эта не фатальна, она возникает и при успешном запуске Radeon.

Квест 1.2. Битва за PCIe ROM

Изучаем дальше.

Здесь имеется в виду BAR6 — видимо, какая-то флешка на самой карте. Она видна в PCIe, но драйверу что-то в ней не нравится.

Посмотрим lspci Linux:

Видим, что регион выключен! Значит, и декодер адресов для Expansion ROM (BAR6), этой флешки, на карте выключен, то есть обращения по этим адресам недоступны. Чтобы включить декодер, надо записать 0 в регистре ROM BAR Address Register:

С помощью OpenOCD можно напрямую почитать содержимое конфигурационного пространства PCIe. Ниже отмечено, где не стоит нужный бит:

Запишем его через OpenOCD и посмотрим, что будет:

Регион активирован, все видно. Прочитаем теперь эту флешку:

Внутри пусто, одни ноли! Но, судя по упоминаниям BIOS выше, там должен быть некий BIOS.

Квест 2. Radeon ATOM BIOS

Поиски официальной документации успехом не увенчались, и мы погрузились в форумы десятилетней давности. Там реверс-инженеры сливали дампы и пытались по ним разобраться, что же это такое. Благодаря им мы поняли, что перед нами байт-код для архитектуры x86, содержащий настройки для ROM видеокарты. Из него мы можем узнать уровни напряжения, коды инициализации, параметры настройки PLL отдельных блоков, в том числе рабочие байт-код команды. В драйвере есть интерпретатор, который умеет это читать, и драйвер через него взаимодействует с видеокартой. Без интерпретатора вообще ничего не заработает, но найти на карте BIOS с ним мы не можем!

Что ж, когда гора не идет к Магомеду, Магомед идет к горе. Попробуем скачать ATOM BIOS из интернета и подсунуть драйверу. Подложили в начало DDR, немного хакнули сам драйвер. В итоге он прочитал ATOM BIOS, покрутился и выдал Fatal error during GPU init:

Квест 3. 32-bit DMA и 64-bit RISC-V

Разбираемся дальше. Driver(-12) — это ошибка out of memory в Linux. Конкретно здесь идет запрос на память для DMA32. DMA32 у нас нет, у нас 64-битная система. Что ж, давайте дадим, что есть, и посмотрим, что будет!

Запомним запрос именно на 32-битные DMA, это может пригодиться. Погасили таким образом ошибки.

Что дальше? Мы видим часть аллокации памяти. Видеокарта проинициализировалась, и начал загружаться некий микрокод.

Квест 4. Микрокод Radeon

Микрокод содержит куски прошивки для отдельных блоков, которые встраиваются в ядро Linux. Тут уже все понятнее: мы видим и можем нагуглить, что нам нужно, и ядро конфигурируется с этими кусками микрокода. Заканчиваем конфигурировать ядро:

CONFIG_EXTRA_FIRMWARE="radeon/RV710_pfp.bin radeon/RV710_me.bin radeon/R700_rlc.bin radeon/RV710_smc.bin radeon/RV710_uvd.bin"
CONFIG_EXTRA_FIRMWARE_DIR="/lib/firmware"

Что получилось? Новые ошибки, много новых ошибок!

Но при этом у нас завелся DRM! Это подсистема Linux-ядра, которая организует доступ разных системных пользователей Linux к видеокарте.

Видим, что распознались коннекторы VGA и DVI. Карта теперь видна в Linux через dev/dri:

Проведем простой тест: запустим рандомный поток в этот framebuffer:

cat /dev/urandom > /dev/fb0

И-и… Получаем черный экран! Ничего не работает.

У Linux есть все, чтобы показать нам хоть что-то, но он не показывает ничего. После кучи шагов мы зашли в тупик и зацепиться было не за что.

Самая темная ночь — перед рассветом

На этом история могла закончиться, но Бог сжалился над несчастными инженерами, и к нам вдруг снизошло озарение. Просматривая логи и фото стенда в тысячный раз, мы заметили: у нас есть определившиеся DVI- и VGA-коннекторы, а есть фотография в Jira, где монитор подключен… через HDMI! То есть все это время монитор был подключен через порт, который не был распознан Linux. Мы откапываем древний VGA-кабель, подключаем монитор через него и видим волшебный снежок — так выглядит hello world в мире видеокарт:

Ура! Мы выбрались на островок надежды!

Решилась сразу куча фундаментальных вопросов:

  • Radeon можно скрестить с RISC-V FPGA.

  • Radeon может работать с современным ядром Linux 6.6, на которое он в принципе не был рассчитан.

  • Стенд собран корректно, все наши соединения хоть как-то, но работают.

  • Можно не искать десктоп на x86, RaspberryPi и, если совсем повезет, новую видеокарту.

Теперь подумаем, что можно поднять на этом этапе.

Wayland без GPU-акселерации

Wayland — это графическая подсистема Linux уже в user space, альтернатива «иксов». Воспроизвели с ней колесики:

Wayland заработал только напрямую с fb0, в обход драйвера. Производительность — пять кадров в секунду, столько выдает наша FPGA-плата с частотой 100 мегагерц, которая всю графику обрабатывает на себе. Это очень медленно.

Если подключить через DRM, задействуя драйвер, появляются разные краши: в системе явно что-то отвалилось.

Квест 5. DPM initialization failed

Что же не так с акселерацией? Почему драйвер не работает? Смотрим в лог. Здесь у нас множество странных ошибок, какой-то dpm, CAFEDEAD, а в итоге печальное disabling GPU acceleration:

Начнем по порядку. Что такое DPM? Это Dynamic Power Management, разгон карты под нагрузкой и отключение блоков для power save. Он может влиять на что угодно. У людей на форумах он периодически отваливается, в результате чего перестает загружаться даже десктоп. Эту подсистему просто отключают, и загрузке Linux она не мешает.

Мы руководствуемся принципом «если оно не нужно, давайте не будем связываться», так что тоже отключаем этот DPM через командную строку Linux. Сообщение драйвера об этой ошибке исчезло, но акселерация не заработала. Перед нами новая гора ошибок, в них есть строчка disabling GPU acceleration и упоминается какой-то Ring 0:

Квест 6. Битва за Ring 0

Что это за Ring 0? Раскопки форумов, анализ кода и скудная документация на еще более древние поколения Radeon приоткрыли завесу тайны.

RIng 0 — это аппаратное кольцо, через которое CPU общается с GPU, с Radeon. В нашем случае запускается самый базовый тест на DMA. CPU пишет в SCRATCH-регистр Radeon значение 0xCAFEDEAD и шлет через кольцо управляющую команду: «GPU, запиши в SCRATCH 0xDEADBEEF». Если у Radeon есть хотя бы базовая связь с нами, тест должен проходить, но не проходит.

Почему же не работает DMA, почему Radeon не читает Ring 0? Вариантов несколько.

  • DMA Engine выключен. Документации у нас нет, мы не знаем, что там вообще происходит.

  • Драйвер кривой и не предназначен для работы с этим ядром Linux. Но мы верим в лучшее.

  • Radeon не видит Ring 0.

  • Сломана адресация, и Radeon читает не оттуда — нас же постоянно ругают за 64 бита. Как такое проверить…

  • Ring 0 кешируется, и Radeon читает из DDR, а не из кешей. Это единственное, что можно проверить легко.

Память, которую мы выделили под этот Ring 0, оказалась обычной кешируемой памятью ядра, что сразу дало +1 к последней гипотезе. Все, что туда попадает, идет в кеши. В драйвере нет flush/invalidate — специальных инструкций, которые скидывали бы данные из кеша или затягивали обратно. Мы их добавили, но ошибка все равно возникает:

Опять очередной мучительный дебаг… Мы решили просто принудительно сбросить кеш в DDR для всего, что связано с этим Ring, в том числе управляющие структуры, которые лежат вообще в другой памяти. И Ring test 0 прошел!

Какие можно сделать выводы? Железо GPU умное: оно умеет не только в Ring смотреть, но и управляющую конструкцию распарсить и все это обновлять. Ring — кольцевой буфер с read pointer и write pointer, и они модифицируются в процессе передачи данных. DMA read работает, и 64-битная адресация этому не помеха. Cache flush в драйвере у нас нет, а это значит, что все предполагает работу через когерентный DMA, когда GPU мониторит кеши CPU, пишет и читает напрямую из них, а не из DDR. В этом режиме мы работать не умеем. Собственно, одной из целей эксперимента и было определить, к чему наше железо не готово.

Но у команды YADRO есть в рукаве козырной туз: попросить помощи у FPGA-инженеров. «Держите когерентный DMA порт!» — ответили нам инженеры. Спасибо! Теперь можно убрать cache flush/invalidate и увидеть надпись с картинки выше уже без костылей. Дальше в логе всплывает строчка уже про какой-то Ring 3 и снова про CAFEDEAD:

Квест 7. Битва за Ring 3

Начинаем сначала. Что это за Ring 3? Увы, поиск по форумам и другие привычные методы ни к чему не привели. Судя по коду, это похожий на Ring 0 кольцевой буфер с приставкой DMA, но с обратным тестом. CPU пишет в свою память 0xCAFEDEAD и просит: «Radeon, запиши нам в память 0xDEADBEEF». Но этого не происходит.

Что нам с этим делать? Может, дело в адресации. Может, DMA работает на чтение, а на запись нет. Океан возможностей. Но посреди него у нас есть островок стабильности — Ring 0, с которым мы уже прошли тест.

А что, если попробовать записать в память из Ring 0? Так мы проверим возможность DMA на запись со стороны Radeon. Раз нет документации, прибегнем к реверс-инжинирингу. Находим в .h-файле Linux похожую по смыслу команду:

Ищем, как эта команда используется в коде, адаптируем под свои нужды. Попробуем написать 0xCOFFEE42 в начало DDR, которое у нас находится по адресу 0x40000000:

Не сработало. Что не так? В этот момент понимаешь, что софтверных возможностей, к которым привыкли обычные разработчики, уже не хватает. Мы тычемся как слепые котята. Значит, пришло время расчехлять тяжелую артиллерию — Vivado! Для разработчика ПО это совершенно новый и дивный мир. В нем можно увидеть состояние внутренних шин CPU-кластера, которое никак не узнать софтовыми средствами со стороны процессора.

С Vivado мы видим, как Radeon мониторит память процессора, где размещен Ring 0. В сторону памяти процессора идет какая-то запись по одному и тому же странному адресу, который ни с чем не ассоциируется. Наш 0хCOFFEE42 от Ring 0 не видно, а память, где размещен Ring 3, видеокарта даже не мониторит: по нему нет абсолютно никакой активности.

Посмотрим, что это за странные записи по странному адресу. Похоже на обновления Read Pointer кольцевого буфера, но при этом адрес никак с ним не ассоциируется. Начинаем рыть код, и в какой-то момент нас осеняет: Radeon использует трансляцию адресов! Он работает не с физическими адресами, а со своими внутренними. У него внутри есть таблицы трансляции из своих виртуальных адресов в наши физические. И мы в начало DDR как раз записали такой физический адрес. Исправляем на внутренний адрес Radeon:

Ура, наш COFFEE42 появился в Vivado!

Но что же не так с Ring 3? Почему вообще нет реакции на команды и даже мониторингов никаких нет? 

Возможно, дело в первом попавшемся ATOM BIOS? Нет, не в нем: мы перебрали 20 других, и эффекта это не дало. Возможно, дело в каком-то нештатном состоянии самого Ring 3? Смотрим в регистр Ring 3, видим в коде статус 0x44483146. Что бы ни значила эта последовательность, документации для ее расшифровки у нас нет.

В коде мы нашли говорящий статус DMA_IDLE — за это состояние отвечает нулевой бит.

Статус выше заканчивается на 6, значит, бит IDLE не проставлен, и мы не в IDLE.

Что делать дальше? Давайте все перезагрузим, вдруг повезет. А как это сделать правильно без документации? Опять роемся в коде драйвера, находим надпись:

Согласно ей, если аппаратная акселерация не работает на AGP, можно откатиться на PCI. Далее идет соответствующая инструкция. Что ж, раз у них это когда-то работало, попробуем и мы.

Ура, бит поднялся! Мы перезагрузили Ring 3 и наконец увидели долгожданное прохождение теста:

Получается, у нас был отломан целый блок. И без дополнительной перезагрузки он не приходил в штатное состояние. Конечно, в логе осталась еще куча ошибок:

Но при этом грустное сообщение disabling GPU acceleration пропало и появился HDMI:

Wayland заработал через драйвер, с DRM-бэкендом и без крашей. Это огромный шаг вперед! А что у нас с акселерацией?

Увы, пока акселерация не работает, видим те же пять фреймов в секунду. Ну, хотя бы драйвер ее уже не отключает.

Стали доступны внутренние бенчмарки драйвера:

Скорость передачи данных между Radeon и процессором очень низкая — 16 Мбит/с. Но DMA как-то работает, и это дает надежду!

Квест 8. Битва за Ring 5?

Почему не работает акселерация? Давайте разбираться.

Что это за гора ошибок UVD not responding? Находим, что UVD — это Unified Video Decoder. Он отвечает за аппаратное декодирование таких форматов, как MPEG-2, H.264, AVC.

Идем проторенной дорожкой и смотрим статусы модулей. В статусе модуля постоянно висит ноль, перезагрузки ему никак не помогают. В коде удалось лишь найти надпись, что статус имеет какой-то значимый бит, первый бит:

Что он означает, не очень понятно, но вроде бы нерабочее аппаратное декодирование не должно быть блокирующей проблемой. Оставим всю портянку ошибок как есть.

Квест 9. Битва за прерывания

Почему же не работает акселерация? Может, дело в том, что нас постоянно ругают за 64-битную архитектуру, тогда как MSI ограничена 32 битами:

Что нам говорит Linux по поводу прерываний? «Прерываний нет у вас, ребят, они не ходят».

Почему не ходят прерывания? Анализ кода открывает вот такой комментарий:

Однажды какой-то разработчик под IBM Power обнаружил, что на их серверах эта карта не работает, поскольку нормально не работают 64-битные прерывания. Так что он откатил 64-битные прерывания на 32-битные, и это помогло. Но для нас это шоу-стоппер, потому что мы 64-битная платформа. Неужели все?

Если приглядеться, то наш DDR по 0x400000000 — это 35-битный адрес. В комментарии к фиксу написано, что поддерживаются только 40-битные адреса прерывания, так что мы выпилим этот фикс.

Прерывания сразу полетели! Оказалось, что Radeon-таки умеет в 35-битные прерывания. Акселерация наконец заработала!

Вот сравнение без акселерации и с ней:

В некоторых случаях разогнались примерно в тысячу раз! В Wayland мы уперлись в фиксированную частоту обновления монитора — это чисто конфигурационный параметр. А бенчмарки сразу показывают огромный прирост.

Quake 2 на российском RISC-V

У нас распределенная команда, мы работаем в основном удаленно: из Питера, Москвы, Нижнего Новгорода и дальше на восток. Мы редко съезжаемся вместе, но как раз на следующий день после описанных событий к 10 утра все по плану должны были приехать в петербургский офис YADRO. В ночи на кухне я задумался: успею ли что-нибудь показать, чтобы порадовать коллег?

Ubuntu на стенде серверная, никакой графики нет. Поставил «иксы». Ребята советуют: давай что-нибудь типа Doom, Quake 2. Попробуем! У нас FPGA, 100 МГц, здоровенный Quake 2 собирается нативно очень долго. На ночь поставил на сборку, а утром уже надо было ехать. В электричке, автобусе дебажил на ходу и в какой-то момент увидел вот это:

Я был в восторге: получилось 7–17 FPS в зависимости от числа врагов. Это здорово, учитывая, что Quake 2 выпущен в 1997 году без поддержки многопоточности, а мы имеем дело с FPGA — довольно медленной технологией в принципе.

Финальные титры

Этот проект — общая победа нашей команды, работающей над кластером на RISC-V. Мы живем в удивительное время, когда ты можешь ехать в автобусе и с напарником из Сибири удаленно дебажить будущий процессор где-то в лаборатории в Москве. Десять лет назад это и представить было нельзя.

Когда результат можно пощупать руками, причем таким веселым образом, это тоже вдохновляет. Вот она, прелесть инженерной профессии! 

В инженерном эпосе также снимались:

  • FPGA,

  • двухъядерный RISC-V-кластер с частотой около 120 МГц,

  • два PCIe c когерентным DMA,

  • Radeon HD 4350 (2008 г. в.) c GPU-ускорением,

  • Linux kernel v6.6 и драйвер Radeon.

Историю проекта Александр также рассказал на конференции FPGA-Systems 2024.1. С другими докладами конференции вы можете ознакомиться в нашем посте. А 26 октября в Санкт-Петербурге пройдет уже FPGA-Systems 2024.2 — регистрируйтесь на онлайн-участие!