Dxe драйвер что это

Обновлено: 06.07.2024

Что происходит при запуске сервера, порой неизвестно даже опытным системным администраторам и разработчикам. Во второй части материала тестировщик Selectel, Владимир Туров, подробно рассказывает о процессе на примере UEFI.

Первая версия того, что сейчас известно как Unified Extensive Firmware Interface (UEFI), разрабатывалась в 90-е годы прошлого тысячелетия специально под системы на Intel® Itanium® и называлась Intel Boot Initiative, а позже — EFI.

Желание «обновить» процесс загрузки было ожидаемо. PC-BIOS, именуемый ныне Legacy, предлагает работать в 16-битном real mode, адресует всего 1 МБ оперативной памяти, а загрузчик вместе с таблицей разделов должен размещаться в первых 512 байтах накопителя. Более того, PC-BIOS передает управление первому найденному загрузчику без возможности возврата назад. При этом обработку случаев с несколькими операционными системами возлагают на плечи загрузчика.

Ограничение на размер загрузчика диктует использование разметки Master Boot Record (MBR), появившийся в 1983 году. MBR не стандартизирован, однако множество производителей придерживаются «сложившихся традиций». У MBR есть серьезные ограничения: по умолчанию поддерживается только 4 раздела и объем накопителя не более 2.2 ТБ.

В декабре 2000 года была выпущена первая широко распространенная спецификация EFI под версией 1.02. Спустя пять лет Intel передали EFI в UEFI Forum, добавив Unified в название, чтобы подчеркнуть изменения. Спецификация UEFI лежит в открытом доступе и состоит из нескольких документов:

  • ACPI Specification;
  • UEFI Specification;
  • UEFI Shell Specification;
  • UEFI Platform Initialization Specification;
  • UEFI Platform Initialization Distribution Packaging Specification.

Самое интересное начинается в UEFI Platform Initialization Specification, где описываются все фазы загрузки платформы.

UEFI универсален, но в данной статье мы будем опираться на стандарт, поглядывая в сторону процессоров на архитектуре x86_64.

Последовательность фаз загрузки UEFI Источник: UEFI Platform Initialization Specification

После инициации включения платформы блок питания ждет, пока не завершатся переходные процессы, и после устанавливает сигнал на линию Power_Good. И первым начинает работу не центральный процессор, а автономная подсистема Intel® Management Engine (ME) или аналогичная ей AMD Secure Technology (ST). Эта подсистема проводит собственные операции, а затем подготавливает и запускает первое ядро одного процессора, именуемое Bootstrap Processor (BSP).

В соответствии с принятой терминологией ядро/поток процессора здесь и далее будет называться процессором: начальным (bootstrap processor) или прикладным (application processor).

Как и в Legacy, процессор начинает выполнять первую инструкцию в конце адресного пространства по адресу 0xfffffff0. Эта инструкция — прыжок на первую фазу инициализации платформы — SEC.

  • обработка события включения;
  • инициализация достаточного количества памяти для следующей фазы;
  • становление корня доверия системы;
  • передача необходимой информации и управления на следующую фазу.

Процессоры x86_64 запускаются в 16-битном реальном режиме, и в процессе первичной инициализации BSP переводится в 32-битный защищенный режим. Затем происходит обновление микрокода всех доступных процессоров.

Следом происходит обработка события включения. Под этим подразумевается агрегация информации о состояниях оборудования, чтобы на следующей фазе некоторые модули могли сделать выводы о «здоровье» и общем состоянии платформы.

Во время фазы SEC не происходит инициализация оперативной памяти. Вместо этого свободный кэш процессора помечается как несбрасываемый, и он превращается во временную оперативную память. Такой режим называется no-eviction mode (NEM). В выделенной памяти создается стек, что позволит модулям из следующих фаз использовать стековые языки программирования до инициализации основной оперативной памяти.

Далее происходит инициализация всех прикладных процессоров (Application Processor, AP) с отправкой им специальной последовательности межпроцессорных прерываний (Inter-Processor Interrupt, IPI). Последовательность Init IPI — Start-up IPI — пробуждает прикладной процессор и запускает на нем самотестирование — Built-In Self-Test (BIST). Результаты тестирования записываются и передаются далее для анализа.

В конце фазы Security необходимо найти раздел Boot Firmware Volume (BFV), на котором располагается исполняемый код следующей фазы, а также по возможности найти другие, неосновные, разделы с кодом (Firmware Volume, FV).

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

