Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Продолжаю перевод отчёта проекта Asahi Linux.
Поиграем с железом
m1n1 пошёл от mini - минимальной оболочки, которую я разработал для Nintendo Wii. mini очень помогла мне при экспериментах и в качестве основы для BootMii (для тех, кто является владельцем Wii и разбирается, что там к чему - mini это то, что на самом деле запущено на ARM CPU пока вы находитесь в BootMii меню).
Ну ладно, а какое отношение это всё имеет к загрузчику Apple Silicon?
Что ж, mini был небольшой и довольно простой программой, умевшей работать напрямую с железом, и запускавшейся прямиком на 32-битной ARM системе без каких-либо библиотек или других зависимостей. Это хорошая база для наращивания функционала, поэтому мы портировали её на AArch64 и Apple Silicon, и порт назвали m1n1.
Что оказалось куда более важно, m1n1 унаследовала от mini киллер-фичу: так как mini запускалась на дополнительном процессоре, которым нужно было управлять с основного процессора, у неё есть встроенный RPC работающий через последовательный порт. Это значит, что мы можем управлять mini и m1n1 с другого компьютера прямо во время работы. m1n1 позволяет с помощью Python скриптов, запускаемых на другой машине, манипулировать железом на целевом m1 маке. Можно даже делать это прямо из интерактивного терминала! Честно говоря, это скорее универсальный инструмент для экспериментов с железом, который по чистой случайности ещё и выступает загрузчиком линукса в нашем случае.
m1n1 даёт нам отличную возможность изучить проприетарные параметры Apple железа. Например, этот скрипт проверяет наличие специальных конфигурационных битов, нужных для ускорения x86 операций с плавающей запятой в эмуляторе Rosetta. А этот скрипт ищет все Apple-specific регистры CPU и печатает их значения и ограничения доступа. Этот скрипт показывает, как такие ограничения могут быть модифицированы проприетарным конфигурационным регистром гипервизора. Ну и, само собой, вот этот скрипт, загрузит для вас Linux прямо через последовательный порт.
Загрузка M1 Mac Mini в m1n1 занимает примерно 7 секунд, и все эти скрипты можно запускать интерактивно, без перезагрузки(пока вы не поймаете BSOD :) ). m1n1 может так же загрузить сам себя, поэтому цикл разработки выходит довольно простым: можно прямо из старой m1n1, запущенной с помощью kmutils, загрузить новую во время выполнения.
В процессе использования m1n1 мы тщательно задокументировали проприетарные Apple команды для ARM, регистры, нестандартные компоненты железа (например, собственный контроллер прерываний), и многое другое.
В будущем мы планируем продолжать добавлять фичи в m1n1, делая его ещё более крутым. Одна из целей - превратить его в такой себе тонкий гипервизор, который может загрузить macOS и перехватывать все обращения к железу. Это позволит нам понять, как работают драйвера Apple, без необходимости их дизассемблировать, что куда менее сомнительно в юридическом плане, да и попросту куда быстрее, чем отлаживать чужой сложный ассемблер код драйверов. Некоторые из вас могут узнать такой подход - так же сделали nouveau, чтобы разреверсить nvidia gpu - однако у них были Linux драйвера и они меняли ядро для перехвата обращения к железу, вместо добавления гипервизора.
Так, стоп - всё это хорошо, но для работы всему этому чуду нужен последовательный порт. Откуда такой порт на M1 mac? Я ждал этого вопроса :)
А вот и UART!
Чтобы отлаживать новую систему в железе, практически безальтернативно нужен последовательный порт. Последовательные порты, иногда называемые UART портами, практически простейший интерфейс для коммуникации, а значит и очень удобный инструмент для низкоуровневой отладки. Послать сообщение по такому порту займёт всего пару инструкций CPU, то есть его можно использовать на самых ранних стадиях запуска, в том числе как терминал для разработки.
Конечно, когда-то у ПК были RS-232 COM порты, но эти времена давно прошли. На многих встроенных системах (на тех же домашних роутерах) низковольтный последовательный порт всё ещё есть внутри начинки, но придётся снять корпус, чтобы туда подключиться, а иногда даже придётся подпаиваться к плате.
Так что там с M1 маками?
Оказывается, что M1 маки тоже оснащены последовательным портом, доступным без снятия корпуса - через один из USB-C портов! Однако, чтобы включить его как последовательный порт, нужно отправить специальный набор команд через USB-PD. USB-PD (Power delivery) это протокол передачи данных по пину канала конфигурации в Type C портах. Как принято в нашем любимом USB стандарте, это огромный монстр, который делает гораздо больше, чем просто поставляет питание - не просто управляет напряжением и определением зарядки, но и определяет тип кабеля, донгла, особые режимы типа DisplayPort, и, в нашем случае, работает как транспорт для проприетарных конфигурационных сообщений. Эти команды просят мак вывести последовательный порт на два пина данного Type C порта. Так же он умеет много другого, включая перезагрузку системы (незаменимо для быстрой разработки), включить DFU recovery mode, получить доступ к внутренним шинам данных (I2C).
Нашим первым вариантом заставить работать последовательный порт на этих маках был vdmtool: самосборный кабель, Arduino, USB-PD PHY чип и адаптер серийного порта. Звучит неплохо для нас, умеющих собрать такую штуковину, но честно говоря слишком заковыристо для тех, кто не привык мастерить на коленке свои устройства. Да и ряд минусов: ну нет, просто нет хорошей платы с USB-PD PHY, умеющей во все сигналы Type C, адаптеры 1.2В UART не так уж и часто встретишь, и тд.
Поэтому мы пришли к другому решению: если у вас внезапно завалялось два m1 мака - супер! Всё, что вам нужно, это стандартный Type C кабель (SuperSpeed / USB3.0) и macvdmtool. Эта небольшая утилита позволит одной m1 машине отлаживать другую по последовательному порту, так что можно будет всё загружать с мака. API позволяет сконфигурировать порт в режим последовательного у себя, так же как и отправить по кабелю команду на другой мак, так что никакого другого железа вообще не надо.
Стоит отметить, правда, что mac m1 - это весьма дорогой адаптер для последовательного порта :) Так что мы обязательно сделаем более функциональный USB-PD кабель в виде открытого проекта, и он будет уметь не только в отладку. По факту, мы хотим, чтобы он работал не только для маков, а и для других устройств, скажем для многих Android телефонов. Такая себе USB-PD платформа для разработки, которая может питать сама телефон, или питаться от него, и многое другое. Это пока всё ещё только в планах, но хорошие новости не заставят себя долго ждать! Наша цель - сделать этот инструмент широко доступным для комьюнити, чтобы каждый мог купить его в 1 клик.
Наконец, хоть последовательный порт и крут для отладки низкоуровневых штук, есть один минус: он весьма медленный, до 150Кб/c. M1 маки, однако, могут вести себя как обычные USB устройства, (как и iPhone), и мы можем заставить их отображаться как USB-последовательное устройство (CDC-ACM), это подхватится без дополнительных драйверов на большинстве ОС. Так мы получим полную скорость USB, и удобство использовать обычный Type C (или CtoA) кабель для подключения к любому компьютеру. USB также даёт нам лучший контроль за процессом передачи, а значит данные не будут теряться из-за неготовности получателя принимать их. Минусы тоже есть - нужен куда более сложный драйверный код, это не подойдёт для исследования по-настоящему низкоуровневых проблем. Однако как только мы внедрим его в m1n1, мы будем пользоваться этим механизмом для почти всего, и мы можем написать этот более сложный драйвер используя текущую поддержку последовательного порта: Type C порт на этих маках умеет одновременно передавать UART и USB сигналы. Увеличение производительности будет крайне важным для работы над гипервизором, который мы упоминали ранее, и так же ускорит загрузку ядра Linux, а это сейчас довольно таки долго по последовательному порту.
Мы добавим это в m1n1 в ближайшие недели, так что следите за обновлениями!
<Продолжение следует>