После двух твитов, оставленных на прошлой неделе, про мои игры с UEFI и Rust, несколько человек попросили опубликовать заметку, объясняющую как создать UEFI-приложение, полностью написанное на Расте и продемонстрировать тестовое окружение.
Так что сегодняшняя цель — это создание UEFI-приложения на Расте, которое распечатывает карту памяти, отфильтрованную по доступности для использования (такая память называется традиционной памятью в описании UEFI-спецификаций):

Однако прежде, чем приступить к работе, освежим некоторые понятия.
При включении компьютера аппаратная часть находится в неопределённом состоянии и необходимо выполнить некоторую инициализацию для того, чтобы подготовить систему к предстоящей работе. BIOS, акроним для Basic Input/Output System, появившийся в районе 1975 года и использовавшийся с тех пор, был способом проведения аппаратной инициализации во время процесса загрузки и предоставления сервисов времени выполнения для ОС и программ. Однако BIOS имеет некоторые ограничения и после 40 лет применения заменён на Unified Extensible Firmware Interface (или UEFI для краткости). UEFI нацелен на устранение технических недостатков BIOS.
UEFI — это спецификация, которая определяет программный интерфейс между ОС\UEFI-приложением и прошивкой платформы. Intel разработала изначальную Extensible Firmware Interface (EFI), работы над которой были закончены в июле 2005 года. В начале 2006 года Apple одной из первых внедрила технологию на своих Intel Macintosh. В том же самом 2005 году выход UEFI сделал устаревшим EFI 1.10 — последний выпуск EFI. UEFI форум — это индустриальный орган, который управляет UEFI-спецификациями. Интерфейс, определяемый этими спецификациями, включает таблицы данных, которые содержат информацию о платформе, сервисы времени загрузки и выполнения, которые доступны приложению\загрузчику ОС. Такая прошивка имеет ряд преимуществ перед традиционным BIOS:
Также UEFI предоставляет более продвинутую функциональность для того, чтобы реализовать загрузчик или UEFI-приложение без необходимости глубоких знаний архитектуры.
Как говорилось в начале, Раст будет использован для написания UEFI-приложения. Для тех, кто не знает, что это такое: Раст — системный язык программирования, разработку которого спонсирует Mozilla. Она описывает его как «безопасный, конкурентный, практичный язык», поддерживающий функциональную и императивно-процедурную парадигмы. Язык очень похож на Си++ в плане синтаксиса, но создатели Раста намереваются обеспечить в нём лучшую безопасность по памяти при сохранении производительности.
ЯП явился результатом персонального проекта сотрудника Mozilla Грейдона Хоара. Организация стала поддерживать проект в 2009 году, после осознания его потенциала. В 2010 году было публично объявлено о проекте; в том же самом году компилятор, изначально разработанный на OCaml, начали переписывать на Расте с использованием LLVM-backend.
Первая пре-альфа версия компилятора появилась в январе 2012 года, но уже через 3 года, 15 мая 2015 была выпущена первая стабильная версия (теперь известная как редакция 2015). Раст является проектом с открытым сообществом. Такая модель означает, что любой может вкладываться в разработку и в уточнение языка, и этот вклад может быть разным, например, улучшение документации, отправка баг-репортов, предложения RFC на добавление функциональности или изменения программного кода. Язык получил огромную обратную связь по опыту разработки Серво — современного движка для обозревателей с превосходной производительностью и возможностью встроенного применения. В наши дни Раст начинает присутствовать во всех сферах ПО, к примеру, в ПО для управления спутниками, программировании микроконтроллеров, веб-серверов, в обозревателе Firefox и т.д. Раст выигрывал первое место в номинации «наиболее любимый язык программирования» в опросе Stack Overflow Developer в 2016, 2017 и 2018 годах (прим. переводчика — и в 2021).
Для того чтобы написать загрузчик, гипервизор или низкоуровневое приложение требуется использовать системный язык программирования. Есть отличная статья с подробным обсуждением этого понятия. Проще говоря, системный ЯП — это язык, позволяющий тонкий контроль над исполнением кода в машине и возможностью изменения любых отдельных байтов в памяти компьютера. И с Растом это возможно.
Во избежание необходимости описывать все UEFI-таблицы будет использован крейт
Наконец, для тестового окружени�� будут использованы Питон и QEMU вкупе с OVMF. QEMU — это хорошо известный полносистемный эмулятор, позволяющий запускать код для любой машины на любой поддерживаемой архитектуре. OVMF — это основанный на EDK II проект, предоставляющий поддержку UEFI для виртуальных машин (QEMU и KVM). QEMU не содержит в поставке OVMF, так что придётся установить его отдельно на вашу машину, либо взять предсобранные образы из Сети.
Например, такие доступны для загрузки в моём тестовом хранилище.
Без дальнейших промедлений приступаем к работе! Первым делом создадим папку и инициализируем Раст проект в ней:
Теперь добавим
Если сейчас запустить
Следующий шаг состоит в создании файла целевых параметров и сценария на Питоне для облегчения сборки и запуска UEFI-приложения. В основном, параметры цели описывают выходной двоичный файл, порядок байтов («endianess»), архитектуру, двоичную структуру и функциональности, которые можно использовать при компиляции. Этот файл будет использован
Таким образом, сперва нам необходимо «сказать» карго, чтобы он включил
И затем добавим крейт в качестве зависимости, чтобы
Далее создадим файл x86_64-none-efi.json со следующим содержимым:
Исполняемый UEFI-файл не что иное, как двоичный формат PE, используемый Windows, но со специальной подсистемой и без таблицы символов; поэтому целевое семейство установлено как windows.
Сейчас нужно создать build.py, реализующий две команды:
Первым шагом нужно заставить грузиться приложение и войти в бесконечный цикл, предупреждая выход в прошивку.
В Расте ошибки могут быть доведены до паники или аварийного прекращения. Паника случается, когда что-то идёт не так, но в целом можно продолжить работу (такое обычно случается с потоками); аварийное завершение происходит, когда программа переходит в состояние, из которого невозможно восстановление. Наличие обработчика паники обязательно, он реализуется в стандартной библиотеке; но поскольку приложение не зависит от ОС, то и стд не может быть использована. Вместо этого мы используем core часть библиотеки, в которой обработчик отсутствует, так что мы вынуждены реализовывать его самостоятельно. К счастью,
Если вы подметили, то в файле целевых параметров указана передача пары аргументов
Первые две строки обозначают, что наш крейт не имеет функции main и не зависит от стд. Также точка входа помечена аттрибутом entry.
Наконец, после сборки и запуска приложения, QEMU отобразит что-то похожее на картину ниже:
QEMU исполняет UEFI-приложение
Ничего интересного, но т.к. QEMU не перешла в цикл загрузки или выскочила в EFI-оболочку, убеждаемся, что наше приложение вызвано. Следующий шаг заключается в том, чтобы напечатать версию UEFI на экран. Опять же, в
Для доступа к макросу info! нужно добавить новую зависимость в Cargo.toml:
Затем необходимо просто добавить следующий код в главную функцию, перед входом в бесконечный цикл:
После сборки и запуска приложение выведет что-то вроде INFO: UEFI 2.70. Эта информация зависит от версии прошивки, которую вы используете.
В завершение давайте напишем функцию, которая принимает ссылку на таблицу Boot Services и распечатывает регионы свободной для использования памяти. Сперва нам потребуется включить крейт alloc, чтобы получить доступ к структуре Vec; для этого нужно добавить следующие три строки в начало файла:
После этого определим константу с размером EFI-страницы, который равен 4KiB независимо от системы.
И, собственно, реализуем непосредственно функцию по обходу карты в поисках традиционной памяти и распечатке свободных диапазонов на экран:
Конечный результат должен совпадать с выводом, изображённым на КДПВ.