В первой части этой статьи мы познакомились с форматом UEFI Capsule и Intel Flash Image. Осталось рассмотреть структуру и содержимое EFI Firmware Volume, но для понимания различий между модулями PEI и драйверами DXE начнем с процесса загрузки UEFI, а структуру EFI Firmware Volume отставим на вторую часть.

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
  1. Обработать все виды platform restart'ов: включение питания после неактивного состояния, перезагрузка из активного состояния, выход из режима глубокого сна, различного рода исключительные ситуации
  2. Подготовить временную память
  3. Стать Root of Trust системы: или доверять остальным частям PI, или проверить их валидность каким-либо способом
  4. Подготовить необходимые структуры данных и передать их и управление в фазу PEI. Как минимум, передаются состояние платформы, адрес и размер BFV , адрес и размер временной RAM, адрес и размер стека
  1. Reset Vector: сброс кэша и переход на главную процедуру иницилизации в ROM
  2. Switch to protected mode: переключение в защищенный режим процессора с плоской памятью без подкачки
  3. Initialize MTRRs for BSP: запись в кэш известных значений для различных областей памяти
  4. Microcode Patch Update: обновление микрокода всех доступных процессоров
  5. Initialize NEM : свободный кэш помечается как несбрасываемый, после чего его можно использовать как временную RAM до инициализации основной, а также позволяет написать эту самую инициализацию на обычных ЯП со стеком, в данном случае на C
  6. Early BSP /AP interactions: отправка всем AP прерывания INIT IPI , затем Start-up IPI, получение данных BIST со всех AP
  7. Hand-off to PEI entry point: передача управления и данных в фазу PEI
Фаза PEI
  1. Establish use of «memory»: перенос данных из ROM в раннюю RAM (т.е. в кэш)
  2. PEI Dispatcher: запуск модулей PEIM в порядке от не имеющих зависимостей до имеющих сложные зависимости. Это цикл, который заканчивается в момент, когда не запущенных модулей не остается
  3. CPI PEIM: инициализация CPU, настройка MSR и т.п. (Мы вернемся к этому модулю при обсуждении патча CPU PM)
  4. Platform PEIM: ранняя инициализация MCH , ICH , встроенных интерфейсов платформы (SMBus , Reset, и т.п.). Определение режима загрузки (обычный, Recovery, S3 Resume), используя данные, полученные в фазе SEC.
  5. Memory Initialization PEIM: инициализация основной RAM и перенос в нее данных из кэша, которым теперь можно пользоваться нормально. процесс зависит от определенного на предыдущем шаге состояния системы, например, при S3 Resume тестирование памяти не выполняется, что сокращает время загрузки
  6. Если система не находится в S3 Resume, то происходит передача HOB'ов и управления в фазу DXE, а фаза PEI на этом завершается
  7. Если все же находится — выполняется CPU PEIM for S3 Boot Script, выполняющий возврат всех процессоров в их сохраненное состояние
  8. S3 Boot Script Executor: восстановление состояния других устройств
  9. OS Resume Vector: переход к ОС
Фаза DXE
Cубфаза SMM

Заключение

Теперь вы знаете, как происходит загрузка UEFI и какие модули необходимы для нее.
Я принял решение разделить планируемую вторую часть еще на две, чтобы уменьшить размер поста и снизить когнитивную нагрузку на читателя.
Во второй части статьи мы наконец рассмотрим структуру файла EFI FV, и сведения из этой вам там очень пригодятся.
Спасибо за внимание.


В прошлой статье про SecureBoot мне очень не хватало возможности сделать снимок экрана при настройке UEFI через BIOS Setup, но тогда выручило перенаправление текстовой консоли в последовательный порт. Это отличное решение, но доступно оно на немногих серверных материнских платах, и через него можно получить только псевдографику, а хотелось бы получить настоящую — она и выглядит приятнее, и вырезать ее каждый раз из окна терминала не надо.
Вот именно этим мы и займемся в этой статье, а заодно я расскажу, что такое DXE-драйвер и как написать, собрать и протестировать такой самостоятельно, как работают ввод с клавиатуры и вывод на экран в UEFI, как найти среди подключенных устройств хранения такое, на которое можно записывать файлы, как сохранить что-нибудь в файл из UEFI и как адаптировать какой-то внешний код на С для работы в составе прошивки.
Если вам все еще интересно — жду вас под катом.

Отказ от ответственности

