Как стать автором
Поиск
Написать публикацию
Обновить
45.35
OpenYard
OpenYard – производитель серверного оборудования

OYBoot: как мы написали свой UEFI-загрузчик под Xeon IceLake

Уровень сложностиСредний
Время на прочтение8 мин
Количество просмотров1.7K

Создать собственный UEFI-загрузчик для серверной платформы на Intel Xeon IceLake без исходников, полной документации и официальной поддержки ― звучит как приключение. Мы в OpenYard решились на этот шаг, чтобы получить полный контроль над прошивкой, безопасность на уровне железа и независимость от вендоров. В статье наш путь: от первых проб с edk2 и FSP до релиза OYBoot, с реверсом драйверов, интеграцией BMC и борьбой за стабильный старт платформы.

Идея о разработке своего UEFI-загрузчика прорабатывалась достаточно стремительно. Во время обсуждений рассматривались все преимущества, недостатки, вопросы и потенциальные сложности этого мероприятия. В какой-то момент времени количество недостатков и вопросов примерно уравновесили преимущества. 

Действительно, на разработку жизнеспособного загрузчика необходимо немало времени (ибо платформа Intel Xeon сложна), достаточно приличный штат разработчиков и доступ к необходимому набору документации от Intel (гайды, мануалы, описания регистров и т.д). Разумеется, ничего из перечисленного под рукой не оказалось, но был некоторый опыт в bring-up новых материнских плат, разработке UEFI-модулей и драйверов. А вот чего мы ждали: возможность контролировать функциональный состав прошивок, обеспечивать непрерывную и стабильную поддержку пользователей, гарантировать в полном объеме информационную безопасность наших аппаратных платформ. Ну и, конечно же, нельзя не упомянуть о престиже OpenYard, как разработчика и производителя серверной электроники.

Разумеется, в силу отсутствия проприетарных исходных кодов на актуальное на тот момент поколение Xeon Scalable (IceLake) единственным нашим инструментом стал edk2-framework. В качестве шаблона для проекта мы использовали также доступные opensource репозитории edk2-platforms и самый важный Intel-FSP, который предоставляет разработчикам бинарные образы FSP (Intel Firmware Support Package), являющиеся неотъемлемой частью Intel-based платформ (серверных, пользовательских, мобильных). И вот на этом этапе возник первый серьезный вопрос: как разобраться с настройками этого бинарного модуля, не имея в доступе полного набора документации от Intel, а тем более какой-либо технической поддержки. В распоряжении команды был только FSP2.0 Integration Guide и набор хедеров, приложенных в репозитории Intel-FSP для целевого процессора. Стоит отметить, что код хедера FSP сделан максимально читабельным и с неплохим набором пояснений и комментариев, что несколько компенсирует сложности в разработке без доступа к документации.

/** FSP-M Configuration

**/