И готово! было несложно, правда? Теперь вы можете продолжить реализовывать новые возможности в приложении, вероятно решившись разрабатывать загрузчик или более сложное UEFI приложение.
И ещё одна важная ремарка для отважных духом. Если вы пустились в разработку своей собственной ОС или углубились в изучение технологии, то вы должны отложить в сторону все API, предоставляемые UEFI для взаимодействия с файловой системой, сетью, доступом к PCI-устройствам и т.д., и разработать свои собственные драйвера.
Не ленитесь от использования всех этих предоставленных абстракций!

Так что сегодняшняя цель — это создание UEFI-приложения на Расте, которое распечатывает карту памяти, отфильтрованную по доступности для использования (такая память называется традиционной памятью в описании UEFI-спецификаций):

Однако прежде, чем приступить к работе, освежим некоторые понятия.
▍ Скомканное вступление
При включении компьютера аппаратная часть находится в неопределённом состоянии и необходимо выполнить некоторую инициализацию для того, чтобы подготовить систему к предстоящей работе. BIOS, акроним для Basic Input/Output System, появившийся в районе 1975 года и использовавшийся с тех пор, был способом проведения аппаратной инициализации во время процесса загрузки и предоставления сервисов времени выполнения для ОС и программ. Однако BIOS имеет некоторые ограничения и после 40 лет применения заменён на Unified Extensible Firmware Interface (или UEFI для краткости). UEFI нацелен на устранение технических недостатков BIOS.
UEFI — это спецификация, которая определяет программный интерфейс между ОС\UEFI-приложением и прошивкой платформы. Intel разработала изначальную Extensible Firmware Interface (EFI), работы над которой были закончены в июле 2005 года. В начале 2006 года Apple одной из первых внедрила технологию на своих Intel Macintosh. В том же самом 2005 году выход UEFI сделал устаревшим EFI 1.10 — последний выпуск EFI. UEFI форум — это индустриальный орган, который управляет UEFI-спецификациями. Интерфейс, определяемый этими спецификациями, включает таблицы данных, которые содержат информацию о платформе, сервисы времени загрузки и выполнения, которые доступны приложению\загрузчику ОС. Такая прошивка имеет ряд преимуществ перед традиционным BIOS:
- возможность ��спользования более вместительных накопителей при помощи GUID Partition table (GPT)
- независимая от CPU архитектура
- независимые от CPU драйверы
- гибкое пре-ОС окружение, включая сетевые возможности
- модульная архитектура
- совместимость назад и вперёд
Также UEFI предоставляет более продвинутую функциональность для того, чтобы реализовать загрузчик или UEFI-приложение без необходимости глубоких знаний архитектуры.
▍ Окисление — это хорошо
Как говорилось в начале, Раст будет использован для написания UEFI-приложения. Для тех, кто не знает, что это такое: Раст — системный язык программирования, разработку которого спонсирует Mozilla. Она описывает его как «безопасный, конкурентный, практичный язык», поддерживающий функциональную и императивно-процедурную парадигмы. Язык очень похож на Си++ в плане синтаксиса, но создатели Раста намереваются обеспечить в нём лучшую безопасность по памяти при сохранении производительности.
ЯП явился результатом персонального проекта сотрудника Mozilla Грейдона Хоара. Организация стала поддерживать проект в 2009 году, после осознания его потенциала. В 2010 году было публично объявлено о проекте; в том же самом году компилятор, изначально разработанный на OCaml, начали переписывать на Расте с использованием LLVM-backend.
Первая пре-альфа версия компилятора появилась в январе 2012 года, но уже через 3 года, 15 мая 2015 была выпущена первая стабильная версия (теперь известная как редакция 2015). Раст является проектом с открытым сообществом. Такая модель означает, что любой может вкладываться в разработку и в уточнение языка, и этот вклад может быть разным, например, улучшение документации, отправка баг-репортов, предложения RFC на добавление функциональности или изменения программного кода. Язык получил огромную обратную связь по опыту разработки Серво — современного движка для обозревателей с превосходной производительностью и возможностью встроенного применения. В наши дни Раст начинает присутствовать во всех сферах ПО, к примеру, в ПО для управления спутниками, программировании микроконтроллеров, веб-серверов, в обозревателе Firefox и т.д. Раст выигрывал первое место в номинации «наиболее любимый язык программирования» в опросе Stack Overflow Developer в 2016, 2017 и 2018 годах (прим. переводчика — и в 2021).
▍ Ещё два или три момента перед началом
Для того чтобы написать загрузчик, гипервизор или низкоуровневое приложение требуется использовать системный язык программирования. Есть отличная статья с подробным обсуждением этого понятия. Проще говоря, системный ЯП — это язык, позволяющий тонкий контроль над исполнением кода в машине и возможностью изменения любых отдельных байтов в памяти компьютера. И с Растом это возможно.
Во избежание необходимости описывать все UEFI-таблицы будет использован крейт
uefi-rs. Этот крейт облегчает создание UEFI-приложений на Расте. Миссия uefi-rs в том, чтобы предоставить безопасные и производительные обёртки вокруг UEFI-интерфейсов и позволить разработчикам писать идиоматичный Раст-код.Наконец, для тестового окружени�� будут использованы Питон и QEMU вкупе с OVMF. QEMU — это хорошо известный полносистемный эмулятор, позволяющий запускать код для любой машины на любой поддерживаемой архитектуре. OVMF — это основанный на EDK II проект, предоставляющий поддержку UEFI для виртуальных машин (QEMU и KVM). QEMU не содержит в поставке OVMF, так что придётся установить его отдельно на вашу машину, либо взять предсобранные образы из Сети.
Например, такие доступны для загрузки в моём тестовом хранилище.
▍ Начинаем
Без дальнейших промедлений приступаем к работе! Первым делом создадим папку и инициализируем Раст проект в ней:
> mkdir uefi-app && cd uefi-app > cargo init
Теперь добавим
uefi-rs в качестве зависимости. Чтобы сделать это, просто добавьте следующие строки в ваш Cargo.toml:uefi = "0.12.0" uefi-services = "0.9.0"
Если сейчас запустить
cargo run, то Карго соберёт uefi-rs вместе с нашим приложением.▍ Рабочий процесс сборки\запуска
Следующий шаг состоит в создании файла целевых параметров и сценария на Питоне для облегчения сборки и запуска UEFI-приложения. В основном, параметры цели описывают выходной двоичный файл, порядок байтов («endianess»), архитектуру, двоичную структуру и функциональности, которые можно использовать при компиляции. Этот файл будет использован
build-std, функциональность Карго для производства крейта core (это основная часть стандартной библиотеки Раста, но без зависимостей, даже от системных библиотек и libc), собранного для другой платформы.Таким образом, сперва нам необходимо «сказать» карго, чтобы он включил
build-std, через создание файла .cargo/config:[unstable] build-std = ["core", "compiler_builtins", "alloc"]
Замечание: чтобы это работало нужно установить ночную сборку Раста так же как и компонентФункциональностьrust-src. Это можно сделать при помощи rustup:rustup component add rust-src --toolchain nightly.
mem из compiler-builtins не включается автоматически во время сборки с использованием Cargo-функциональности build-std. Таким образом, мы должны вручную добавить поддержку функций по работе с памятью прописав следующее в файл Cargo.toml:rlibc = "1.0.0"
И затем добавим крейт в качестве зависимости, чтобы
mem* функции были связаны:extern crate rlibc;
Далее создадим файл x86_64-none-efi.json со следующим содержимым:
{ "llvm-target": "x86_64-pc-windows-gnu", "env": "gnu", "target-family": "windows", "target-endian": "little", "target-pointer-width": "64", "target-c-int-width": "32", "os": "uefi", "arch": "x86_64", "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", "linker": "rust-lld", "linker-flavor": "lld-link", "pre-link-args": { "lld-link": [ "/Subsystem:EFI_Application", "/Entry:uefi_start" ] }, "panic-strategy": "abort", "default-hidden-visibility": true, "executables": true, "position-independent-executables": true, "exe-suffix": ".efi", "is-like-windows": true, "emit-debug-gdb-scripts": false }
прим. переводчика
По правде говоря, на текущий момент уже нет необходимости в создании такого файла. Поддержку uefi влили — PR/56769.
Я решил всё же переводить статью в оригинальном виде.
Вообще, сейчас полезно сразу читать uefi-rs/BUILDING.md.
Также добавлю ещё одну хорошую, на мой взгляд, ссылку — blog.timhutt/std-embedded-rust. Благодаря ей сформировалась команда сборки —
Я решил всё же переводить статью в оригинальном виде.
Вообще, сейчас полезно сразу читать uefi-rs/BUILDING.md.
Также добавлю ещё одну хорошую, на мой взгляд, ссылку — blog.timhutt/std-embedded-rust. Благодаря ей сформировалась команда сборки —
cargo +nightly build -Z build-std=std,panic_abort --target x86_64-unknown-uefi.Исполняемый UEFI-файл не что иное, как двоичный формат PE, используемый Windows, но со специальной подсистемой и без таблицы символов; поэтому целевое семейство установлено как windows.
Сейчас нужно создать build.py, реализующий две команды:
build: эта команда собирает UEFI-приложениеrun: запускает собранное приложение в QEMU.
#!/usr/bin/env python3 import argparse import os import shutil import sys import subprocess as sp from pathlib import Path ARCH = "x86_64" TARGET = ARCH + "-none-efi" CONFIG = "debug" QEMU = "qemu-system-" + ARCH WORKSPACE_DIR = Path(__file__).resolve().parents[0] BUILD_DIR = WORKSPACE_DIR / "build" CARGO_BUILD_DIR = WORKSPACE_DIR / "target" / TARGET / CONFIG OVMF_FW = WORKSPACE_DIR / "OVMF_CODE.fd" OVMF_VARS = WORKSPACE_DIR / "OVMF_VARS-1024x768.fd" def run_build(*flags): "Run Cargo-<tool> with the given arguments" cmd = ["cargo", "build", "--target", TARGET, *flags] sp.run(cmd).check_returncode() def build_command(): "Builds UEFI application" run_build("--package", "uefi-app") # Create build folder boot_dir = BUILD_DIR / "EFI" / "BOOT" boot_dir.mkdir(parents=True, exist_ok=True) # Copy the build EFI application to the build directory built_file = CARGO_BUILD_DIR / "uefi-app.efi" output_file = boot_dir / "BootX64.efi" shutil.copy2(built_file, output_file) # Write a startup script to make UEFI Shell load into # the application automatically startup_file = open(BUILD_DIR / "startup.nsh", "w") startup_file.write("\EFI\BOOT\BOOTX64.EFI") startup_file.close() def run_command(): "Run the application in QEMU" qemu_flags = [ # Disable default devices # QEMU by default enables a ton of devices which slow down boot. "-nodefaults", # Use a standard VGA for graphics "-vga", "std", # Use a modern machine, with acceleration if possible. "-machine", "q35,accel=kvm:tcg", # Allocate some memory "-m", "128M", # Set up OVMF "-drive", f"if=pflash,format=raw,readonly,file={OVMF_FW}", "-drive", f"if=pflash,format=raw,file={OVMF_VARS}", # Mount a local directory as a FAT partition "-drive", f"format=raw,file=fat:rw:{BUILD_DIR}", # Enable serial # # Connect the serial port to the host. OVMF is kind enough to connect # the UEFI stdout and stdin to that port too. "-serial", "stdio", # Setup monitor "-monitor", "vc:1024x768", ] sp.run([QEMU] + qemu_flags).check_returncode() def main(args): "Runs the user-requested actions" # Clear any Rust flags which might affect the build. os.environ["RUSTFLAGS"] = "" os.environ["RUST_TARGET_PATH"] = str(WORKSPACE_DIR) usage = "%(prog)s verb [options]" desc = "Build script for the UEFI App" parser = argparse.ArgumentParser(usage=usage, description=desc) subparsers = parser.add_subparsers(dest="verb") build_parser = subparsers.add_parser("build") run_parser = subparsers.add_parser("run") opts = parser.parse_args() if opts.verb == "build": build_command() elif opts.verb == "run": run_command() else: print(f"Unknown verb '{opts.verb}'") if __name__ == '__main__': sys.exit(main(sys.argv))
Заметка: я не нашёл, по какой причине исполняемый файл не загружается автоматически с этой версией OVMF, поэтому используется сценарий startup.nsh для облегчения загрузки.
▍ Само приложение
Первым шагом нужно заставить грузиться приложение и войти в бесконечный цикл, предупреждая выход в прошивку.
В Расте ошибки могут быть доведены до паники или аварийного прекращения. Паника случается, когда что-то идёт не так, но в целом можно продолжить работу (такое обычно случается с потоками); аварийное завершение происходит, когда программа переходит в состояние, из которого невозможно восстановление. Наличие обработчика паники обязательно, он реализуется в стандартной библиотеке; но поскольку приложение не зависит от ОС, то и стд не может быть использована. Вместо этого мы используем core часть библиотеки, в которой обработчик отсутствует, так что мы вынуждены реализовывать его самостоятельно. К счастью,
uefi-rs предоставляет одну реализацию оного.Если вы подметили, то в файле целевых параметров указана передача пары аргументов
lld (компоновщик LLVM), указывающие точку входа (uefi_start) и подсистему. Так, нам нужно отредактировать main.rs, чтобы импортировать uefi-rs крейт и определить функцию с именем uefi_start, содержащую бесконечный цикл:#![no_std] #![no_main] #![feature(asm)] #![feature(abi_efiapi)] extern crate uefi; extern crate uefi_services; use uefi::prelude::*; #[entry] fn efi_main(_image_handler: uefi::Handle, system_table: SystemTable<Boot>) -> Status { loop {} Status::SUCCESS }
Первые две строки обозначают, что наш крейт не имеет функции main и не зависит от стд. Также точка входа помечена аттрибутом entry.
Наконец, после сборки и запуска приложения, QEMU отобразит что-то похожее на картину ниже:

Ничего интересного, но т.к. QEMU не перешла в цикл загрузки или выскочила в EFI-оболочку, убеждаемся, что наше приложение вызвано. Следующий шаг заключается в том, чтобы напечатать версию UEFI на экран. Опять же, в
rust-rs уже реализованы вспомогательные функции для этого, поэтому достаточно проинициализировать систему логгирования и использовать макрос info! для распечатки текста на экране или даже на последовательном порту.Для доступа к макросу info! нужно добавить новую зависимость в Cargo.toml:
log = { version = "0.4.11", default-features = false }
Затем необходимо просто добавить следующий код в главную функцию, перед входом в бесконечный цикл:
uefi_services::init(&system_table).expect_success("Failed to initialize utils"); // reset console before doing anything else system_table .stdout() .reset(false) .expect_success("Failed to reset output buffer"); // Print out UEFI revision number { let rev = system_table.uefi_revision(); let (major, minor) = (rev.major(), rev.minor()); info!("UEFI {}.{}", major, minor); }
После сборки и запуска приложение выведет что-то вроде INFO: UEFI 2.70. Эта информация зависит от версии прошивки, которую вы используете.
прим. переводчика
Вот так запуск выглядит на моём стареньком Самсунг NP535U4C: 

В завершение давайте напишем функцию, которая принимает ссылку на таблицу Boot Services и распечатывает регионы свободной для использования памяти. Сперва нам потребуется включить крейт alloc, чтобы получить доступ к структуре Vec; для этого нужно добавить следующие три строки в начало файла:
#![feature(alloc)] // (...) extern crate alloc; // (...) use crate::alloc::vec::Vec;
После этого определим константу с размером EFI-страницы, который равен 4KiB независимо от системы.
const EFI_PAGE_SIZE: u64 = 0x1000;
И, собственно, реализуем непосредственно функцию по обходу карты в поисках традиционной памяти и распечатке свободных диапазонов на экран:
fn memory_map(bt: &BootServices) { // Get the estimated map size let map_size = bt.memory_map_size(); // Build a buffer bigger enough to handle the memory map let mut buffer = Vec::with_capacity(map_size); unsafe { buffer.set_len(map_size); } let (_k, desc_iter) = bt .memory_map(&mut buffer) .expect_success("Failed to retrieve UEFI memory map"); let descriptors = desc_iter.copied().collect::<Vec<_>>(); assert!(!descriptors.is_empty(), "Memory map is empty"); // Print out a list of all the usable memory we see in the memory map. // Don't print out everything, the memory map is probably pretty big // (e.g. OVMF under QEMU returns a map with nearly 50 entries here). info!("efi: usable memory ranges ({} total)", descriptors.len()); descriptors .iter() .for_each(|descriptor| match descriptor.ty { MemoryType::CONVENTIONAL => { let size = descriptor.page_count * EFI_PAGE_SIZE; let end_address = descriptor.phys_start + size; info!( "> {:#x} - {:#x} ({} KiB)", descriptor.phys_start, end_address, size ); } _ => {} }) } // (...) // Call this function inside main memory_map(&system_table.boot_services());
Конечный результат должен совпадать с выводом, изображённым на КДПВ.

И готово! было несложно, правда? Теперь вы можете продолжить реализовывать новые возможности в приложении, вероятно решившись разрабатывать загрузчик или более сложное UEFI приложение.
И ещё одна важная ремарка для отважных духом. Если вы пустились в разработку своей собственной ОС или углубились в изучение технологии, то вы должны отложить в сторону все API, предоставляемые UEFI для взаимодействия с файловой системой, сетью, доступом к PCI-устройствам и т.д., и разработать свои собственные драйвера.
Не ленитесь от использования всех этих предоставленных абстракций!