Прежде чем говорить о написании и отладке драйверов для UEFI, стоит сразу же сказать, что эксперименты с прошивкой — дело опасное, они могут привести к «кирпичу», а в самых неудачных редких случаях — к выходу из строя аппаратуры, поэтому я заранее предупреждаю: всё, что вы тут прочитаете, вы используете на свой страх и риск, я не несу и не буду нести ответственность за потерю работоспособности вашей прошивки или платы. Прежде чем начинать любые эксперименты с прошивкой, необходимо сделать полную копию всего содержимого SPI flash при помощи программатора. Только так вы можете гарантировать успешное восстановление прошивки после любого программного сбоя.
Если у вас нет программатора, но попробовать написать и отладить DXE -драйвер очень хочется, используйте для этого OVMF, VmWare Workstation 12 или любые другие системы виртуализации с поддержкой UEFI на ваш выбор.

Что там нужно и почему это DXE-драйвер

Задача наша состоит в том, чтобы снять скриншот со всего экрана во время работы какого-то UEFI-приложения, например BIOS Setup, нажатием определенной комбинации клавиш, найти файловую систему с доступом на запись и сохранить полученный скриншот на нее. Также было бы неплохо получить какую-то индикацию статуса. Т.к. для снятия скриншота потребуется прерывать работу UEFI-приложений, сама программа по их снятию приложением быть не может, ведь никакой вытесняющей многозадачности в UEFI пока еще не предусмотрено, поэтому нам нужен DXE-драйвер.
Схема его работы планируется примерно следующая:
0. Загружаемся только после появления текстового ввода (чтобы обрабатывать нажатия комбинации клавиш) и графического вывода (чтобы было с чего снимать скриншоты).
1. Вешаем обработчик нажатия комбинации LCtrl + LAlt + F12 (или любой другой на ваш вкус) на все доступные входные текстовые консоли.
2. В обработчике находим все выходные графические консоли, делаем с них скриншот и перекодируем его в формат PNG (т.к. UEFI-приложения обычно не используют миллионы цветов, то в этом формате скриншоты получаются размером в десятки килобайт вместо нескольких мегабайт в BMP).
3. В том же обработчике находим первую попавшуюся ФС с возможностью записи в корень и сохраняем туда полученные файлы.
Можно расширить функциональность выбором не первой попавшейся ФС, а, к примеру, только USB-устройств или только разделов ESP , оставим это на самостоятельную работу читателю.

Выбираем SDK

Для написания нового кода для работы в UEFI имеются два различных SDK — более новый EDK2 от UEFI Forum и GNU-EFI от независимых разработчиков, основанный на старом коде Intel. Оба решения подразумевают, что вы будете писать код на C и/или ассемблере, в нашем случае постараемся обойтись чистым C.
Не мне судить, какой SDK лучше, но я предлагаю использовать EDK2, т.к. он официальный и кроссплатформенный, и новые фичи (вместе с исправлением старых багов) появляются в нем значительно быстрее благодаря близости к источнику изменений, плюс именно его используют все известные мне IBV для написания своего кода.
EDK2 находится в процессе постоянной разработки, и в его trunk стабильно добавляют по 2-3 коммита в день, но так как мы здесь за самыми последними веяниями не гонимся (все равно они еще ни у кого не работают), поэтому будем использовать последний на данный момент стабильный срез EDK2, который называется UDK2015.
Чтобы обеспечить кроссплатформенность и возможность сборки различными компиляторами, EDK2 генерирует make-файлы для каждой платформы, используя конфигурационные файлы TXT (конфигурация окружения), DEC, DSC и FDF (конфигурация пакета) и INF (конфигурация компонента), подробнее о них я расскажу по ходу повествования, а сейчас нужно достать EDK2 и собрать HelloWorld, чем и займемся, если же вам не терпится узнать подробности прямо сейчас — проследуйте в документацию.

Настраиваем сборочное окружение

Структура проекта


