Подробный гайд: Как настроить последовательность загрузки драйверов dxe?
Гайд по настройке последовательности загрузки DXE-драйверов в UEFI. Основной акцент — на EDK2/собственную прошивку, потому что именно там порядок можно нормально проектировать. Если речь о готовом BIOS от AMI/Insyde/Phoenix, принципы те же, но правки обычно делаются через редактирование Firmware Volume, DEPEX и A Priori, часто вендорскими утилитами и с риском.
Как настроить последовательность загрузки DXE-драйверов
1. Главное правило
В UEFI/EDK2 порядок загрузки DXE-драйверов нельзя гарантировать просто перестановкой файлов в прошивке.
За запуск DXE-драйверов отвечает DXE Dispatcher. Он смотрит:
- Какие драйверы лежат в Firmware Volume, FV.
- Какие у них зависимости — DEPEX.
- Есть ли специальный список приоритетных драйверов — A Priori file.
- Какие протоколы уже установлены ранее запущенными драйверами.
Поэтому правильная настройка порядка — это почти всегда настройка зависимостей между драйверами.
2. Как работает DXE Dispatcher
Упрощённо процесс выглядит так:
- PEI передаёт управление в DXE Core.
- DXE Core находит Firmware Volume, обычно через HOB от PEI.
- DXE Dispatcher перебирает FFS-файлы драйверов внутри FV.
- Для каждого драйвера читается секция
DXE_DEPEX. - Если DEPEX истинен, драйвер может быть загружен и запущен.
- Если драйвер устанавливает новые протоколы, это может разблокировать другие драйверы.
- Dispatcher повторяет проходы, пока есть драйверы, которые можно запустить.
То есть порядок определяется не «физическим» расположением драйвера в FV, а тем, какие протоколы уже доступны и что написано в DEPEX.
3. Основные механизмы управления порядком
Есть несколько способов:
| Способ | Когда использовать | Надёжность |
|---|---|---|
| DEPEX через протокол | Основной правильный способ | Высокая |
| A Priori DXE | Нужно гарантировать ранний запуск группы драйверов | Средняя/высокая, но не заменяет DEPEX |
| BEFORE / AFTER в DEPEX | Редкие специальные случаи | Низкая/средняя, легко получить неочевидное поведение |
| Protocol Notify | Зависимость опциональная или нужна отложенная инициализация | Высокая для runtime-логики |
| Ручная загрузка через BDS/Shell | Драйверы грузятся как UEFI-образы после основной DXE-фазы | Полностью ручное управление |
4. Самый надёжный способ: зависимость через протокол
Если вам нужно, чтобы драйвер B запускался строго после драйвера A, сделайте так:
- Драйвер A устанавливает протокол, например
gMyDriverAReadyProtocolGuid. - Драйвер B в своём DEPEX требует этот протокол.
Тогда DXE Dispatcher не запустит B, пока A не установит протокол. Это самый чистый и предсказуемый метод.
4.1. Пример задачи
Нужно, чтобы порядок был такой:
MyDriverA
MyDriverB
MyDriverC
Тогда можно сделать цепочку:
MyDriverA устанавливает ProtocolA
MyDriverB зависит от ProtocolA и устанавливает ProtocolB
MyDriverC зависит от ProtocolB
Результат:
MyDriverA -> MyDriverB -> MyDriverC
5. Практический пример в EDK2
Допустим, у вас есть два драйвера:
MyPkg/Universal/MyDriverA/MyDriverA.inf
MyPkg/Universal/MyDriverB/MyDriverB.inf
Нужно, чтобы MyDriverB загружался только после MyDriverA.
5.1. Объявите GUID протокола в DEC-файле
Например, в MyPkg/MyPkg.dec:
[Protocols]
gMyDriverAReadyProtocolGuid = { 0x12345678, 0x9abc, 0xdef0, { 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0 } }
Этот GUID будет использоваться как маркер: драйвер A готов.
5.2. Драйвер A устанавливает протокол
В MyDriverA.inf желательно указать:
[Packages]
MdePkg/MdePkg.dec
MyPkg/MyPkg.dec
[LibraryClasses]
UefiDriverEntryPoint
UefiBootServicesTableLib
DebugLib
[Protocols]
gMyDriverAReadyProtocolGuid ## PRODUCES
В коде драйвера A, например MyDriverA.c:
#include <PiDxe.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Protocol/MyDriverAReady.h>
STATIC UINT8 mMyDriverAReadyMarker;
EFI_STATUS
EFIAPI
MyDriverAEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
EFI_HANDLE Handle;
DEBUG ((DEBUG_INFO, "[ORDER] MyDriverA entry\n"));
//
// Здесь ваша инициализация драйвера A.
//
Handle = NULL;
Status = gBS->InstallProtocolInterface (
&Handle,
&gMyDriverAReadyProtocolGuid,
EFI_NATIVE_INTERFACE,
&mMyDriverAReadyMarker
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "[ORDER] MyDriverA failed to install ready protocol: %r\n", Status));
return Status;
}
DEBUG ((DEBUG_INFO, "[ORDER] MyDriverA ready protocol installed\n"));
return EFI_SUCCESS;
}
Важно:
Протокол должен устанавливаться до выхода из entry point, если вы хотите, чтобы зависимые драйверы могли запуститься сразу после него.
5.3. Драйвер B требует протокол через DEPEX
В MyDriverB.inf:
[Packages]
MdePkg/MdePkg.dec
MyPkg/MyPkg.dec
[LibraryClasses]
UefiDriverEntryPoint
UefiBootServicesTableLib
DebugLib
[Protocols]
gMyDriverAReadyProtocolGuid ## CONSUMES
[Depex]
gMyDriverAReadyProtocolGuid
Теперь DXE Dispatcher не запустит MyDriverB, пока протокол gMyDriverAReadyProtocolGuid не будет установлен.
В коде драйвера B можно дополнительно проверить протокол:
#include <PiDxe.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Protocol/MyDriverAReady.h>
EFI_STATUS
EFIAPI
MyDriverBEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
VOID *Marker;
DEBUG ((DEBUG_INFO, "[ORDER] MyDriverB entry\n"));
Status = gBS->LocateProtocol (
&gMyDriverAReadyProtocolGuid,
NULL,
&Marker
);
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "[ORDER] MyDriverB cannot find MyDriverA ready protocol: %r\n", Status));
return Status;
}
//
// Здесь ваша инициализация драйвера B.
//
return EFI_SUCCESS;
}
Если протокол используется только для порядка, сам интерфейс может быть фиктивным. Если драйверу B реально нужны функции драйвера A, делайте полноценный протокол с функциями и данными.
6. Варианты DEPEX
Секция [Depex] в INF может содержать логические выражения.
6.1. Один протокол
[Depex]
gMyDriverAReadyProtocolGuid
Драйвер запустится только после установки этого протокола.
6.2. Несколько протоколов одновременно
[Depex]
gMyDriverAReadyProtocolGuid AND gEfiDevicePathProtocolGuid
Драйвер запустится только если оба протокола доступны.
6.3. Любой из протоколов
[Depex]
gMyDriverAReadyProtocolGuid OR gMyDriverXReadyProtocolGuid
Драйвер может запуститься, если доступен хотя бы один из протоколов.
6.4. Всегда запускать
[Depex]
TRUE
Драйвер может быть запущен в любой момент, когда до него дойдёт очередь. Порядок в этом случае не гарантируется.
6.5. Никогда не запускать
[Depex]
FALSE
Иногда используется для временного отключения драйвера.
7. A Priori DXE: приоритетная группа драйверов
Если вам нужно, чтобы определённые драйверы пытались загрузиться раньше остальных, используется A Priori file. В EDK2 это обычно описывается в FDF-файле платформы.
7.1. Что делает A Priori
A Priori — это специальный файл внутри FV, который содержит список GUID драйверов. DXE Dispatcher сначала пытается обработать драйверы из A Priori, а затем остальные.
Но важно:
- A Priori не отменяет DEPEX.
- Если у драйвера из A Priori зависимости ещё не выполнены, он не запустится.
- A Priori не является абсолютной гарантией полного порядка всей системы.
- Это хороший способ задать приоритет для критичных ранних драйверов.
7.2. Пример в FDF
В разных платформах EDK2 синтаксис может немного отличаться. Чаще всего встречается блок APRIORI DXE внутри нужного FV.
Пример:
[FV.FvMain]
FvAlignment = 16
APRIORI DXE {
INF MdeModulePkg/Universal/PCD/Dxe/Pcd.inf
INF MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf
INF MyPkg/Universal/MyEarlyDriver/MyEarlyDriver.inf
}
INF MdeModulePkg/Core/Dxe/DxeCore.inf
INF MdeModulePkg/Universal/PCD/Dxe/Pcd.inf
INF MdeModulePkg/Universal/DevicePathDxe/DevicePathDxe.inf
INF MyPkg/Universal/MyEarlyDriver/MyEarlyDriver.inf
Суть:
PcdDxeчасто нужен очень рано, потому что многие драйверы используют PCD.DevicePathDxeтоже часто нужен рано.- Ваши критичные драйверы можно добавить в A Priori, чтобы dispatcher пробовал их раньше.
В некоторых FDF-файлах используется отдельная секция [APRIORI]. Это зависит от платформы и версии инструментов. Смотрите, как уже сделано в конкретной платформе.
7.3. Важная ошибка с A Priori
Некоторые думают:
Добавлю драйвер в A Priori — и он гарантированно загрузится первым. Это не так. Если у драйвера DEPEX требует протокол, который ещё не установлен, драйвер не запустится, даже если он находится в A Priori. Поэтому A Priori нужно использовать вместе с правильными DEPEX.
8. BEFORE и AFTER в DEPEX
Спецификация UEFI PI поддерживает операторы BEFORE и AFTER в dependency expression.
Например, концептуально можно задать:
AFTER gSomeProtocolGuid
или
BEFORE gSomeProtocolGuid
Но на практике я рекомендую использовать их очень осторожно.
Причины:
- Поведение менее очевидно, чем у прямой протокольной зависимости.
- Легко получить конфликт порядка.
- Не всегда понятно, к какому именно драйверу или протоколу применяется ограничение.
- В реальных платформах EDK2 чаще выживают схемы с обычными протокольными зависимостями.
Если вам нужен детерминированный порядок, лучше сделать маркерный протокол.
9. Если зависимость опциональная: Protocol Notify
Иногда нельзя жёстко писать:
[Depex]
gSomeProtocolGuid
Потому что драйвер должен загрузиться, даже если протокол появится позже или может появиться не всегда. В таком случае используют RegisterProtocolNotify.
Пример логики:
#include <PiDxe.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DebugLib.h>
#include <Protocol/MyDriverAReady.h>
STATIC EFI_EVENT mMyDriverAReadyEvent;
STATIC VOID *mMyDriverAReadyRegistration;
VOID
EFIAPI
MyDriverAReadyNotify (
IN EFI_EVENT Event,
IN VOID *Context
)
{
EFI_STATUS Status;
VOID *Protocol;
Status = gBS->LocateProtocol (
&gMyDriverAReadyProtocolGuid,
NULL,
&Protocol
);
if (EFI_ERROR (Status)) {
return;
}
DEBUG ((DEBUG_INFO, "[ORDER] MyDriverB sees MyDriverA ready protocol\n"));
//
// Здесь можно продолжить инициализацию.
//
gBS->CloseEvent (Event);
}
EFI_STATUS
EFIAPI
MyDriverBEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
EFI_STATUS Status;
DEBUG ((DEBUG_INFO, "[ORDER] MyDriverB entry, waiting for MyDriverA\n"));
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL,
TPL_CALLBACK,
MyDriverAReadyNotify,
NULL,
&mMyDriverAReadyEvent
);
if (EFI_ERROR (Status)) {
return Status;
}
Status = gBS->RegisterProtocolNotify (
&gMyDriverAReadyProtocolGuid,
mMyDriverAReadyEvent,
&mMyDriverAReadyRegistration
);
return Status;
}
Такой подход не меняет порядок загрузки самого драйвера B, но позволяет правильно отложить его инициализацию до появления нужного протокола. Это часто лучше, чем жёсткий DEPEX, если драйвер может работать не сразу.
10. Ручная загрузка драйверов через BDS или UEFI Shell
Если речь не о встроенных DXE-драйверах из FV, а о загрузке .efi-драйверов уже после основной DXE-фазы, порядок можно задавать вручную.
Например, через UEFI Shell:
fs0:
load MyDriverA.efi
load MyDriverB.efi
connect -r
Или через startup.nsh:
@echo -off
fs0:
load MyDriverA.efi
load MyDriverB.efi
connect -r
Важно:
LoadImage/StartImageобычно не проверяют FFS DEPEX так, как это делает DXE Dispatcher.- Если драйвер ожидает протоколы, которых ещё нет, он должен сам это обрабатывать.
- Такой способ подходит для опциональных UEFI-драйверов, но не является заменой правильной настройки встроенных DXE-драйверов.
11. DriverOrder и Driver
В UEFI есть переменные типа:
DriverOrder
Driver0000
Driver0001
Они могут использоваться BDS для загрузки драйверов как boot-option. Это относится не к базовому DXE Dispatcher, а к политике BDS и загрузке драйверов из load option.
Если вы видите в системе DriverOrder, обычно это касается:
- опциональных UEFI-драйверов;
- драйверов, загружаемых BDS;
- некоторых vendor-реализаций BIOS;
- случаев, когда драйверы грузятся как образы с диска или из специальных переменных.
Для классических встроенных DXE-драйверов основной механизм — DEPEX и A Priori.
12. Как посмотреть текущий порядок загрузки
12.1. Debug-лог через serial
Самый практичный способ — добавить в entry point каждого драйвера:
DEBUG ((DEBUG_INFO, "[ORDER] MyDriverA entry\n"));
DEBUG ((DEBUG_INFO, "[ORDER] MyDriverB entry\n"));
DEBUG ((DEBUG_INFO, "[ORDER] MyDriverC entry\n"));
Затем собрать DEBUG-прошивку и смотреть serial-лог.
В DSC обычно должны быть включены debug-библиотеки и PCD, например:
[LibraryClasses]
DebugLib|MdePkg/Library/BaseDebugLibSerialPort/BaseDebugLibSerialPort.inf
[PcdsFixedAtBuild]
gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
gEfiMdePkgTokenSpaceGuid.PcdDebugPrintErrorLevel|0x80000040
Конкретные значения зависят от платформы.
12.2. Отчёты сборки EDK2
После сборки смотрите:
Build/<Platform>/<Toolchain>/<Target>/FV/
Build/<Platform>/<Toolchain>/<Target>/X64/
Там можно найти:
- итоговый FV;
- map/report файлы;
- сгенерированные
.efi; - файлы зависимостей;
- логи сборки.
Полезно проверять, что ваш драйвер действительно попал в нужный FV.
12.3. UEFI Shell
Если система уже дошла до Shell, можно использовать:
drivers
dh
devices
devtree
Команды зависят от версии Shell и платформы. Но нужно понимать: если драйвер не запустился из-за DEPEX, в Shell вы его можете не увидеть.
13. Диагностика: драйвер не загружается
Если драйвер не стартует, проверяйте по шагам.
Шаг 1. Есть ли драйвер в FV?
Убедитесь, что INF добавлен в FDF или DSC/FDF вашей платформы.
Например, в FDF:
[FV.FvMain]
INF MyPkg/Universal/MyDriverB/MyDriverB.inf
Если драйвера нет в FV, он не будет загружен DXE Dispatcher.
Шаг 2. Какой у него DEPEX?
Проверьте INF:
[Depex]
gMyDriverAReadyProtocolGuid
Если зависимость не установлена, драйвер не запустится.
Шаг 3. Устанавливает ли предыдущий драйвер протокол?
Добавьте debug-печать:
DEBUG ((DEBUG_INFO, "[ORDER] DriverA installed protocol\n"));
И проверьте, появляется ли сообщение.
Шаг 4. Нет ли циклической зависимости?
Плохо:
DriverA depex -> ProtocolB
DriverB depex -> ProtocolA
Если никто не может стартовать первым, оба могут не загрузиться.
Шаг 5. Временно поставьте DEPEX TRUE
Для диагностики можно временно сделать:
[Depex]
TRUE
Если драйвер после этого стартует — проблема была в зависимостях. Но будьте осторожны: если драйвер внутри ожидает протоколы, которых нет, он может упасть.
14. Частые ошибки
Ошибка 1. Думать, что порядок в FV гарантирует порядок запуска
Нет. Порядок файлов в FV может влиять на перебор, но не гарантирует нужную последовательность. DXE Dispatcher опирается на DEPEX и доступность протоколов.
Ошибка 2. Использовать A Priori вместо DEPEX
A Priori только задаёт приоритет попытки запуска. Если зависимости не выполнены, драйвер не стартует.
Ошибка 3. Драйвер устанавливает протокол слишком поздно
Если протокол устанавливается после того, как зависимый драйвер уже мог запуститься, возможны гонки. Устанавливайте протокол в entry point до возврата успеха.
Ошибка 4. Драйвер B зависит от протокола, который драйвер A не устанавливает
Проверяйте GUID, имена пакетов, секции [Protocols], [Depex], [Packages].
Ошибка 5. Ручная загрузка через Shell и ожидание работы DEPEX
Если вы делаете:
load MyDriver.efi
то DEPEX из FFS обычно не используется так, как при dispatch из FV. Драйвер должен сам проверять окружение.
Ошибка 6. Пытаться управлять UEFI Driver Binding только через DEPEX
Если драйвер реализует UEFI Driver Model и подключается к устройствам через DriverBinding, порядок подключения может зависеть от BDS, enumeration и ConnectController. DEPEX управляет моментом загрузки/запуска образа, но не всегда управляет порядком подключения к конкретным устройствам.
15. Если нужен строгий порядок для N драйверов
Лучший шаблон — цепочка протоколов.
Пример:
DriverA:
Depex: TRUE
Produces: gDriverAReadyProtocolGuid
DriverB:
Depex: gDriverAReadyProtocolGuid
Produces: gDriverBReadyProtocolGuid
DriverC:
Depex: gDriverBReadyProtocolGuid
Produces: gDriverCReadyProtocolGuid
DriverD:
Depex: gDriverCReadyProtocolGuid
Тогда порядок будет:
DriverA -> DriverB -> DriverC -> DriverD
Если один из драйверов завершится ошибкой и не установит протокол, следующие не запустятся. Иногда это правильно. Если нужно продолжать загрузку без него — используйте events или ручную логику, а не жёсткий DEPEX.
16. Когда использовать маркерный протокол
Маркерный протокол полезен, если:
- драйвер A не имеет подходящего протокола, на который мог бы зависеть драйвер B;
- нужно просто обозначить момент инициализации;
- не хочется менять архитектуру драйвера;
- требуется минимально инвазивно изменить порядок.
Пример маркерного протокола:
//
// Пустой маркер.
//
Главное — не разыменовывать его как реальный интерфейс, если он фиктивный.
17. Если у вас готовый BIOS без исходников
Если речь о модификации готового UEFI BIOS, например AMI, Insyde, Phoenix:
- Нужно извлечь Firmware Volume.
- Найти нужный DXE-драйвер как FFS-файл.
- Посмотреть его
DXE_DEPEX. - При необходимости изменить DEPEX или добавить A Priori.
- Пересобрать FV.
- Учесть подпись, Secure Boot, Capsule, OEM-проверки.
Инструменты могут быть разные:
- UEFITool для анализа;
- AMI-утилиты для модулей AMI;
- vendor-инструменты для Insyde/Phoenix;
- собственные скрипты для FFS/FV.
Но это уже рискованная область: неправильный порядок DXE-драйверов может привести к тому, что система не инициализирует устройства, не выйдет в BDS или не загрузится.
18. Рекомендуемая стратегия
Если вы разрабатываете или сопровождаете собственную EDK2-прошивку, я бы рекомендовал такой подход:
- Не пытайтесь управлять порядком только A Priori.
- Для каждого важного порядка создавайте явный протокол.
- Используйте
[Depex]в INF. - Для ранних критичных драйверов добавляйте их в A Priori DXE.
- Для опциональных зависимостей используйте
RegisterProtocolNotify. - Логируйте вход в каждый драйвер через
DEBUG. - Проверяйте итоговый FV и serial-лог.
- Избегайте
BEFORE/AFTER, если можно сделать нормальную протокольную зависимость.
19. Краткая памятка
Если нужно:
Драйвер B после драйвера A
Сделайте:
DriverA: устанавливает ProtocolA
DriverB: Depex = ProtocolA
Группа драйверов должна пробоваться раньше остальных
Используйте:
APRIORI DXE
Драйвер должен ждать протокол, но не обязан из-за него блокироваться навсегда
Используйте:
RegisterProtocolNotify
Драйверы грузятся вручную из Shell/BDS
Используйте:
load DriverA.efi
load DriverB.efi
connect -r
или startup.nsh, DriverOrder, Driver####.
20. Итог
Последовательность загрузки DXE-драйверов в UEFI настраивается через комбинацию:
DEPEX + A Priori + установка протоколов
Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.