Skip navigation

Category Archives: UEFI

Скучно без работы. Прочитал такую вещь в UEFI спеке в разделе с базовыми типами:

EFI_GUID

128-bit buffer containing a unique identifier value. Unless otherwise specified, aligned on a 64-bit boundary.

В сурсах EDK тип объявлен так:

///
/// 128 bit buffer containing a unique identifier value.
/// Unless otherwise specified, aligned on a 64 bit boundary.
///
typedef struct {
 UINT32 Data1;
 UINT16 Data2;
 UINT16 Data3;
 UINT8 Data4[8];
} GUID;
typedef GUID EFI_GUID;

Т.е. если исключить алаисинг и касты случаных невыровненных адресов в указатель на EFI_GUID, то дефолтное выравнивание типа получаем по его первому члену, т.е. по 4 байта.

Если EFI_GUID выделить на куче, то допустим что в качестве аллокатора в EFI среде используется EFI_BOOT_SERVICES::AllocatePool. Про него в спецификации написано, что “All allocations are eight-byte aligned”. Вопрос закрыт.

Если EFI_GUID аллоцировать на стеке на amd64, то там выравнивание стека соблюдет требование автоматически. На IA32 этого может и не быть. С секцией данных история примерно такая же как и со стеком.

Получается что требование спецификации в EDK не соблюдено и тут возникает вопрос, а что будет то если оно не соблюдено? Зачем они вообще привязали этот тип к такому базовому выравниванию? У меня есть одна теория: на IA64 невыровненный доступ был очень чувствительным. Возможно они хотели выровнять гуид чтобы можно было быстро их сравнивать парой инструкций ручным ассемблером.

 

До того как Intel открыл TianoCore и вокруг него развился EDK был EFI toolkit:
http://sourceforge.net/p/efi-toolkit/code/HEAD/tree/trunk/efi-toolkit/

Гораздо более легковесный проект чем EDK, содержит EFI спеку в хедерах, порты libc, libm, libsocket, libz, реализацию TCP/IP стека и тонкую convinience wrapper library в виде libefi. Причем порт libc, в отличии от EDK, зависим только от спеки и libefi. В общем то что мне и нужно было всегда, но есть минусы:

  1. С появлением EDK на проект забили, последние коммиты датируются концом 2006 года. Версия спецификации в хедерах 1.3.что-то.
  2. Нет GNU мейкфайлов, только nmake.

Я сделал себе бранч: https://github.com/warfish/uefi-toolkit
Пока планирую сделать сборку под юниксы, стряхнуть пыль и обновить хедера до UEFI spec 2.4.

EDK преследует архитектуру library classes, которая позволяет им обернуть самый безобидный интерфейс, типа ASSERT, в полиморфную библиотеку у которой есть один интерфейс и много реализаций. Конкретная “инстанциация” этого полиморфного интерфейса задается в их билд системе через DSC файл. Реализация C99 stdlib, EDK/StdLib, которую они взяли из Open/Net/Free BSD и жестко мутировали, работает также. Например memcmp у них реализована через BaseMemoryLib/CompareMem. Такая архитектура позволяет им задавать реализации библиотек для разных этапов загрузки системы (Pei, Dxe, Bbs, Runtime) и местами кажется очень мощной. Но не понятно зачем она нужна в stdlib если они ограничили линковку своей реализации только до EFI_APPLICATION, т.е. только до стадии boot loader-а. Если по-другому, то в результате из реализацию нельзя линковать с EFI драйвером например, хотя бы ради того же memcmp.

Еще один неприятный момент в их реализации – она зависит от EFI shell. В реализации stdio у них скорее всего встал вопрос, как интерпретировать файловые пути где-нибудь в fopen? Ответ такой – через шелл. Их реализация реализует (sic) NetBSD сисколлы через “драйвера устройств”, которые показывают на маппинги файловых систем в шелле. В результате проблема решена, но появляется жесткая зависимость от шелла. Эта зависимость явно отражена в реализации точки входа. Также шелл позволяет им ввести понятие аргументов командной строки.