Приложения и драйверы в EDK2 собираются не отдельно, а в составе т.н Package, т.е. пакета. В пакет, кроме самих приложений, входят еще и библиотеки, наборы заголовочных файлов и файлы с описанием конфигурации пакета и его содержимого. Сделано это для того, чтобы позволить различным драйверам и приложениям использовать различные реализации библиотек, иметь доступ к различным заголовочным файлам и GUID'ам. Мы будем использовать MdeModulePkg, это очень общий пакет без каких-либо зависимостей от архитектуры и железа, и если наш драйвер удастся собрать в нем, он почти гарантированно будет работать на любых реализациях UEFI 2.1 и более новых. Минусом такого подхода является то, что большая часть библиотек в нем (к примеру, DebugLib, используемая для получения отладочного вывода) — просто заглушки, и их придется писать самому, если возникнет такая необходимость.
Для сборки нашего драйвера понадобится INF-файл с информацией о том, какие именно библиотеки, протоколы и файлы ему нужны для сборки, а также добавление пути до этого INF-файла в DSC-файл пакета, чтобы сборочная система вообще знала, что такой INF-файл есть.
Начнем с конца: открываем файл UDK2015/MdeModulePkg/MdeModulePkg.dsc и пролистываем его до раздела [Components] (можно найти его поиском — это быстрее). В разделе перечислены по порядку все файлы, принадлежащие пакету, выглядит начало раздела вот так:Добавляем туда свой будущий INF-файл вместе с путем до него относительно UDK2015. Предлагаю создать для него прямо в MdeModulePkg папку CrScreenshotDxe, а сам INF-файл назвать CrScreenshotDxe.inf. Как вы уже догадались, Cr — это от «CodeRush», а автор этой статьи — сама скромность. В результате получится что-то такое:Сохраняем изменения и закрываем DSC-файл, больше мы его менять не будем, если не захотим настроить отладочный вывод, но это уже совсем другая история.
Теперь нужно заполнить сам INF-файл:
Осталось создать упомянутый выше файл CrScreenshotDxe.с:

Если теперь повторить команду build, она должна быть успешной, иначе вы что-то сделали неправильно.
Вот теперь у нас, наконец, есть заготовка для нашего драйвера, и можно перейти непосредственно к написанию кода. Совершенно ясно, что такая сборочная система никуда не годится, и работать с ней через редактирование текстовых файлов не очень приятно, поэтому каждый из IBV имеет собственное решение по интеграции сборочной системы EDK2 в какую-нибудь современную IDE, к примеру среда AMI Visual eBIOS — это такой обвешенный плагинами Eclipse, а Phoenix и Insyde обвешивают ими же Visual Studio.
Есть еще замечательный проект VisualUefi за авторством известного специалиста по компьютерной безопасности Алекса Ионеску, и если вы тоже любите Visual Studio — предлагаю попробовать его, а мы пока продолжим угарать по хардкору, поддерживать дух старой школы и всё такое.

Реагируем на нажатие комбинации клавиш


Здесь все достаточно просто: при загрузке драйвера переберем все экземпляры протокола SimpleTextInputEx, который публикуется драйвером клавиатуры и чаще всего ровно один, даже в случае, когда к системе подключено несколько клавиатур — буфер то общий, если специально что-то не менять. Тем не менее, на всякий случай переберем все доступные экземпляры, вызвав у каждого функцию RegisterKeyNotify, которая в качестве параметра принимает комбинацию клавиш, на которую мы намерены реагировать, и указатель на callback-функцию, которая будет вызвана после нажатия нужно комбинации, а в ней уже и будет проведена вся основная работа.

Для успешной компиляции пока не хватает функций TakeScreenshot и ShowStatus, о которых ниже.

Ищем ФС с доступом на запись, пишем данные в файл


Прежде, чем искать доступные графические консоли и снимать с них скриншоты, нужно выяснить, можно ли эти самые скриншоты куда-то сохранить. Для этого нужно найти все экземпляры протокола SimpleFileSystem, который публикуется драйвером PartitionDxe для каждого обнаруженного тома, ФС которого известна прошивке. Чаще всего единственные известные ФС — семейство FAT12/16/32 (иногда только FAT32), которые по стандарту UEFI могут использоваться для ESP. Дальше нужно проверить, что на найденную ФС возможна запись, сделать это можно разными способами, самый простой — попытаться создать на ней файл и открыть его на чтение и запись, если получилось — на эту ФС можно писать. Решение, конечно, не самое оптимальное, но работающее, правильную реализацию предлагаю читателям в качестве упражнения.

Этому коду больше ничего не нужно, работает как есть.

Ищем графическую консоль и делаем снимок её экрана


