Вступительное слово переводчика
Некоторое время назад на Хабре уже писали про проект Asahi Linux.
Если кратко, то это попытка запустить linux на новом маке с M1 архитектурой.
Прошло 2 месяца с момента старта проекта, и разработчики подготовили отчёт о проделанной работе, который помимо перечня сделанного и несделанного, позволяет немного проникнуться сутью работы смеси системного инженера и реверсера.
Отчёт они написали немаленький, так что его перевод будет появляться по частям.
Отчёт о проделанной работе
Рад представить вам первый отчёт о проделанной работе над Asahi Linux! В этих отчётах, по образу и подобию Dolphin playbook, мы будем ежемесячно рассказывать о том, что у нас происходит.
Добавить поддержку Linux для нового SoC это не самая лёгкая задача. Я надеюсь, эти отчёты дадут вам прочувствовать, сколько всего нужно, чтобы Linux заработал на совершенно новом устройстве.
Изначально, я планировал сделать два разных отчёта за январь и февраль, но разработка идёт такими ударными темпами, что я решил сделать один отчёт за оба месяца.
Маленькая вставка о терминологии
В этом отчёте вы можете встретить термины AArch64, ARM64, и ARMv8-A.
AArch64 это набор команд 64-битного ARM;
ARM64 – так в Linux называют поддержку 64-битного ARM.
ARMv8-A это спецификация архитектуры центрального процессора ARM, которая включает AArch64.
Точные значения этих терминов, как вы видите, несколько отличаются, но для наших целей можете читать всё это как “64-битный ARM”.
Путь в тысячу миль
Проект Asahi Linux официально стартовал в начале года, мы тогда все ждали релиза очень важной для нас штуки: поддержки от Apple загрузки сторонних ядер на Apple Silicon системах.
Несмотря на то, что эта фича документирована и практически готова, всё ещё не хватает кое-чего, чтобы она заработала : поддержки kmutil configure-boot команды, которая собственно и даёт вам возможность устанавливать не-Apple ядра.
Но не то, что бы нас останавливало отсутствие этой возможности, ведь первый шаг в портировании ОС на недокументированную платформу – это документировать её!
Apple Silicon Mac-и загружаются вообще не так, как ПК. Их загрузка больше похожа на то, как стартуют embedded платформы (например, Android или, само собой, iOS устройства), однако не без своих специфических нюансов. Вдобавок, Apple предприняла некоторые действия, чтобы процесс загрузки ощущался более похожим на Intel Mac, так что итоговая схема работы выглядит довольно запутанно.
Ну скажем, вы знали, что Apple Silicon Mac-и вообще не могут загружаться с внешних дисков? Или что их загрузчик не может отображать GUI вообще, а Boot Picker это по факту обычное полноэкранное приложение, а не часть загрузчика?
Так что прежде чем мы смогли запустить свои ядра на новых маках, мы описали как там работает весь процесс загрузки, как разбит на разделы встроенный SSD, и в чём отличие этих всех схем от ПК.
Мы писали это не только для себя, но и для всех пользователей, которые интересуются, как работает их компьютер. Некоторые детали и “почему так?” так были раскрыты в материалах Apple, но там есть далеко не всё.
Наводя мосты
Процесс загрузки Apple Silicon Mac не основан на существующих стандартах. Этот механизм медленно развивался с первых дней iOS устройств, и наконец стал таким, как он есть.
Весь остальной мир 64-битных ARM по большей части придерживается одного из двух соперничающих стандартов: UEFI + ACPI(по большей части для серверов с Windows или Linux) и ARM64 Linux boot protocol + DeviceTree (используется на небольших системах, так же поддерживается U-Boot и многими другими). Нам нужно было выбрать один из этих стандартов для Asahi Linux, и придумать, как подружить его с тем, что творится внутри Apple Silicon Mac.
UEFI&ACPI довольно сложные штуки, чаще всего используемые для больших ARM систем. Их стандарт по большей части определяется комитетом и UEFI Forum. В отличие от мира x86 ПК, который куда однороднее подходит к этому вопросу, ARM максимально разнообразен и включает в себя всевозможные дизайны SoC, с совершенно разными требованиями к описанию железа. Это значит, что добавить поддержку нового SoC почти всегда означает поправить немножко эти стандарты под себя, добавив поддержку специальных битов, отличающих ваш чип от остальных. Говоря про ACPI – такая работа заняла бы очень много ресурсов, поэтому ACPI почти не используется в малых embedded системах, без Windows. Это не подойдёт и нам по тем же причинам.
Широкий спектр небольших embedded ARM Linux систем пользуется стандартом DeviceTree, почти что без изменений. Например, его использует загрузчик Android. DeviceTree куда проще, чем ACPI, ведь это по сути просто набор данных, описывающих железо, в то время как ACPI таблицы это мешанина из кода и данных.
Стандарт DeviceTree фактически устанавливается набором соответствий устройств и их описания, который ведётся вместе с разработкой ядра Linux, то есть мы сможем менять то, что нам нужно, вместе с написанием самих драйверов под Linux. Как вы понимаете, загрузчик Asahi Linux будет использовать именно DeviceTree.
Довольно занятно, что Apple используют свою версию DeviceTree для Apple Silicon, которая называется Apple Device Tree! Как же так получилось? А очень просто: сам DeviceTree основан на OpenFirmware, который лёг в основу загрузчика ещё PowerPC, включая старые маки.
К сожалению, несмотря на то, что формат ADT довольно близок embedded Linux разработчикам, это не значит, что мы можем использовать его напрямую: бинарно формат отличается от DeviceTree, и его нельзя автоматически конвертировать без понимания, где какие данные там лежат. Вдобавок к этому, соответствия устройств и их описаний, весьма отличаются. Хотя Linux и macOS работают одинаково на PowerPC Mac-ах, и совместимы друг с другом, Linux прошёл огромный путь совершенствования по сравнению с Apple касательно ARM. Попытка объединить две разные идеи о том, как должны выглядеть деревья устройств, Linux-овую и Apple-овскую, грозит обернуться кошмаром.
Чтобы привести то, как Apple видят device tree, к тому, как это видит стандарт, мы делаем m1n1 – загрузчик для Apple Silicon машин. Его цель – взять на себя заботу о максимальном количестве специфических для Apple вещах, и упростить жизнь для всех, включая Linux.
Вы можете добавить m1n1 к ядру Linux (cat m1n1.macho initrd.bin devicetree.dtb Image.gz > m1n1-kernel.macho
), установить его на Mac с помощью kmutil
и он сделает всё, что требуется для запуска Linux. Когда вы загружаетесь в Linux с помощью m1n1, он делает примерно следующее:
Инициализирует ЦПУ, выставляет т.н. chicken bit, чтобы он заработал
Считывает информацию, которую iBoot (загрузчик Apple) ему даёт: сколько оперативной памяти доступно, адрес framebuffer(видеопамяти, отображаемой на экране) в ОЗУ
Инициализирует MMU. Это нужно, чтобы включить использование кэшей ЦПУ, иначе всё будет ужасно тормозить.
Заменяет Apple логотип на Asahi Linux логотип :)
Убивает watchdog timer. Без этого шага мак бы перезагружался через минуту-другую, потому что думал бы, что процесс загрузки просто завис.
Определяет, что же мы будем загружать: ядро Linux, DeviceTree и (опционально) initramfs radmisk с программами, которые могут работать во время загрузки, если таковые были.
Инициализирует все остальные ядра ЦПУ, выставляет им chicken bits, и переводит их в режим ожидания spin-table (подробнее об этом - ниже), чтобы их мог использовать Linux.
Берёт информацию из ADT (Apple Device Tree) и генерирует соответствующий ему DeviceTree. Этот шаг нужен для поддержки тех параметров, которые могут меняться от машины к машине или от версии iBoot : размер памяти, информация про framebuffer, сид для инициализации Linux random generator, и всякого другого. m1n1 добавляет некоторую собственную информацию, включая подробности о spin-table и cmd аргументы для ядра.
Передаём управление Linux, ну или кому-нибудь другому, кто ждёт от нас этого.
Так, а что такое spin-table? Это один из двух стандартов по использованию Linux on ARM дополнительных ядер процессора с помощью DeviceTree. Вместо того, чтобы каждый городил драйверы для своих платформ, есть два ожидаемых варианта поведения: spin-table и PSCI.
Spin-table – загрузчик просто включает все CPU и оставляет их в режиме ожидания (spinning). Чтобы вывести ядро из режима ожидания, Linux пишет в табличку в ОЗУ определённое значение(адрес), которое указывает, какую команду нужно выполнить в ядре. Это идеальный путь для простых систем. Единственный недостаток: нельзя полностью остановить ядро после такого, это путь в один конец. Однако всё ещё можно вводить ядра в разные энергоэффективные режимы с помощью разных других техник. Пока что мы решили использовать этот подход, и не факт, что нам когда-либо захочется его поменять.
PSCI (ссылка) – это ARM стандарт по управлению ядрами (и не только) во время исполнения. Обычно, такое управление осуществляется из EL3 (secure firmware or TrustZone), или же через VM гипервизор, запущенный в EL2 (ОС обычно работает в EL1).
Примечание переводчика
Чтобы внести немного ясности для тех, кому ARM не очень близок. Вам может быть знакомо такое понятие, как protection ring: https://en.wikipedia.org/wiki/Protection_ring
По-простому, оно определяет, кому какие команды ЦПУ можно выполнять, и куда в памяти можно обращаться. Обычно говорят про ring 0 как про уровень ядра, 1-2 уровень драйверов, а 3 – уровень приложений.
Так вот на ARM всё решили назвать наоборот:
учитывайте это, и не удивляйтесь.
Однако, EL2 и EL3 – опциональные фичи для ARMv8 CPU, и оказалось, что в M1 EL3 не поддерживается. EL2 есть, но мы хотим поддерживать запуск VM с Linux на борту, что само по себе требует запускать Linux в EL2, так что мы уже не сможем добавить туда ещё один гипервизор.
Для нас это означает неприменимость PSCI прямо сейчас, однако в ближайшем будущем ситуация может измениться.
Тут нужно заметить, что без PSCI не будет поддержки полноценного режима сна. Однако может быть, если мы достаточно хорошо обкатаем наш power management, нам и не понадобится полноценный режим сна, и это не будет сказываться на жизни батареи. (многие современные устройства работают без полноценного режима сна!).
Время покажет, как оно будет.
Итак, ладно, мы договорились, что у нас будет devicetree, но это же не значит, что у нас не может быть UEFI!
ARM64 системы могут загружаться используя UEFI+DeviceTree, чтобы обеспечить пользователю видимость загрузки “как на ПК”, использовать GRUB и прочие стандартные механизмы для установки и обновления ядер. Однако, m1n1 не поддерживает ничего из вышеперечисленного, так что же делать?
К счастью, ответ есть, и это U-Boot. U-Boot умеет загружаться как ядро Linux – так что мы можем просто загрузить U-Boot из m1n1 – и даёт неплохое UEFI окружение для GRUB и Linux.
В общем, скорее всего, последовательность запуска Asahi Linux будет примерно такой:
m1n1-> U-boot -> GRUB -> Linux
Если добавить сюда установку разных Apple-specific битов, то весь процесс выглядит так:
SecureROM внутри M1 SoC стартует и загружает iBoot1 из NOR flash памяти.
iBoot1 читает загрузочный конфиг с встроенного SSD, валидирует системную политику загрузки, выбирает ОС для загрузки – в нашем случае Asahi Linux / m1n1 будет выглядеть как раздел с ОС для iBoot1
iBoot2, который по сути и является загрузчиком, и должен находиться на разделе с загружаемой ОС, загружает firmware для встроенных устройств, строит apple device tree, и загружает Mach-O ядро (в нашем случае m1n1)
m1n1 разбирает ADT, превращает его в понятный для Linux набор описаний (Flattened Device Tree), и загружает U-boot
U-boot, у которого будут драйвера для встроенного SSD, читает конфиги и даёт нам UEFI, включая поставку devicetree из m1n1
GRUB загружается как обычное UEFI приложение, работает так же, как и на любом ПК. Именно он позволит нам управлять ядрами так, как мы привыкли, через grub-mkconfig и /etc/default/grub.
Наконец-то, ядро Linux запускается, используя DeviceTree, сгенерированное ещё m1n1.
Фух! Выглядит немного дико для людей из ПК-мира, но на самом деле долгие последовательности загрузки это норма в embedded системах (по факту UEFI на ПК тоже включает в себя целый ряд шагов, просто пользователю они не видны).
Вот вам для примера одна из возможных цепочек запуска DragonBoard410c (Qualcomm платформа):
PBL->SBL->QSEE->QHEE->LK->U-boot->GRUB->Linux
Заметьте, мы не можем заменить iBoot2 (он подписан Apple, и эта подпись проверяется), но итоговый процесс установки будет автоматически настраивать минимальную “macOS” вместе с iBoot2 и всем чем надо, достаточным, чтобы mac распознал этот образ как загружаемый раздел с ОС (а вот реального ядра macOS или его файловой системы там не будет). Установщик ещё не готов, так что пока всем, кто захочет проверить, глубока ли кроличья нора, придётся последовать нашему гайду.
Пока что мы грузим сразу Linux из m1n1, но Mark Kettenis работает с нами над поддержкой U-Boot и OpenBSD.
Но нужно же понимать, что m1n1 он не о том, чтобы запустить Linux. Честно говоря, это даже не загрузчик на самом то деле!
<Продолжение следует>
lrrr11
читал это на английском, эпическую работу люди делают, мягко говоря.
Помню как разработчики Байкала добавили поддержу своей SoC в апстрим ядра Linux и это подавалось как большая победа. А тут мало того что куда более сложная, так ещё и практически не документированная система.