typedef struct {

 

/** Offset 0x0040 - Customer Revision

  The Customer can set this revision string for their own purpose.

**/

  UINT8                       CustomerRevision[32];

 

/** Offset 0x0060 - Bus Ratio

  Indicates the ratio of Bus/MMIOL/IO resource to be allocated for each CPU's IIO.

  Default 0x1

**/

  UINT8                       BusRatio[8];

 

/** Offset 0x0068 - D2K Credit Config

  Set the D2K Credit Config. 1: Min,<b>2: Med (Default), 3: Max.

  1:Min, 2:Med, 3:Max

**/

  UINT8                       D2KCreditConfig;

 

/** Offset 0x0069 - Snoop Throttle Config

  Set the Snoop Throttle Config. <b>0: Disable(Default)</b>, 1: Min, 2: Med, 3: Max

  0:Disable, 1:Min, 2:Med, 3:Max

**/

  UINT8                       SnoopThrottleConfig;

 

/** Offset 0x006A - Snoop Throttle Config

  Set the Snoop All Core Config. <b>0: Disable(Default)</b>, 1: Enable, 2: Auto

  0:Disable, 1:Enable, 2:Auto

**/

  UINT8                       SnoopAllCores;

 

/** Offset 0x006B - Legacy VGA Socket

  Socket that claims the legacy VGA range. Default: Socket 0

**/

  UINT8                       LegacyVgaSoc;

 

/** Offset 0x006C - Legacy VGA Stack

  Stack that claims the legacy VGA range. Default: Stack 0

Первой важной задачей во всей этой истории была корректная и достаточная конфигурация и интеграция FSP в наш проект. Здесь стоит сказать, что мы пошли по самому простому алгоритму: внесение изменений в программный код конфигурации FSP, сборка нашего бинарного образа UEFI, запуск на целевой платформе и контроль состояния загрузки в отладочной консоли. По большому счету такой алгоритм называется «метод проб и ошибок». Количество итераций, которые мы провели для успешного перехода хотя бы в UEFI-payload назвать страшно. Наибольшее время мы затратили на поиск оптимальных настроек FSP для корректного исполнения MRC (Memory Reference Code). Результатом этого этапа стала загрузка нашей системы в оболочку Efi (Efi shell)… Но только в отладочной консоли. Как позже выяснилось, у нас не работала видеоконсоль в силу отсутствия в нашей сборке корректного Efi-драйвера.

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

В части доработки необходимых драйверов команде пришлось осваивать технологии реверс-инжиниринга. Далеко не для всех драйверов было желание (да и возможность разрабатывать код самим). Как пример UEFI GOP ― драйвер: команда вытащила этот драйвер из уже имеющейся прошивки от нашего ODM партнера. А для правильной интеграции полученного бинарника в нашу сборку мы разработали код по примеру, взятому из одного из старых проприетарных проектов.

Пока одна часть команды боролась с созданием минимального работоспособного загрузчика, вторая часть разрабатывала функциональные модули, такие как: функции управления портами USB, PCIe и SATA платформы, устройствами шифрования TPM2.0, модуль настройки порта менеджмента (BMC).

Все это необходимо было завернуть в привычный и user-friendly интерфейс, который пользователям более знаком, как Setup Utility. Получилось так:

Отдельно отмечу задачи, связанные с обязательным функционалом взаимодействия нашего OYBoot с BMC (Baseboard Management Controller) по интерфейсу IPMI, а также трансляции полной SMBIOS-таблицы с целью обеспечения инвенторики оборудования. Тут сложность оказалась в том, что SMBIOS опять же надо было передавать в «черный ящик» под названием AMI MegaRAC. На помощь нам пришел уже повышенный опыт реверс-инжиниринга. После непродолжительного исследования команда точно знала все о механизме транспортировки SMBIOS-таблицы в пространство памяти BMC, а также почти все о структуре разделов этой таблицы. Разумеется, помимо стандартных типов (информация о системе, процессорах и памяти) нам пришлось создавать OEM-разделы, со структурами, понятными MegaRAC.

EFI_STATUS

EFIAPI

GetSmbiosTable(VOID** SmbiosTableAddress, UINT32* SmbiosTableSize)

{

  EFI_STATUS Status = EFI_NOT_FOUND;

 

  //

  // Get SMBIOS table from System Configure table

  //

  Status = EfiGetSystemConfigurationTable(&gEfiSmbiosTableGuid, (VOID**)&mSmbiosTable);

 

  if ((mSmbiosTable != NULL) && (!EFI_ERROR(Status))) {

    SmbiosTableAddress = (UINT8)(UINTN)(mSmbiosTable->TableAddress);

    *SmbiosTableSize = (UINT32)mSmbiosTable->TableLength;

    return EFI_SUCCESS;

  }

 

  //

  // Get SMBIOS table 3 from System Configure table

  //

  Status = EfiGetSystemConfigurationTable(&gEfiSmbios3TableGuid, (VOID**)&mSmbios64BitTable);

 

  if ((mSmbios64BitTable != NULL) && (!EFI_ERROR(Status))) {

    SmbiosTableAddress = (UINT8)(UINTN)(mSmbios64BitTable->TableAddress);

    *SmbiosTableSize = (UINT32)mSmbios64BitTable->TableMaximumSize;

    return EFI_SUCCESS;

  }

 

  return Status;

}

 

VOID

EFIAPI

ReadyToBootNotify(

  EFI_EVENT Event,

  VOID* Context)

{

  EFI_STATUS Status;

  EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop;

 

  VOID* SmbiosTableAddress = NULL;

  UINT32 SmbiosTableSize = 0x00;

 

  UINT8 Response[0x10];

  UINT8 ResponseSize = 0x10;

  

  Status = gBS->LocateProtocol(

    &gEfiGraphicsOutputProtocolGuid,

    NULL,

    (VOID**)&Gop

      );

  if (EFI_ERROR(Status)) {

    return;

  }   

 

  VOID* Destination = (VOID*)(UINTN)Gop->Mode->FrameBufferBase;

 

  Status = GetSmbiosTable(&SmbiosTableAddress, &SmbiosTableSize);

 

  DEBUG((DEBUG_INFO, "GetSmbiosTable, Status = %r\n", Status));

 

  if (!EFI_ERROR(Status))

  {

 

    DEBUG((DEBUG_INFO, "Address = 0x%p, Size = 0x%x bytes\n", SmbiosTableAddress, SmbiosTableSize));

 

    CopyMem(Destination, SmbiosTableAddress, SmbiosTableSize);

  }

  else

  {

    return;

  }

 

  ZeroMem(Response, 0x10);

 

  UINT8 IpmiCommandData1[] = IPMI_COMMAND_DATA_GET_SHARED_MEMORY_ADDRESS;

  Status = IpmiSendCommand(

    IPMI_COMMAND_GET_SHARED_MEMORY_ADDRESS,

    IpmiCommandData1,

    sizeof(IpmiCommandData1),

    Response,

    &ResponseSize

  );

 

  DEBUG((DEBUG_INFO, "IpmiSendCommand: Get shared memory address, Status = %r\n", Status));

 

  if (!EFI_ERROR(Status))

  {

    ZeroMem(Response, 0x10);

    ResponseSize = 0x0A;

 

    UINT8 IpmiCommandData2[] = IPMI_COMMAND_DATA_DATA_READY;

    Status = IpmiSendCommand(

      IPMI_COMMAND_DATA_READY,

      IpmiCommandData2,

      sizeof(IpmiCommandData2),

      Response,

      &ResponseSize

    );

 

    DEBUG((DEBUG_INFO, "IpmiSendCommand: Smbios data ready, Status = %r\n", Status));

  }

 

  gBS->Stall(1  1000  1000);

}

Исходный код выше реализует разработанную нами процедуру автоматического определения адреса в пространстве памяти BMC для последующей передачи SMBIOS. 

Полгода назад у нас не было ничего, кроме идеи, опыта bring-up и пары утилит с открытым кодом. Сегодня у нас есть OYBoot ― собственный UEFI-загрузчик, полностью адаптированный под наши серверы на Xeon IceLake, с поддержкой всех необходимых функций и глубокой интеграцией в инфраструктуру. Но ещё важнее ― мы получили бесценный опыт работы с FSP, освоили тонкости edk2, научились реверсить драйверы и наладили командную работу в условиях ограниченных ресурсов. Для нас это доказательство того, что даже в мире закрытых платформ можно добиться полного контроля над железом ― если есть упорство, любопытство и готовность к экспериментам.

Теги:
Хабы:
+21
Комментарии2

Публикации

Информация

Сайт
openyard.ru
Дата регистрации
Дата основания
2021
Численность
201–500 человек
Представитель
Лиза Усачева