Easyboot поддерживает различные ядра с помощью плагинов. Но если подходящий плагин не найден, он возвращается к двоичным файлам ELF64 или PE32+ с упрощенным (не нужно ничего встраивать) вариантом протокола Multiboot2.
Это тот же протокол, который использует Simpleboot, все примеры ядер в этом репозитории также должны работать с Easyboot.
Вы можете использовать исходный заголовок multiboot2.h в репозитории GRUB или файл заголовка easyboot.h C/C++, чтобы упростить работу. используйте определения типов. Бинарный формат низкого уровня тот же, вы также можете использовать любые существующие библиотеки Multiboot2, даже с языками, отличными от C, например Rust (примечание: я никоим образом не связан с этими разработчиками, я просто искал «Rust Multiboot2», и это был первый результат).
[[TOC]]
На машинах с BIOS самый первый сектор диска загружается прошивкой по адресу 0:0x7C00, и управление передается ему. В этом секторе Easyboot имеет boot_x86.asm, который достаточно умен, чтобы найти и загрузить загрузчик 2-го этапа, а также настроить для него длинный режим. .
На машинах UEFI тот же самый файл второго этапа, называемый EFI/BOOT/BOOTX64.EFI, загружается непосредственно прошивкой. Исходный код этого загрузчика можно найти в loader_x86.c. Вот и все, Easyboot — это не GRUB и не syslinux, оба из которых требуют десятков и десятков системных файлов на диске. Здесь больше никаких файлов не требуется, только этот (плагины не являются обязательными, они не нужны для обеспечения совместимости с Multiboot2).
На Raspberry Pi загрузчик называется «KERNEL8.IMG», скомпилированный из loader_rpi.c.
Этот загрузчик очень тщательно написан для работы с несколькими конфигурациями. Он загружает таблицу разделения GUID с диска и ищет
«EFI System Partition». Найдя его, он ищет файл конфигурации easyboot/menu.cfg
в этом загрузочном разделе. После того, как выбран
вариант загрузки и известно имя файла ядра, загрузчик находит и загружает его.
Затем он автоматически определяет формат ядра и достаточно умен, чтобы интерпретировать информацию о разделах и сегментах о том, где и что загружать (он выполняет сопоставление памяти по требованию, когда это необходимо). Затем он настраивает подходящую среду в зависимости от обнаруженного протокола загрузки (Multiboot2 / Linux / и т. д., защищенный или длинный режим, аргументы ABI и т. д.). После того, как состояние машины станет четким и четко определенным, в качестве самого последнего действия загрузчик переходит к точке входа вашего ядра.
Все, что написано в спецификации Multiboot2 (на английском языке) о состоянии машины, за исключением регистров общего назначения. Easyboot передает два аргумента в точку входа вашего ядра в соответствии с SysV ABI и Microsoft fastcall ABI. Первый параметр — это магия, второй — адрес физической памяти, указывающий на список тегов информации о мультизагрузке (далее сокращенно MBI, см. ниже).
Мы также немного нарушаем протокол Multiboot2 для обработки ядер старших половин. Multiboot2 требует, чтобы память была сопоставлена с идентификаторами. Что ж, в случае Easyboot это верно лишь отчасти: мы лишь гарантируем, что вся физическая оперативная память обязательно сопоставлена с идентификаторами, как и ожидалось; однако некоторые регионы выше этого (в зависимости от заголовков программы ядра) могут быть еще доступны. Это не нарушает работу обычных ядер, совместимых с Multiboot2, которые не должны иметь доступ к памяти за пределами доступной физической оперативной памяти.
Ваше ядро загружается одинаково как в системах BIOS, так и в UEFI, а также в RPi, различия в прошивке - это просто «чужая проблема». Единственное, что увидит ваше ядро, — это содержит ли MBI тег системной таблицы EFI или нет. Чтобы упростить вашу жизнь, Easyboot также не генерирует тег карты памяти EFI (тип 17), он предоставляет только тег Карта памяти (тип 6) без разбора на всех платформах (в том числе и в системах UEFI). там карта памяти просто преобразуется для вас, поэтому вашему ядру приходится иметь дело только с одним типом тега списка памяти). Старые, устаревшие теги также опускаются и никогда не генерируются этим менеджером загрузки.
Ядро работает на уровне супервизора (кольцо 0 на x86, EL1 на ARM), возможно, на всех ядрах ЦП параллельно.
GDT не указан, но допустим. Стек установлен в первых 640k и растет вниз (но вы должны изменить это как можно скорее на любой стек, который вы считаете достойным). Когда включен SMP, все ядра имеют свои собственные стеки, а идентификатор ядра находится на вершине стека (но вы также можете получить идентификатор ядра обычным платформенно-специфическим способом, используя cpuid / mpidr / и т. д.).
Вам следует рассматривать IDT как неопределенный; IRQ, NMI и программные прерывания отключены. Фиктивные обработчики исключений настроены на отображение минимального дампа и остановку машины. На них следует полагаться только в том случае, если ваше ядро выйдет из строя до того, как вы сможете настроить свои собственные IDT и обработчики, желательно как можно скорее. В ARM vbar_el1 настроен на вызов одних и тех же фиктивных обработчиков исключений (хотя, конечно, они сбрасывают разные регистры).
Фреймбуфер также установлен по умолчанию. Вы можете изменить разрешение в конфигурации, но если оно не указано, фреймбуфер все равно будет настроен.
Важно никогда не возвращаться из ядра. Вы можете перезаписать в памяти любую часть загрузчика (как только закончите с тегами MBI), так что возвращаться просто некуда. "Der Mohr hat seine Schuldigkeit getan, der Mohr kann gehen."
На первый взгляд это не очевидно, но спецификация Multiboot2 на самом деле определяет два совершенно независимых набора тегов:
Первый набор должен быть встроен в ядро, совместимое с Multiboot2, и называется заголовком Multiboot2 образа ОС (раздел 3.1.2), следовательно, предоставляется ядром. Easyboot не учитывает эти теги и не анализирует их в вашем ядре. Вам просто не нужны какие-либо специальные магические данные, встроенные в файл ядра: Easyboot, достаточно заголовков ELF и PE.
Второй набор передается ядру динамически при загрузке, Easyboot использует только эти теги. Однако он не генерирует все, что указывает Multiboot2 (он просто опускает старые, устаревшие или устаревшие). Эти теги называются тегами MBI, см. Информация о загрузке (раздел 3.6).
ПРИМЕЧАНИЕ: Спецификация Multiboot2 для тегов MBI чертовски ошибочна. Ниже вы можете найти исправленную версию, которая соответствует заголовочному файлу multiboot2.h, который вы можете найти в исходном репозитории GRUB.
Первый параметр вашего ядра — это магический 0x36d76289 (в rax
, rcx
и rdi
). Вы можете найти теги MBI, используя второй
параметр (в rbx
, rdx
и rsi
). На платформе ARM магия находится в x0
, а адрес в x1
. На RISC-V и MIPS используются a0
и
a1
соответственно. Если и когда этот загрузчик переносится на другую архитектуру, то всегда должны использоваться регистры,
указанные SysV ABI для аргументов функции. Если на платформе есть другие общие ABI, которые не мешают SysV ABI, то значения также
следует дублировать в регистрах этих ABI (или на вершине стека).
Передаваемый адрес всегда выравнивается по 8 байтам и начинается с заголовка MBI:
+-------------------+
u32 | total_size |
u32 | reserved |
+-------------------+
За этим следует ряд тегов, также выровненных по 8 байт. Каждый тег начинается со следующих полей заголовка тега:
+-------------------+
u32 | type |
u32 | size |
+-------------------+
type
содержит идентификатор содержимого остальной части тега. size
содержит размер тега, включая поля заголовка, но не включая
отступы. Теги следуют друг за другом, дополняя их при необходимости, чтобы каждый тег начинался с 8-байтового выровненного адреса.
+-------------------+
u32 | type = 0 |
u32 | size = 8 |
+-------------------+
Теги завершаются тегом типа 0
и размера 8
.
+-------------------+
u32 | type = 1 |
u32 | size |
u8[n] | string |
+-------------------+
string
содержит командную строку, указанную в строке kernel
menuentry (без пути к ядру и имени файла). Командная строка
представляет собой обычную строку UTF-8 с нулевым завершением в стиле C.
+----------------------+
u32 | type = 2 |
u32 | size = 17 |
u8[n] | string "Easyboot" |
+----------------------+
string
содержит имя загрузчика, загружающего ядро. Имя представляет собой обычную строку UTF-8 в стиле C с нулевым завершением.
+-------------------+
u32 | type = 3 |
u32 | size |
u32 | mod_start |
u32 | mod_end |
u8[n] | string |
+-------------------+
Этот тег указывает ядру, какой загрузочный модуль был загружен вместе с образом ядра и где его можно найти. mod_start
и mod_end
содержат начальный и конечный физические адреса самого загрузочного модуля. Вы никогда не получите буфер, сжатый gzip, потому что
Easyboot прозрачно распаковывает его за вас (а если вы предоставите плагин, он также будет работать и с данными, отличными от
сжатых gzip). Поле string
предоставляет произвольную строку, которая будет связана с этим конкретным загрузочным модулем; это
обычная строка UTF-8 с нулевым завершением в стиле C. Указывается в строке module
menuentry, и его точное использование зависит
от операционной системы. В отличие от тега командной строки загрузки, теги модуля также включают путь и имя файла модуля.
Для каждого модуля отображается один тег. Этот тип тега может появляться несколько раз. Если вместе с вашим ядром был загружен исходный виртуальный диск, то он будет отображаться как первый модуль.
Существует особый случай: если файл представляет собой таблицу ACPI DSDT большой двоичный объект FDT (dtb) или GUDT, тогда он не будет отображаться как модуль, а будет исправлен старый RSDP ACPI (тип 14) или новый RSDP ACPI (тип 15) и их DSDT заменены содержимым этого файла.
Этот тег предоставляет карту памяти.
+-------------------+
u32 | type = 6 |
u32 | size |
u32 | entry_size = 24 |
u32 | entry_version = 0 |
varies | entries |
+-------------------+
size
содержит размер всех записей, включая само это поле. entry_size
всегда равен 24. entry_version
имеет значение 0
.
Каждая запись имеет следующую структуру:
+-------------------+
u64 | base_addr |
u64 | length |
u32 | type |
u32 | reserved |
+-------------------+
base_addr
— это начальный физический адрес. length
— это размер области памяти в байтах. type
— это разновидность
представленного диапазона адресов, где значение 1
указывает на доступную оперативную память, значение 3
указывает на доступную
для использования память, содержащую информацию ACPI, значение 4
указывает на зарезервированную память, которую необходимо
сохранять при гибернации, значение 5
указывает на память, занятую неисправными модулями ОЗУ, а все остальные значения в настоящее
время указывают на зарезервированную область. Для параметра reserved
установлено значение 0
при загрузке BIOS.
Когда MBI создается на машине UEFI, различные записи карты памяти EFI сохраняются как тип 1
(доступная ОЗУ) или 2
(зарезервированная ОЗУ), и если вам это понадобится, исходный тип памяти EFI помещается в reserved
поле.
Предоставленная карта гарантированно содержит список всей стандартной оперативной памяти, которая должна быть доступна для
нормального использования, и она всегда упорядочивается по возрастанию base_addr
. Однако этот доступный тип ОЗУ включает области,
занимаемые ядром, mbi, сегментами и модулями. Ядро должно позаботиться о том, чтобы не перезаписать эти регионы (Easyboot может
легко исключить эти регионы, но это нарушит совместимость Multiboot2).
+----------------------------------+
u32 | type = 8 |
u32 | size = 38 |
u64 | framebuffer_addr |
u32 | framebuffer_pitch |
u32 | framebuffer_width |
u32 | framebuffer_height |
u8 | framebuffer_bpp |
u8 | framebuffer_type = 1 |
u16 | reserved |
u8 | framebuffer_red_field_position |
u8 | framebuffer_red_mask_size |
u8 | framebuffer_green_field_position |
u8 | framebuffer_green_mask_size |
u8 | framebuffer_blue_field_position |
u8 | framebuffer_blue_mask_size |
+----------------------------------+
Поле framebuffer_addr
содержит физический адрес кадрового буфера. Поле framebuffer_pitch
содержит длину одной строки в байтах.
Поля framebuffer_width
, framebuffer_height
содержат размеры кадрового буфера в пикселях. Поле framebuffer_bpp
содержит
количество бит на пиксель. framebuffer_type
всегда имеет значение 1, а reserved
всегда содержит 0 в текущей версии спецификации
и должен игнорироваться образом ОС. Остальные поля описывают формат упакованных пикселей, положение и размер каналов в битах. Вы
можете использовать выражение ((~(0xffffffff << size)) <<position) & 0xffffffff
, чтобы получить маску канала, подобную UEFI GOP.
Этот тег существует только в том случае, если Easyboot работает на компьютере с UEFI. На машине с BIOS этот тег никогда не генерируется.
+-------------------+
u32 | type = 12 |
u32 | size = 16 |
u64 | pointer |
+-------------------+
Этот тег содержит указатель на EFI system table.
Этот тег существует только в том случае, если Easyboot работает на компьютере с UEFI. На машине с BIOS этот тег никогда не генерируется.
+-------------------+
u32 | type = 20 |
u32 | size = 16 |
u64 | pointer |
+-------------------+
Этот тег содержит указатель на EFI image handle. Обычно это дескриптор образа загрузчика.
+-------------------+
u32 | type = 13 |
u32 | size |
u8 | major |
u8 | minor |
u8[6] | reserved |
| smbios таблицы |
+-------------------+
Этот тег содержит копию таблиц SMBIOS, а также их версию.
+-------------------+
u32 | type = 14 |
u32 | size |
| копия RSDPv1 |
+-------------------+
Этот тег содержит копию RSDP, определенную в спецификации ACPI 1.0. (С 32-битным адресом.)
+-------------------+
u32 | type = 15 |
u32 | size |
| копия RSDPv2 |
+-------------------+
Этот тег содержит копию RSDP, определенную в спецификации ACPI 2.0 или более поздней версии. (Возможно, с 64-битным адресом.)
Они (типы 14 и 15) указывают на таблицу RSDT
или XSDT
с указателем на таблицу FACP
, которая, в свою очередь, содержит два
указателя на таблицу DSDT
, описывающую машину. Easyboot подделывает эти таблицы на компьютерах, которые в противном случае не
поддерживают ACPI. Кроме того, если вы предоставляете таблицу DSDT, большой двоичный объект FDT (dtb) или GUDT в качестве модуля,
Easyboot исправит указатели, чтобы они указывали на эту предоставленную пользователем таблицу. Для анализа этих таблиц вы
можете использовать мою независимую библиотеку с одним заголовком hwdet (или раздутую библиотеку
apcica acpica) и libfdt).
Теги с type
больше или равным 256 не являются частью спецификации Multiboot2, тем не менее, предоставленной Easyboot.
Они могут быть добавлены в список с помощью дополнительных плагинов, если и когда они потребуются ядру.
+-------------------+
u32 | type = 256 |
u32 | size |
| копия EDID |
+-------------------+
Этот тег содержит копию списка поддерживаемых разрешений монитора согласно спецификации EDID.
+-------------------+
u32 | type = 257 |
u32 | size |
u32 | numcores |
u32 | running |
u32 | bspid |
+-------------------+
Этот тег существует, если была указана директива multicore. numcores
содержит количество ядер ЦП в системе, running
— это
количество ядер, которые успешно инициализировались и параллельно запускали одно и то же ядро. bspid
содержит идентификатор ядра
начальной загрузки (по идентификатору lAPIC x86), чтобы ядра могли различать приложения и запускать на них другой код. Все ядра
приложения имеют свой собственный стек, и на вершине стека будет идентификатор текущего ядра.
+-------------------+
u32 | type = 258 |
u32 | size = 24 / 40 |
u128 | bootuuid |
u128 | rootuuid |
+-------------------+
Этот тег содержит уникальные поля идентификаторов в GPT загрузочного и корневого разделов. Если загрузка не использует таблицу
разделов GUID, то bootuuid
генерируется как 54524150-(код устройства)-(номер раздела)-616F6F7400000000
.
Начало | Конец | Описание |
---|---|---|
0x0 | 0x400 | Interrupt Vector Table (можно использовать, реальный режим IDT) |
0x400 | 0x4FF | BIOS Data Area (можно использовать) |
0x4FF | 0x500 | код загрузочного диска BIOS (скорее всего, 0x80; можно использовать) |
0x500 | 0x5A0 | данные синхронизации для SMP (можно использовать) |
0x5A0 | 0x1000 | стек обработчиков исключений (можно использовать после настройки IDT) |
0x1000 | 0x8000 | таблицы подкачки (можно использовать после настройки таблиц подкачки) |
0x8000 | 0x20000 | код и данные загрузчика (можно использовать после настройки IDT) |
0x20000 | 0x40000 | конфигурация + теги (можно использовать после анализа MBI) |
0x40000 | 0x90000 | plugin ids; сверху вниз: стек ядра |
0x90000 | 0x9A000 | Только ядро Linux: zero page + cmdline |
0x9A000 | 0xA0000 | Extended BIOS Data Area (лучше не трогать) |
0xA0000 | 0xFFFFF | VRAM и BIOS ROM (не использовать) |
0x100000 | x | сегменты ядра, за которыми следуют модули, каждая страница выровнена |
Никто не знает. UEFI распределяет память по своему усмотрению. Ожидайте всего и вся. Вся область обязательно будет указана в карте
памяти как тип = 1 (MULTIBOOT_MEMORY_AVAILABLE
) и reserved = 2 (EfiLoaderData
), однако это не является исключительным, другие
виды памяти также могут быть указаны таким же образом (например, раздел bss менеджера загрузки).
Начало | Конец | Описание |
---|---|---|
0x0 | 0x500 | зарезервировано прошивкой (лучше не трогать) |
0x500 | 0x5A0 | данные синхронизации для SMP (можно использовать) |
0x5A0 | 0x1000 | стек обработчиков исключений (можно использовать после настройки VBAR) |
0x1000 | 0x9000 | таблицы подкачки (можно использовать после настройки таблиц подкачки) |
0x9000 | 0x20000 | код и данные загрузчика (можно использовать после настройки VBAR) |
0x20000 | 0x40000 | конфигурация + теги (можно использовать после анализа MBI) |
0x40000 | 0x80000 | FDT (dtb) предоставляемый прошивкой; сверху вниз: стек ядра |
0x100000 | x | сегменты ядра, за которыми следуют модули, каждая страница выровнена |
Первые несколько байтов зарезервированы для armstub.
Запустилось только ядро 0, поэтому для запуска процессоров приложений запишите адрес функции в 0xE0 (ядро 1), 0xE8 (ядро 2), 0xF0
(ядро 3), какие адреса расположены в этой области. Это не имеет значения, если используется директива multicore
, тогда все ядра
будут выполнять ядро.
Хотя изначально RPi не поддерживается, вы все равно получаете старый тег RSDP ACPI (тип 14) с поддельными таблицами. Таблица APIC
используется для передачи ядру количества доступных ядер ЦП. Адрес функции запуска хранится в поле RSD PTR -> RSDT -> APIC ->
cpu[x].apic_id (и идентификатор ядра в cpu[x].acpi_id, где BSP всегда равен cpu[0].acpi_id = 0 и cpu[0].apic_id = 0xD8. Будьте
осторожны, «acpi» и «apic» выглядят очень похоже).
Если встроенное FDT передает действительный большой двоичный объект FDT или если один из модулей представляет собой файл .dtb, .gud
или .aml, то также добавляется таблица FADT (с магическим FACP
). В этой таблице указатель DSDT (32-битный, со смещением 40)
указывает на предоставленный плоский объект дерева устройств.
Несмотря на то, что прошивка не обеспечивает функцию карты памяти, вы все равно получите тег карты памяти (тип 6), в котором перечислены обнаруженные ОЗУ и регион MMIO. Вы можете использовать это для определения базового адреса MMIO, который отличается на RPi3 и RPi4.