В первой части этой статьи мы познакомились с форматом UEFI Capsule и Intel Flash Image. Осталось рассмотреть структуру и содержимое EFI Firmware Volume, но для понимания различий между модулями PEI и драйверами DXE начнем с процесса загрузки UEFI, а структуру EFI Firmware Volume отставим на вторую часть.
С высоты птичьего полета процесс загрузки UEFI выглядит так:
Вообще говоря, нас интересует не весь это процесс, а его часть — Platform Initialization (PI), которая делится на 3 фазы: SEC, PEI и DXE.
Всю документацию по PI можно свободно загрузить с сайта UEFI Forum. Фазы SEC и PEI описаны в Volume 1, фаза DXE — в Volume 2, общие архитектурные элементы, в том числе интересующие нас форматы файлов и заголовков EFI FFS — в Volume 3, субфаза SMM (стартует в середине DXE и идет параллельно) — в Volume 4, стандарты на совместимое с PI оборудование и ПО — в Volume 5. Про оборудование и ПО здесь я писать не стану, а вот остальные фазы нужно упомянуть, т.к. не зная их, сложно понять, зачем в файле BIOS'а столько всего и чем это всё отличается друг от друга.
Первая фаза загрузки, задачи которой следующие:
По факту, на x86-64 фаза SEC проходит так:
Видно, что в участникам фазы SEC из образа BIOS понадобятся как минимум хранящиеся там патчи для микрокода CPU, а также адрес и размер Boot Firmware Volume. Да и сам код SEC записан в той же микросхеме и пока еще выполняется там же.
Вторая фаза, основная задача которой — инициализировать достаточное количество непрерывной RAM для того, чтобы можно было запустить фазу DXE, подготовить и передать в фазу DXE данные об обнаруженных устройствах, чтобы драйверы DXE смогли их правильно инициализировать. Исполняемый код PEI состоит из ядра, называемого PEI Foundation, которое является общим для процессоров с одинаковой архитектурой и модулей PEIM, выполняющих начальную инициализацию конкретных устройств и разработанные производителями этих устройств. Модули поддержки Chain of Trust, выполняющие проверку валидности других модулей, также могут присутствовать. Архитектура PEI позволяет независимую разработку и отладку модулей, и никто не мешает написать и интегрировать свой собственный модуль, если понадобится.
У PEIM может присутствовать список зависимостей от других модулей, поэтому порядок их запуска не случаен и выбирается диспетчером PEI. Модули PEIM могут заполнять независимые от позиции структуры данных — HOB'ы, в которых содержатся данные для передачи драйверу DXE и GUID этого драйвера.
По факту, на x86-64 фаза PEI происходит так:
Таким образом, при S3 Resume запуск фазы DXE не происходит вообще, что позволяет сильно ускорить загрузку. При включении FastBoot загрузку можно ускорить еще сильнее, выполняя минимальный набор тестов и модулей PEIM.
Видно, что в фазе PEI из образа BIOS нужны будут как минимум PEI Foundation и модули для всего оборудования, нуждающегося в ранней инициализации. Также стоит рассказать о том, что формат модулей PEI может как совпадать с форматом драйверов DXE (PE32+), так и отличаться от него заголовком, т.к. заголовок PE32+ содержит множество неиспользуемых в фазе PEI полей, а место в кэше процессора не резиновое. Поэтому для PEIM был разработан специальный формат TE, заголовок которого содержит только необходимые поля. TE бывают исполняемые-на-месте (XIP), перемещаемые (relocatable) и независимые от позиции (PIC). Также встречаются гибридные DXE/PEI-модули с двумя точками входа, но они обязаны быть в формате PE32+, поскольку иначе как драйвер DXE такой модуль не запустится.
Здесь выполняется основная и окончательная инициализация всего на основе полученных от PEI HOB'ов. Код DXE состоит из ядра, оно же DXE Foundation, диспетчера и драйверов. Ядро инициализирует и запускает различные службы UEFI: Boot Services, Runtime Services и DXE Services. Диспетчер отвечает за поиск и запуск DXE-драйверов, которые также имеют зависимости. Драйверы проводят окончательную инициализацию аппаратуры и предоставляют аппаратную абстракцию для служб. Весь код DXE, кроме Runtime-частей Foundation и Runtime DXE драйверов выгружается из памяти по окончанию фазы BDS, которую я здесь рассматривать не буду.
Расписывать досконально процесс запуска DXE тоже нет смысла, можно описать его в двух словах: загружается ядро, создает нужные структуры данных, затем запускается диспетчер и грузит все доступные драйверы со всех доступных носителей, затем запускается бутлоадер и пытается найти на этих носителях загрузчик ОС и передать ему управление. Если нашелся — отлично, если нет — пробуем дальше, пока не найдем. Если так ничего и не нашли — выполняем код модуля Platform Policy, который для нас написал производитель материнской платы, выводящий нам сообщение о том, что «Operating System is missing».
Видно, что из образа BIOS'а для этой фазы нужны DXE-драйверы и все, что им может понадобится. Большая часть файлов в EFI FS используется именно здесь.
Во время фазы DXE наступает момент, когда диспетчер загружает драйвер SMM Init, с которого и начинается эта субфаза. SMM — специальный режим процессора, в который он переходит при получении специального прерывания — SMI, которое может быть как программным, так и аппаратным. Большую часть (или вообще все) источников SMI можно отключить, если переход в SMM не требуется. Код SMM выполняется в SMRAM, которая становится недоступной для ОС после окончания фазы DXE, поскольку драйвер SMM намеренно закрывает к ней доступ. Код SMM выполняется и после окончания фазы DXE, до самого выключения ПК.
Драйвер SMM Init открывает SMRAM, создает ее карту и структуры данных, необходимые для запуска других драйверов SMM, а перед окончанием фазы DXE закрывает доступ к SMRAM полностью. Драйверы SMM зависят от оборудования и не имеют доступа к интерпретатору байткода, поэтому написание драйверов SMM на EBC не поддерживается. Бывают эти самые драйверы двух видов: чистые SMM, которые загружаются Init-ом непосредственно в SMRAM, и SMM/DXE-гибриды, которые сначала запускаются диспетчером DXE, а потом уже копируют часть себя в SMRAM. Сам SMM Init — именно такой гибрид.
Видно, что для этой субфазы из образа BIOS'а нужны драйверы SMM.
Теперь вы знаете, как происходит загрузка UEFI и какие модули необходимы для нее.
Я принял решение разделить планируемую вторую часть еще на две, чтобы уменьшить размер поста и снизить когнитивную нагрузку на читателя.
Во второй части статьи мы наконец рассмотрим структуру файла EFI FV, и сведения из этой вам там очень пригодятся.
Спасибо за внимание.
UEFI Platform Initialization
С высоты птичьего полета процесс загрузки UEFI выглядит так:
Вообще говоря, нас интересует не весь это процесс, а его часть — Platform Initialization (PI), которая делится на 3 фазы: SEC, PEI и DXE.
Всю документацию по PI можно свободно загрузить с сайта UEFI Forum. Фазы SEC и PEI описаны в Volume 1, фаза DXE — в Volume 2, общие архитектурные элементы, в том числе интересующие нас форматы файлов и заголовков EFI FFS — в Volume 3, субфаза SMM (стартует в середине DXE и идет параллельно) — в Volume 4, стандарты на совместимое с PI оборудование и ПО — в Volume 5. Про оборудование и ПО здесь я писать не стану, а вот остальные фазы нужно упомянуть, т.к. не зная их, сложно понять, зачем в файле BIOS'а столько всего и чем это всё отличается друг от друга.
Фаза SEC
Первая фаза загрузки, задачи которой следующие:
- Обработать все виды platform restart'ов: включение питания после неактивного состояния, перезагрузка из активного состояния, выход из режима глубокого сна, различного рода исключительные ситуации
- Подготовить временную память
- Стать Root of Trust системы: или доверять остальным частям PI, или проверить их валидность каким-либо способом
- Подготовить необходимые структуры данных и передать их и управление в фазу PEI. Как минимум, передаются состояние платформы, адрес и размер BFV, адрес и размер временной RAM, адрес и размер стека
По факту, на x86-64 фаза SEC проходит так:
- Reset Vector: сброс кэша и переход на главную процедуру иницилизации в ROM
- Switch to protected mode: переключение в защищенный режим процессора с плоской памятью без подкачки
- Initialize MTRRs for BSP: запись в кэш известных значений для различных областей памяти
- Microcode Patch Update: обновление микрокода всех доступных процессоров
- Initialize NEM: свободный кэш помечается как несбрасываемый, после чего его можно использовать как временную RAM до инициализации основной, а также позволяет написать эту самую инициализацию на обычных ЯП со стеком, в данном случае на C
- Early BSP/AP interactions: отправка всем AP прерывания INIT IPI, затем Start-up IPI, получение данных BIST со всех AP
- Hand-off to PEI entry point: передача управления и данных в фазу PEI
Видно, что в участникам фазы SEC из образа BIOS понадобятся как минимум хранящиеся там патчи для микрокода CPU, а также адрес и размер Boot Firmware Volume. Да и сам код SEC записан в той же микросхеме и пока еще выполняется там же.
Фаза PEI
Вторая фаза, основная задача которой — инициализировать достаточное количество непрерывной RAM для того, чтобы можно было запустить фазу DXE, подготовить и передать в фазу DXE данные об обнаруженных устройствах, чтобы драйверы DXE смогли их правильно инициализировать. Исполняемый код PEI состоит из ядра, называемого PEI Foundation, которое является общим для процессоров с одинаковой архитектурой и модулей PEIM, выполняющих начальную инициализацию конкретных устройств и разработанные производителями этих устройств. Модули поддержки Chain of Trust, выполняющие проверку валидности других модулей, также могут присутствовать. Архитектура PEI позволяет независимую разработку и отладку модулей, и никто не мешает написать и интегрировать свой собственный модуль, если понадобится.
У PEIM может присутствовать список зависимостей от других модулей, поэтому порядок их запуска не случаен и выбирается диспетчером PEI. Модули PEIM могут заполнять независимые от позиции структуры данных — HOB'ы, в которых содержатся данные для передачи драйверу DXE и GUID этого драйвера.
По факту, на x86-64 фаза PEI происходит так:
- Establish use of «memory»: перенос данных из ROM в раннюю RAM (т.е. в кэш)
- PEI Dispatcher: запуск модулей PEIM в порядке от не имеющих зависимостей до имеющих сложные зависимости. Это цикл, который заканчивается в момент, когда не запущенных модулей не остается
- CPI PEIM: инициализация CPU, настройка MSR и т.п. (Мы вернемся к этому модулю при обсуждении патча CPU PM)
- Platform PEIM: ранняя инициализация MCH, ICH, встроенных интерфейсов платформы (SMBus, Reset, и т.п.). Определение режима загрузки (обычный, Recovery, S3 Resume), используя данные, полученные в фазе SEC.
- Memory Initialization PEIM: инициализация основной RAM и перенос в нее данных из кэша, которым теперь можно пользоваться нормально. процесс зависит от определенного на предыдущем шаге состояния системы, например, при S3 Resume тестирование памяти не выполняется, что сокращает время загрузки
- Если система не находится в S3 Resume, то происходит передача HOB'ов и управления в фазу DXE, а фаза PEI на этом завершается
- Если все же находится — выполняется CPU PEIM for S3 Boot Script, выполняющий возврат всех процессоров в их сохраненное состояние
- S3 Boot Script Executor: восстановление состояния других устройств
- OS Resume Vector: переход к ОС
Таким образом, при S3 Resume запуск фазы DXE не происходит вообще, что позволяет сильно ускорить загрузку. При включении FastBoot загрузку можно ускорить еще сильнее, выполняя минимальный набор тестов и модулей PEIM.
Видно, что в фазе PEI из образа BIOS нужны будут как минимум PEI Foundation и модули для всего оборудования, нуждающегося в ранней инициализации. Также стоит рассказать о том, что формат модулей PEI может как совпадать с форматом драйверов DXE (PE32+), так и отличаться от него заголовком, т.к. заголовок PE32+ содержит множество неиспользуемых в фазе PEI полей, а место в кэше процессора не резиновое. Поэтому для PEIM был разработан специальный формат TE, заголовок которого содержит только необходимые поля. TE бывают исполняемые-на-месте (XIP), перемещаемые (relocatable) и независимые от позиции (PIC). Также встречаются гибридные DXE/PEI-модули с двумя точками входа, но они обязаны быть в формате PE32+, поскольку иначе как драйвер DXE такой модуль не запустится.
Фаза DXE
Здесь выполняется основная и окончательная инициализация всего на основе полученных от PEI HOB'ов. Код DXE состоит из ядра, оно же DXE Foundation, диспетчера и драйверов. Ядро инициализирует и запускает различные службы UEFI: Boot Services, Runtime Services и DXE Services. Диспетчер отвечает за поиск и запуск DXE-драйверов, которые также имеют зависимости. Драйверы проводят окончательную инициализацию аппаратуры и предоставляют аппаратную абстракцию для служб. Весь код DXE, кроме Runtime-частей Foundation и Runtime DXE драйверов выгружается из памяти по окончанию фазы BDS, которую я здесь рассматривать не буду.
Расписывать досконально процесс запуска DXE тоже нет смысла, можно описать его в двух словах: загружается ядро, создает нужные структуры данных, затем запускается диспетчер и грузит все доступные драйверы со всех доступных носителей, затем запускается бутлоадер и пытается найти на этих носителях загрузчик ОС и передать ему управление. Если нашелся — отлично, если нет — пробуем дальше, пока не найдем. Если так ничего и не нашли — выполняем код модуля Platform Policy, который для нас написал производитель материнской платы, выводящий нам сообщение о том, что «Operating System is missing».
Видно, что из образа BIOS'а для этой фазы нужны DXE-драйверы и все, что им может понадобится. Большая часть файлов в EFI FS используется именно здесь.
Cубфаза SMM
Во время фазы DXE наступает момент, когда диспетчер загружает драйвер SMM Init, с которого и начинается эта субфаза. SMM — специальный режим процессора, в который он переходит при получении специального прерывания — SMI, которое может быть как программным, так и аппаратным. Большую часть (или вообще все) источников SMI можно отключить, если переход в SMM не требуется. Код SMM выполняется в SMRAM, которая становится недоступной для ОС после окончания фазы DXE, поскольку драйвер SMM намеренно закрывает к ней доступ. Код SMM выполняется и после окончания фазы DXE, до самого выключения ПК.
Драйвер SMM Init открывает SMRAM, создает ее карту и структуры данных, необходимые для запуска других драйверов SMM, а перед окончанием фазы DXE закрывает доступ к SMRAM полностью. Драйверы SMM зависят от оборудования и не имеют доступа к интерпретатору байткода, поэтому написание драйверов SMM на EBC не поддерживается. Бывают эти самые драйверы двух видов: чистые SMM, которые загружаются Init-ом непосредственно в SMRAM, и SMM/DXE-гибриды, которые сначала запускаются диспетчером DXE, а потом уже копируют часть себя в SMRAM. Сам SMM Init — именно такой гибрид.
Видно, что для этой субфазы из образа BIOS'а нужны драйверы SMM.
Заключение
Теперь вы знаете, как происходит загрузка UEFI и какие модули необходимы для нее.
Я принял решение разделить планируемую вторую часть еще на две, чтобы уменьшить размер поста и снизить когнитивную нагрузку на читателя.
Во второй части статьи мы наконец рассмотрим структуру файла EFI FV, и сведения из этой вам там очень пригодятся.
Спасибо за внимание.
Литература
- Analysis of the building blocks and attack vectors associated with the Unified Extensible Firmware Interface (UEFI), Paper by Jean-François Agneessens
- Reducing Platform Boot Time, Paper by Michael Rothman, Genliu Xing, Yan Wang and Jiong Gong
- UEFI Platform Initialization Specification 1.2.1 Errata A, Documents by UEFI Forum