Есть еще россыпь проблем поменьше: типа неготовность их хедеров к компиляции в плюсовом юните трансляции, переопределение флагов компиляции в inf файле, реализация realloc которая опирается на их же собственные приватные структуры кучи из реализации DxeCore, что делает опасным ее использование на реальном железе где куча реализована иначе. Ну и как и все остальное в EDK она непредназначена для линковки с проектами вне дерева EDK.

Все это заставляет задуматься, а зачем? Возьмем например memcmp. Нужно было _выкинуть_ реализацию чтобы заменить ее своей, через полиморфную либу. Можно было опереться на EFI_SIMPLE_FILE_SYSTEM в реализации fopen приняв например схему с эмуляцией дерева фс через гуиды разделов (/Volume{GUID}/…). Можно было заинвертировать зависимость опубликовав свой кастомный протокол, EDK_LIBC_ARGS например, инстанс которого клиент бы вешал на хендл слинкованного с StdLib приложения. Тоже самое можно было бы сделать и с stdio тоже. Под эту схему и шелл бы подошел. But alas.

И вот конструктивная часть поста. Мне интересно насколько трудоемкий вариант выдернуть реализацию StdLib из EDK и сделать ее зависимой только от UEFI spec. Может быть было бы проще начать с нуля, т.е. с BSD libc и портировать ее. Не знаю, но что-то сделать руки чешутся.

Речь пойдет об особенности PI спецификации – архитектурных протоколах, которые осуществляют портабельность DXE core и позволяют переносить всю реализацию DXE стадии на платформы эмуляции UEFI, такие как legacy BIOS (EDK/DuetPkg) и Win32 (EDK/Nt32Pkg).

Меня всегда интересовало, почему DuetPkg и Nt32Pkg, судя по их DSC файлам, по большей части состоят из бинарных модулей, ничего не знающих про эмуляцию. Наример оба эмулятора использую общую реализацию всего DXE рантайма. Как такое работает?

Согласно спецификации UEFI инициализация платформы и подготовка ее к загрузке ОС осуществляется в несколько стадий:

uefifig6[1]

Весь API, который описан в UEFI спецификации становится (полностью) доступен на стадии BDS (Boot Device Selection), когда начинает работать boot manager. Реализация этого API и его инициализация происходит, в общем случае, на стадии DXE – driver execution environment. На стадии DXE работаю несколько основных компонентов, которые нас интересуют в контексте темы поста:

  • DXE Core
    Ядро DXE стадии, получает управление после PEI, разворачивает реализацию базы данных хендлов.
  • DXE Dispatcher
    Занимается загрузкой драйверов из firmware volume, который был проинициализирован на стадии PEI.

Референсную реализацию DXE можно найти в EDK/MdeModulePkg/Core/Dxe.

DXE Core реализует EFI_SYSTEM_TABLE и все сервисы из EFI_BOOT_SERVICES и EFI_RUNTIME_SERVICES за счет опоры на EFI architecture protocols:

dxe+foundation+architectural[1]

Картинка выше перегружена деталями, но в центре находится реализация DXE, которая зависит от набора архитектурных протоколов таких как EFI_CPU_ARCH_PROTOCOL, EFI_TIMER_ARCH_PROTOCOL и так далее. Эти протоколы описаны в PI спецификации и немногим отличаются от протоколов из UEFI спецификации. Различия есть в драйверах, которые их реализуют: это обычные DXE boot service / DXE runtime service драйвера, однако т.к. опубликованные ими протоколы являются опорой для реализации основных boot и runtime сервисов, то они не могут рассчитывать на их полный набор на некоторых этапах своего выполнения.