Проверив, что сохранять скриншоты есть на что, займемся их снятием. Для этого понадобится перебрать все экземпляры протокола GOP, который публикуют GOP-драйверы и VideoBIOS'ы (точнее, не сам VBIOS, который ничего не знает ни про какие протоколы, а драйвер ConSplitter, реализующий прослойку между старыми VBIOS и UEFI) для каждого устройства вывода с графикой. У этого протокола есть функция Blt для копирования изображения из фреймбуффера и в него, пока нам понадобится только первое. При помощи объекта Mode того же протокола можно получить текущее разрешение экрана, которое нужно для выделения буффера нужного размера и снятия скриншота со всего экрана, а не с какой-то его части. Получив скриншот, стоит проверить что он не абсолютно черный, ибо сохранять такие — лишняя трата времени и места на ФС, черный прямоугольник нужного размера можно и в Paint нарисовать. Затем нужно преобразовать картинку из BGR (в котором её отдает Blt) в RGB (который нужен энкодеру PNG) иначе цвета на скриншотах будут неправильные. Кодируем полученную после конвертации картинку и сохраняем её в файл на той ФС, которую мы нашли на предыдущем шаге. Имя файла в формате 8.3 соберем из текущей даты и времени, так меньше шанс, что один скриншот перепишет другой.

Для работы не хватает lodepng_encode32 и уже упоминавшейся выше ShowStatus, продолжим.

Кодируем изображение в формат PNG

Выводим статус


Осталось написать функцию ShowStatus и наш драйвер готов. Получать этот самый статус можно разными способами, например, выводить числа от 0x00 до 0xFF в CPU IO-порт 80h, который подключен к POST-кодеру, но есть он далеко не у всех, а на ноутбуках — вообще не встречается. Можно пищать спикером, но это, во-первых, платформо-зависимо, а во-вторых — дико бесит уже после пары скриншотов. Можно мигать лампочками на клавиатуре, это дополнительное задание для читателя, а мы будем показывать статус работы с графической консолью прямо через эту графическую консоль — отображая маленький квадрат нужного цвета в левом верхнем углу экрана. При этом белый квадрат будет означать «драйвер успешно загружен», желтый — «ФС с возможностью записи не найдена», синий — «Скриншот текущей консоли полностью черный, сохранять нет смысла», красный — «произошла ошибка» и, наконец, зеленый — «скриншот снят и сохранен». Выводить это квадрат нужно на все консоли, а после короткого времени восстанавливать тот кусочек изображения, который им был затерт.

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

Тестируем результат в UEFI Shell



Этот скриншот, как и все последующие, снят нашим драйвером, поэтому квадрата в углу на нем не видно.

Драйверы DCH для Windows — это пакеты драйверов, которые будут устанавливаться и работать на универсальной платформе Windows (UWP) на базе редакций Windows® 10 и Windows 11*.

  • После завершения установки драйвера графической системы приложение будет автоматически загружено Центр управления графикой Intel® и установлено Центр управления графикой Intel® из Microsoft Store (требуется подключение к Интернету).
  • Если загрузка не возможна или она будет неисправна, ее можно загрузить из Microsoft Store в любое время. Инструкции см. в разделе Как установить Центр управления графикой Intel®.

Intel и Microsoft работали совместно, чтобы сделать этот переход как можно более плавным. Таким образом, вы не должны ощутить никаких влияний на ваши повседневные впечатления.

Графические контроллеры Intel, поддерживаемые драйверами DCH

Процессоры Intel® Core 6-го поколения или процессоры новее поддерживаются драйверами DCH для Windows. Если вы хотите знать, какое поколение вашего процессора, см. раздел Как определить поколение процессоров Intel® Core™ процессоров.

  • Драйвер dc (Declarative, Componentized) (DC): Это означает, что драйвер устанавливается с помощью только декларативных директив INF, и специальные OEM-настройки драйвера отделены от базового пакета драйверов. Набор Intel® Graphics Control Panel по-прежнему поставляется вместе с драйвером. Драйверы DC также известны как устаревшие драйверы.
  • Драйвер поддержки приложений DCH (Declarative, Componentized and Hardware): Драйверы DCH упакованы и устанавливаются по-разному, чем драйверы DC. В отличие от драйверов DC, Intel® Graphics Control Panel больше не поставляется в комплекте с драйверами DCH. Он должен быть загружен и установлен отдельно из Microsoft Store. Эти драйверы предназначены для работы с Windows® 10.

Корпорация Intel рекомендует получить драйвер графической системы непосредственно у производителя вашего компьютера. Ваш OEM-производитель обязан опубликовать драйвер DCH на своих веб-сайтах и с помощью обновлений Windows.

Однако вы все еще можете загрузить и установить драйверы из Центра загрузки или с помощью приложения Intel® Driver and Support Assistant.

Читайте также: