Добавить в корзинуПозвонить
Найти в Дзене
Триалогия

Пишем операционную систему Триалогия - ACPI и MADT: LAPIC, IOAPIC и таблицы прошивки

Статья про PCI закончилась вопросом - устройства и нужные им прерывания найдены, но куда направлять эти IRQ? Для этого ядру нужно знать, какие контроллеры прерываний у машины вообще есть, сколько ядер CPU, какие таймеры. Эта информация приходит не от PCI а от ACPI и интересна тут не только MADT, а целое маленькое дерево таблиц, которое обходит мой парсер. ACPI это способ, которым прошивка описывает материнскую плату операционной системе. Сколько CPU, какие контроллеры прерываний, какие таймеры, как корректно выключиться. Всё это лежит в цепочке таблиц, а точка входа, в BIOS ROM. Мой парсер просто находит её там по сигнатуре. // узнать RSDP в области BIOS (0xE0000 .. 0x100000) по его сигнатуре for (addr = 0xE0000; addr < 0x100000; addr += 16) if (memcmp((void*)addr, "RSD PTR ", 8) == 0 && verify_checksum(addr)) rsdp = (rsdp_t*)addr; // найдено RSDP указывает на RSDT, Root System Description Table, а это не что иное, как каталог, массив указателей на все прочие
Оглавление
Триалогия - Пишем операционную систему
Триалогия - Пишем операционную систему

ACPI и MADT

Статья про PCI закончилась вопросом - устройства и нужные им прерывания найдены, но куда направлять эти IRQ? Для этого ядру нужно знать, какие контроллеры прерываний у машины вообще есть, сколько ядер CPU, какие таймеры. Эта информация приходит не от PCI а от ACPI и интересна тут не только MADT, а целое маленькое дерево таблиц, которое обходит мой парсер.

Что такое ACPI и как туда войти

Дерево таблиц ACPI BIOS - Триалогия
Дерево таблиц ACPI BIOS - Триалогия

ACPI это способ, которым прошивка описывает материнскую плату операционной системе. Сколько CPU, какие контроллеры прерываний, какие таймеры, как корректно выключиться. Всё это лежит в цепочке таблиц, а точка входа, в BIOS ROM. Мой парсер просто находит её там по сигнатуре.

// узнать RSDP в области BIOS (0xE0000 .. 0x100000) по его сигнатуре

for (addr = 0xE0000; addr < 0x100000; addr += 16)

if (memcmp((void*)addr, "RSD PTR ", 8) == 0 && verify_checksum(addr))

rsdp = (rsdp_t*)addr; // найдено

RSDP указывает на RSDT, Root System Description Table, а это не что иное, как каталог, массив указателей на все прочие таблицы. Каждая из этих таблиц начинается с одного и того же заголовка - 4-байтовой сигнатуры, длины и контрольной суммы. Так парсер знает, что перед ним.

rsdt_t* rsdt = map(rsdp->rsdt_address);

uint32_t count = (rsdt->length - sizeof(rsdt_t)) / 4; // число указателей на таблицы

for (i = 0; i < count; i++) {

void* table = map(rsdt->entry[i]);

if (memcmp(table, "FACP", 4) == 0) { /* обработать FADT */ }

else if (memcmp(table, "HPET", 4) == 0) { /* запомнить адрес HPET */ }

else if (memcmp(table, "APIC", 4) == 0) { /* обработать MADT */ }

}

Если контрольная сумма таблицы не сходится (все байты в сумме должны дать ноль по модулю 256), она пропускается. Простая, но действенная защита от битых или выдуманных таблиц.

Что мы берём из каждой таблицы

Что мы читаем из каждой таблицы ACPI - Триалогия
Что мы читаем из каждой таблицы ACPI - Триалогия