Становится понятной роль архитектурных протоколов – они абстрагируют базовое железо конкретной архитектурной платформы и позволяют коду в DXE core опираться на эти абстракции в реализации основных сервисов, например:

  • Реализация рантайм сервиса EFI_RUNTIME_SERVICES::GetTime() опирается на архитектурный протокол EFI_REAL_TIME_CLOCK_ARCH_PROTOCOL для доступа к аппаратному устройству wall-time clock
  • Вся реализация ивентов в EFI_BOOT_SERVICES использует EFI_TIMER_ARCH_PROTOCOL для генерирования периодических прерываний по таймеру.
  • EFI_CPU_ARCH_PROTOCOL используется для синхронизации кешей процессора и реализации сервисов управления памятью

Драйверы, реализующие эти протоколы, как правило находятся в firmware volume, который инициализируется на стадии PEI и информация о котором передается в DXE Core посредством списка Hand-off Block структур (HOB list), что показано в верхней части картинки выше. Я не буду сейчас заострять внимание на деталях HOB списка, скажу только что это каждый HOB представляет из себя блок данных и GUID, который позволяет интерпретировать эти данные различными клиентами на стадии инициализации DXE. При помощи HOB передается информация о доступной памяти, memory mapped firmware volume и т.п.

Firmware volume был упомянут уже не раз, но его определение так и не было дано до сих пор. Firmware volume (FV) это структурированная база данных исполняемых модулей DXE, т.е. драйверов и приложений. База данных FV адресует образы по GUIDу и хранит информацию о зависимостях между различными модулями, а так же т.н. a priori list – список GUIDов образов, которые нужно загрузить при инициализации DXE в строго определенной последовательности. Драйвера архитектурных протоколов как правило и находятся в a priori list. Физически на реальной системе FV находятся в ROM и доступ к нему предоставляется через замапленый диапазон адресов физический памяти. Маппинг осуществляется на стадии PEI и информация о нем передается в HOB листе.

Таким образом при старте DXE код может опираться только на рабочую физическую память, проинициализированную на стадии PEI и описанную в HOB листе. Этого достаточно  для инициализации базы данных хендлов, доступа к FV, загрузке драйверов из FV посредством DXE Dispatcher и инициализацию всех UEFI сервисов.

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

  • Реализация всех архитектурных протоколов, на который опирается DXE Core
  • Реализация firmware volume, доступа к нему и загрузку бинарных образов в среду эмуляции.
  • Реализация дополнительных драйверов, таких как block io, GOP, консолей и т.п.
  • Формарование корректного HOB листа и передача управления в DXE.

В этом и заключается полезная нагрузка DuetPkg и Nt32Pkg. К примеру Nt32Pkg реализует архитектурные протоколы и драйвера основных UEFI протоколов через сервисы Win32 и хранит FV как папку на файловой системе предоставляя доступ через FvbServiceRuntimeDxe драйвер. Реализацию аналогичных компонентов можно найти и в DuetPkg, хотя там все сложнее из-за специфики эмулятора UEFI поверх legacy BIOS, но общая схема остается неизменной.

EFI (нативно и эмулируемо) дает интерфейс GOP, который поддерживает BitBlt и предлагает реализовывать GUI поверх него.  Предполагается, что BitBlt это все что нужно для этого. Я поискал в интернетах открытые и проприетарные GUI тулкиты для EFI, для embedded и для linux framebuffer, потому что последний достаточно близко эмулируется поверх GOP. Вот что удалось откапать.

 

C/PEG, PEG+, PEG Pro

http://www.swellsoftware.com/products/cpeg.php

Проприетарное решение от SwellSoftware, ссылка ведет на C/PEG, рядом лежат PEG+ и PEG Pro, которые стоят дороже, но дают больше функционала. В пдфке стоит поддержка x86 VBE и linux framebuffer. К сожалению evaluation kit они хотят слать физической почтой и требуют указать американский штат, поэтому посмотреть в жизни не удается. Тем не менее решение выглядит интересно, хотя цена на лицензирование только по прямому запросу в отдел продаж.

 

FLTK

