Подробный гайд: Как настроить последовательность загрузки драйверов dxe?

Подробный гайд по настройке порядка загрузки DXE-драйверов UEFI в EDK2: DEPEX, A Priori, протоколы, отладка, ошибки и практика.

2026.08.20                  


Подробный гайд: Как настроить последовательность загрузки драйверов dxe?Подробный гайд: Как настроить последовательность загрузки драйверов dxe? Гайд по настройке последовательности загрузки DXE-драйверов в UEFI. Основной акцент — на EDK2/собственную прошивку, потому что именно там порядок можно нормально проектировать. Если речь о готовом BIOS от AMI/Insyde/Phoenix, принципы те же, но правки обычно делаются через редактирование Firmware Volume, DEPEX и A Priori, часто вендорскими утилитами и с риском.


Как настроить последовательность загрузки DXE-драйверов

1. Главное правило



В UEFI/EDK2 порядок загрузки DXE-драйверов нельзя гарантировать просто перестановкой файлов в прошивке.

За запуск DXE-драйверов отвечает DXE Dispatcher. Он смотрит:

  1. Какие драйверы лежат в Firmware Volume, FV.
  2. Какие у них зависимости — DEPEX.
  3. Есть ли специальный список приоритетных драйверов — A Priori file.
  4. Какие протоколы уже установлены ранее запущенными драйверами.

Поэтому правильная настройка порядка — это почти всегда настройка зависимостей между драйверами.


2. Как работает DXE Dispatcher

Упрощённо процесс выглядит так:

  1. PEI передаёт управление в DXE Core.
  2. DXE Core находит Firmware Volume, обычно через HOB от PEI.
  3. DXE Dispatcher перебирает FFS-файлы драйверов внутри FV.
  4. Для каждого драйвера читается секция DXE_DEPEX.
  5. Если DEPEX истинен, драйвер может быть загружен и запущен.
  6. Если драйвер устанавливает новые протоколы, это может разблокировать другие драйверы.
  7. Dispatcher повторяет проходы, пока есть драйверы, которые можно запустить.

То есть порядок определяется не «физическим» расположением драйвера в FV, а тем, какие протоколы уже доступны и что написано в DEPEX.


3. Основные механизмы управления порядком

Есть несколько способов:

Способ Когда использовать Надёжность
DEPEX через протокол Основной правильный способ Высокая
A Priori DXE Нужно гарантировать ранний запуск группы драйверов Средняя/высокая, но не заменяет DEPEX
BEFORE / AFTER в DEPEX Редкие специальные случаи Низкая/средняя, легко получить неочевидное поведение
Protocol Notify Зависимость опциональная или нужна отложенная инициализация Высокая для runtime-логики
Ручная загрузка через BDS/Shell Драйверы грузятся как UEFI-образы после основной DXE-фазы Полностью ручное управление

4. Самый надёжный способ: зависимость через протокол

Если вам нужно, чтобы драйвер B запускался строго после драйвера A, сделайте так:

  1. Драйвер A устанавливает протокол, например gMyDriverAReadyProtocolGuid.
  2. Драйвер 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

Но на практике я рекомендую использовать их очень осторожно.

Причины:

  1. Поведение менее очевидно, чем у прямой протокольной зависимости.
  2. Легко получить конфликт порядка.
  3. Не всегда понятно, к какому именно драйверу или протоколу применяется ограничение.
  4. В реальных платформах 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:

  1. Нужно извлечь Firmware Volume.
  2. Найти нужный DXE-драйвер как FFS-файл.
  3. Посмотреть его DXE_DEPEX.
  4. При необходимости изменить DEPEX или добавить A Priori.
  5. Пересобрать FV.
  6. Учесть подпись, Secure Boot, Capsule, OEM-проверки.


Инструменты могут быть разные:

  • UEFITool для анализа;
  • AMI-утилиты для модулей AMI;
  • vendor-инструменты для Insyde/Phoenix;
  • собственные скрипты для FFS/FV.

Но это уже рискованная область: неправильный порядок DXE-драйверов может привести к тому, что система не инициализирует устройства, не выйдет в BDS или не загрузится.


18. Рекомендуемая стратегия

Если вы разрабатываете или сопровождаете собственную EDK2-прошивку, я бы рекомендовал такой подход:

  1. Не пытайтесь управлять порядком только A Priori.
  2. Для каждого важного порядка создавайте явный протокол.
  3. Используйте [Depex] в INF.
  4. Для ранних критичных драйверов добавляйте их в A Priori DXE.
  5. Для опциональных зависимостей используйте RegisterProtocolNotify.
  6. Логируйте вход в каждый драйвер через DEBUG.
  7. Проверяйте итоговый FV и serial-лог.
  8. Избегайте 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 + установка протоколов

Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.


Статью подготовил: Аверко Денис Сергеевич @Nymexis г. Омск (специалист по ЗИ)

Комментарии

Загрузка...
Если комментарии не загружаются, можете попробовать отключить блокировщик рекламы для этого сайта