Из FADT (сигнатура "FACP") мой парсер берёт всё про питание и платформу: регистры управления питанием, команду вообще включить ACPI, значения для чистого выключения, версию ACPI, есть ли контроллер PS/2, и указатель на DSDT. Тема ACPICA достойна отдельной статьи. Вы даже не догадываетесь что в вашем компе живет тайком интерпретатор кода AML. Вроде бы он должен делать полезную работу, парсер железа на материнской плате. Но кто может помешать ему делать что то еще. Неспроста производители операционных систем не афишируют, что используют APICA. И выполняют (пытаются, стараются) его в песочнице что бы шаловливые ручки обрубить напрочь. Поищите информацию в интернете - очень удивитесь. А может я просто параноик?

acpi_pm1_control_register = facp->pm1a_control_block; // управление питанием

acpi_turn_on_command = facp->acpi_enable; // включить ACPI

shutdown_value_pm1 = facp->pm1a_control_block; // чистое выключение

acpi_version = facp->revision;

dsdt_mem = (void*)facp->dsdt; // указатель на DSDT

Из таблицы HPET он запоминает лишь адрес high precision event timer, который драйвер таймера использует позже, один из точных хронометров, заменяющих мой выключенный PIT. А MADT это самая богатая табличка из всех, ей посвятим следующий раздел.

MADT изнутри

Записи MADT  - Триалогия
Записи MADT - Триалогия

MADT (Multiple APIC Description Table, сигнатура "APIC") это полная карта железа прерываний. После её заголовка, называющего базовый адрес local APIC, идёт список записей, у каждой свой тип. Три важны для меня:

lapic_phys_addr = madt->lapic_address;

for (/* каждую запись */) switch (entry->type) {

case LAPIC: // тип 0: одна запись на ядро CPU

if (lapic->flags & 1) // "processor enabled"?

cpu_info[cpu_count++].apic_id = lapic->apic_id; // ещё один CPU

break;

case IOAPIC: // тип 1: контроллер прерываний

ioapics[ioapic_count].phys_addr = ioapic->ioapic_address;

ioapics[ioapic_count].gsi_base = ioapic->gsi_base;

ioapic_count++;

break;

case OVERRIDE: // исключения из стандартной маршрутизации

/* настоящее соответствие некоторых legacy-IRQ, иногда override адреса LAPIC */

break;

}

Здесь замыкается круг к двум прежним частям. Список CPU из записей local APIC это основа, на которой подсистема SMP вообще может запустить несколько ядер, каждая включённая запись это отдельное ядро. А список IOAPIC говорит системе прерываний из части про IDT/IOAPIC/LAPIC, куда реально направлять IRQ устройств с шины PCI. MADT таким образом, это связка между "какие устройства есть" и "как до меня доходят их прерывания".

Честное место

Мой собственный парсер занимается лишь "фиксированными" таблицами, которые можно читать поле за полем: RSDP, RSDT, FADT, HPET, MADT. DSDT же, на которую указывает FADT это нечто совсем другое, она содержит машинный код на своём языке под названием AML, который надо было бы интерпретировать чтобы отвечать на более сложные запросы о платформе. Писать для этого интерпретатор самому, это отдельный большой проект, поэтому рядом в дереве исходников лежит мощная библиотека ACPICA от Intel. Мой парсер покрывает то, что ядру нужно для загрузки, и оставляет тяжёлое дело AML парсеру, который я еще не использую. Для меня это выглядит как огромная дыра в безопасности. Триалогия пока не имеет песочницы (изолированной среды выполнения), поэтому я не использую эту библиотеку!

Что дальше

Теперь ядро знает устройства (от сканирования PCI) и железо прерываний (от ACPI). Но как из этого получается работающая система? Кто решает, что этот E1000 получит драйвер E1000, а тот контроллер, драйвер AHCI? Это сопоставление берёт на себя следующая подсистема - UDM (Universal Device Manager ) универсальный диспетчер устройств.

Было бы интересно увидеть ваши комментарии и улучшить статьи.

Предыдущая статья Содержание Следующая статья

*Система не стоит на месте, поэтому в дальнейшем тексты могут не совпадать с реальным положением