http://www.fltk.org/index.php

Fast Light Tool Kit. Достаточно взрослый и стабильный проект, быстрый, легковесный, умеет рендерить шрифты и содержит достаточно неплохой набор виджетов. Лицензия GPL v2 с небольшими дополнениями, а именно http://www.fltk.org/COPYING.php:

Static linking of applications and widgets to the FLTK library does not constitute a derivative work and does not require the author to provide source code for the application or widget, use the shared FLTK libraries, or link their applications or widgets against a user-supplied version of FLTK.

If you link the application or widget to a modified version of FLTK, then the changes to FLTK must be provided under the terms of the LGPL in sections 1, 2, and 4.

Т.е. позволяет линковаться статически и не требует раскрытия кода клиентского приложения.

Проблема с FLTK в том, что он опирается на Carbon/GDI/X11, что существенно усложняет портирование.

 

Microwindows

http://www.microwindows.org/

Еще один распространенный в embedded среде тулкит. Пристально пока не смотрел, но радует вот это утверждение с главной страницы:

All drivers are endian-neutral with only Read/DrawPixel, DrawV/Hline and Blit entry points.

Т.е. реализация опирается на архитектуру графического драйвера, которому нужны DrawPixel, Draw[V/H]Line и Blit. Опять же, пристально не вглядывался, но такой вариант достаточно портируемый. Лицензирован под MPL. Архитектуру можно почитать здесь, советую обратить внимание на раздел Screen Driver

Embedded Qt

http://qt-project.org/doc/qt-4.8/qt-embedded-linux.html

Встраиваемая версия Qt работающая поверх linux framebuffer. Очень интересный вариант. Что такое Qt думаю пояснять не стоит, а вот про то, что такое фреймбуфер можно почитать здесь.

 

Итого

Есть еще несколько слишком абстрактных вариантов, типа Tcl\Tk порт, но наиболее интересными мне кажутся варианты с Microwindows и Embedded Qt.

Прототипирование UEFI эмуляции на базе EDK/Duet можно считать законченным. Я считаю результат умеренным успехом. Успехом потому что такой подход действительно позволяет поставить знак равенства между Legacy BIOS и UEFI системами на том уровне поддержки аппаратуры, что нам нужен. А умеренным потому что, несмотря на знак равенства, возможности самого UEFI, будь то native или эмуляция, оставляют за нами массу прикладной работы по поддержке локализации, ввода национальных символов и двух факторной авторизации. Глупо было ожидать обратного. 🙂

Если сконцентрироваться на хорошем, то UEFI, по сравнению с “голым” PBA дает:

  • Защиту памяти и полную адресацию. Коррапты памяти раньше происходили тихо и незаметно. Теперь такие баги генерируют GPF.
  • Поддержку современных тулчейнов, включая тот, что используется для остального проекта. Использование MSVC 1.52 можно свести к минимуму, а значит и его баги тоже (а они есть).
  • Графический фреймбуфер и рендер растровых юникодных шрифтов.
  • Работающий USB и PCI стек.
  • Достаточный контроль состояния аппаратуры.
  • Модульность, взаимозаменяемость драйверов через повсеместную абстракцию протоколами. Это очень важно для эмуляции, потому что дает возможность заменить реализацию например графического дисплея на свою, работающую через BIOS.
  • Поддержка source-level отладки.
  • Стеки сетевых протоколов.
  • libc

Из “умеренного”:

  • Поддержка ввода национальных символов совсем базовая. Раскладки нужно генерировать самостоятельно. Если для европейских языков все терпимо, то для азиатских и арабских выглядит довольно сложно, хотя и не невозможно.
  • Драйвера крипто токенов. Их либо нет, либо они в непонятно каком состоянии в опенсорсных либах, либо нужно начинать говорить с вендорами токенов.
  • Нет полноценного GUI фреймворка “из коробки”. Есть все необходимые базовые технологические стеки для его реализации/портирования, типа фреймбуфера, драйверов устройств ввода и т.д, но нет самих примитивов, из которых строится GUI.

Если ссумировать, то UEFI предоставляет всю базу, чтобы реализовать прикладные задачи, но не больше этого. Реализовать все остальное можно используя нормальный компилятор, отладчик, защиту памяти, снятие множества ограничений по ресурсам и т.д. EDK/Duet выравнивает BIOS до этого же уровня, что экономит наверное год работы и дает унифицирование платформы на поддерживаемых аппаратных конфигурациях. Но при этом все, что выходит за рамки базовой платформы, нужно делать/портировать почти самостоятельно, и, хочу еще раз подчеркнуть, это касается UEFI в целом, а не только его эмуляции. Приятное исключение это рендер шрифтов 🙂

From http://www.libusb.org/wiki/libusb-1.0

libusb-1.0 includes a platform abstraction layer allowing for cross-platform compatibility. Linux, Darwin (Mac OS X), Windows, OpenBSD and NetBSD are supported in the latest release.

FreeBSD 8 and above include a FreeBSD-specific reimplementation of the libusb-1.0 API, so your applications will probably work there too. The source code for this library can be found ​here.

If you are interested in porting to other platforms, the PORTING file tells you where to start. We are more than happy to help out here, please write to the mailing list with your questions and feedback.

From PORTING:

Implementation-wise, the basic idea is that you provide an interface to
libusb’s internal “backend” API, which performs the appropriate operations on
your target platform.
In terms of USB I/O, your backend provides functionality to submit
asynchronous transfers (synchronous transfers are implemented in the higher
layers, based on the async interface). Your backend must also provide
functionality to cancel those transfers.
Your backend must also provide an event handling function to “reap” ongoing
transfers and process their results.
The backend must also provide standard functions for other USB operations,
e.g. setting configuration, obtaining descriptors, etc.
Existing libusb ports are found in libusb/os folder
libusbi.h contains a very well documented interface that should be implemented on a new platform: usbi_os_backend

Чтобы портировать связку libopensc/libopenct нужно:

  1. Затащить StdLib. В EDK есть реализация: http://sourceforge.net/p/tianocore/edk2/ci/master/tree/StdLib/. Есть небольшое неудобство – ванильная реализация StdLib в EDK линкуется только с UEFI_APPLICATION (а не DXE_DRIVER). Поэтому PBA _везде_ должен быть аппликейшеном. Либо можно распилить StdLib на части и копилировать сишники. Тоже вариант.
  2. Написать реализацию функций ifd_sysdep_usb_* в OpenCT. Вот пример порта на libusb/Linux: https://github.com/OpenSC/openct/blob/master/src/ifd/sys-linux.c.
  3. Вытравить зависимости от файловой системы в вспомогательном коде OpenCT. Например у него есть конфиг, который он при инициализации парсит из файла и т.д.
  4. Выкинуть все “ненужное”. Например общение с токенами по серийному порту.

Вместо пункта 2 (порт ifd_sysdep_usb_*) можно подойти к вопросу фундаментальнее – портировать libusb. Эта либа своего рода стандартный API доступа к USB устройствам на линуксе. На нее портировано много всего, включая OpenCT. Если портануть ее, то это дает гибкость, можно не портировать USB стек второй раз в какой-нибудь другой либе. Но это медленнее, потому что libusb больше.

Следующая цель прототипирования: локализованный ввод и вывод. Мы хотим, чтобы пользователь мог ввести свой логин и пароль на своем родном языке и чтобы мы могли локализовать ему пребут. Это означает, что нам нужна поддержка:

  • Юникодных строк и их рендеринга на графический дисплей.
  • Клавиатурных раскладок с маппингом на юникодные символы.

Что на данный момент удалось получить от EDK/Duet.

Сначала о выводе юникода на экран. Вообще весь UEFI и EDK юникодные (UTF16). Для вывода юникодных строк на дисплей UEFI предоставляет описание шрифта: http://wiki.phoenix.com/wiki/index.php/EFI_HII_SIMPLE_FONT_PACKAGE_HDR. Это по сути таблица маппинга UTF16 кода символа на глиф размером либо 9×19 либо 16×19 пикселей. EDK предоставляет драйвер графической консоли, который учитывает наборы этих шрифтов и рендерит каждый символ в UTF16 строке согласно ее глифу, учитывая переносы строк, слияние границ символов и т.д. И это достаточно много очень противного кода.

Модель для разработчика получается такая: чтобы поддержать печать текста например на русском языке надо изготовить таблицу глифов для каждого UTF16 символа этого языка, загрузить ее в рантайме и автомагически вся печать на дисплей будет работать. Есть также более продвинутое описание шрифтов, не привязанное к размеру глифа: http://wiki.phoenix.com/wiki/index.php/EFI_HII_FONT_PACKAGE_HDR.

Никто не запрещает поступить как grub2 – сделать большой шрифт со всеми нужными языками в одном пакете и грузить его целиком. Никто не запрещает взять векторный шрифт и отрендерить его в нужно размере получив таким образом растр. В общем с этой стороны поддержку можно считать достаточной.

Теперь о вводе. UEFI предоставляет в том же пакете HII (Human Interface Infrastructure) еще один тип ресурсов – клавиатурные раскладки: http://wiki.phoenix.com/wiki/index.php/EFI_HII_KEYBOARD_LAYOUT. Раскладка это таблица маппинга скан кода на UTF16 код символа + флаги: http://wiki.phoenix.com/wiki/index.php/EFI_KEY_DESCRIPTOR. Здесь маппинг уже не такой прямой как в случае со шрифтами. В поле атрибутов можно указать ряд флагов, а клавиатурный драйвер будет их учитывать и изменять свое внутреннее состояние в зависимости от нажатой клавиши. Например через флаги EFI_NS_KEY_MODIFIER и EFI_NS_KEY_DEPENDENCY_MODIFIER реализуется поддержка “мертвых клавиш”.

Модель для разработчика выглядит примерно так же как и в случае со шрифтами: чтобы поддержать ввод символов например на русском языке нужно изготовить таблицу дескрипторов и загрузить ее в рантайме. Но как оказалось тут кроется подстава. В EDK две реализации клавиатурного драйвера: UsbKbDxe и Ps2KeyboardDxe. Первый – драйвер USB клавиатуры, а второй – PS2, работающий через Intel 8042 контроллер. Как оказалось раскладки поддерживает только драйвер USB клавиатуры. Соответвенно для PS2 клавиатур нужно портировать код обработки раскладок.

Ситуацию усложняет еще и так называемый Lеgacy USB. Это стандартная фича USB контроллеров и биосов когда USB клавиатура эмулируется как PS/2 устройство через Intel 8042. Т.е. в большинстве случаев даже если в машину воткнута клавиатура через настоящий USB порт, то биос видит ее как PS/2 клавиатуру. Это можно исправить в ту или другую сторону, но проще не становится.

Итого. При условии что у нас есть драйвер PS/2 клавиатуры, который умеет работать с раскладками, то получается такой псевдокод добавление поддержки национальных символов:

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

Для меня это не выглядит страшным, особенно если учесть, что можно избавится от первого шага сгенерировав “большой” юникодный шрифт один раз. Основная доля работы в раскладках и это будет точно не бесплатно, не из коробки, как в случае с линуксом например.

Мне удалось за один рабочий день корявенько локализовать ввод и вывод на русский язык. Я сгенерировал кривой шрифт и нарисовал раскладку, пока не уперся в то, что VmWare эмулирует клавиатуру как PS/2 устройство, драйвер которого в EDK не понимает раскладки. Но если вынести проблему с драйвером за скобки, то получается примерно такие трудозатраты для относительно простого (и главное знакомого) языка.

This content is password protected. To view it please enter your password below: