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

Пишем операционную систему Триалогия - Шина PCI - как находят устройства, config space, BAR

Первая серия статей крутилась вокруг центральных интерфейсов ядра. Теперь спускаемся на уровень ниже, к фундаменту на котором стоят все драйверы. Как ядро вообще находит железо с которым должно говорить? При старте оно ничего не знает о машине в которой работает. Ни списка дисков, ни сетевой карты, ничего. Ответ - это шина PCI и она на удивление систематична. У каждого устройства PCI есть 256-байтовое конфигурационное пространство, в котором оно описывает само себя. Устройство адресуется тремя числами: шина, устройство и функция. Доступ идёт через два фиксированных порта I/O 0xCF8 для адреса и 0xCFC для данных. У меня этот механизм находится в слое архитектуры (звучит тривиально, но на самом деле это основной принцип построения всей системы, который позволит в дальнейшем переписать систему на другую архитектуру. Напишу отдельную статью), а pci_read это лишь тонкая обёртка вокруг него. uint32_t pci_read(uint32_t bus, uint32_t device, uint32_t function, uint32_t offset) { return arch_pc
Оглавление
Триалогия - Пишем операционную систему
Триалогия - Пишем операционную систему

Шина PCI

Первая серия статей крутилась вокруг центральных интерфейсов ядра. Теперь спускаемся на уровень ниже, к фундаменту на котором стоят все драйверы. Как ядро вообще находит железо с которым должно говорить? При старте оно ничего не знает о машине в которой работает. Ни списка дисков, ни сетевой карты, ничего. Ответ - это шина PCI и она на удивление систематична.

Каждое устройство описывает себя само

Конфигурационное пространство PCI - Триалогия
Конфигурационное пространство PCI - Триалогия

У каждого устройства PCI есть 256-байтовое конфигурационное пространство, в котором оно описывает само себя. Устройство адресуется тремя числами: шина, устройство и функция. Доступ идёт через два фиксированных порта I/O 0xCF8 для адреса и 0xCFC для данных. У меня этот механизм находится в слое архитектуры (звучит тривиально, но на самом деле это основной принцип построения всей системы, который позволит в дальнейшем переписать систему на другую архитектуру. Напишу отдельную статью), а pci_read это лишь тонкая обёртка вокруг него.

uint32_t pci_read(uint32_t bus, uint32_t device, uint32_t function, uint32_t offset) {

return arch_pci_config_read32(bus, device, function, offset); // 0xCF8 / 0xCFC

}

Первые поля этого пространства- визитка устройства. По смещению 0x00 находятся ID производителя и устройства а по 0x08, класс устройства:

uint32_t vendor_device = pci_read(bus, dev, func, 0x00);

out->vendor_id = vendor_device & 0xFFFF; // кто делает

out->device_id = vendor_device >> 16; // какая модель

if (out->vendor_id == 0xFFFF) return -1; // 0xFFFF -> устройства здесь нет

uint32_t class_rev = pci_read(bus, dev, func, 0x08);

out->class_code = class_rev >> 24; // 0x01=storage, 0x02=сеть, 0x03 = графика

out->subclass = class_rev >> 16;

out->prog_if = class_rev >> 8;

Фишка в том, что будь то сетевая карта, контроллер дисков или графика, все они представляются в одном едином формате. Ядру нужно лишь прочитать 256 байт и оно знает, что там.

Сканирование шины PCI

Сканирование  шины PCI - Триалогия
Сканирование шины PCI - Триалогия

Поскольку ядро не знает железо заранее, оно пробует каждый возможный адрес: каждую шину, каждое устройство, каждую функцию. Если по смещению 0x00 читается лишь 0xFFFF, адрес пуст, и оно идёт дальше. Иначе - оно нашло устройство и читает его класс:

for (bus = 0; bus < 256; bus++)

for (dev = 0; dev < 32; dev++)

for (func = 0; func < 8; func++)

if ((pci_read(bus, dev, func, 0x00) & 0xFFFF) != 0xFFFF)

/* устройство найдено, прочитать класс, запомнить, потом искать драйвер */

По классу устройства ядро примерно знает, что нашло - 0x01 это хранилище, 0x02 сеть, 0x03 графика, 0x0C USB. Функция scan_pci_by_class целенаправленно собирает все устройства одного класса. В итоге у ядра список: вот Intel E1000, вот контроллер AHCI.

BAR - где говорить с устройством

BAR - Триалогия
BAR - Триалогия

Но найти устройство мало. Надо ещё знать, где лежат его регистры, чтобы давать ему команды. Именно для этого есть base address registers. Шесть штук, по смещениям 0x10 до 0x24. Каждый BAR указывает на область, которую использует устройство и есть два типа:

  • BAR порта I/O даёт номер порта, к которому обращаются через in/out, как у драйвера IDE.
  • BAR MMIO даёт физический адрес памяти, который отображаешь и потом обращаешься к нему как к памяти.

Младший бит BAR выдаёт, какого он типа.

Здесь замыкается круг к прежним частям: например контроллер AHCI из главы про Storage сообщает базу своих регистров через BAR MMIO. Ядро читает BAR, отображает адрес (ровно то, что описывала статья про отображение MMIO) и затем пишет command list именно туда. PCI говорит "где", MMIO делает это пригодным для обмена данными, а драйвер через это говорит с диском.

А теперь о ...

Как ни чиста модель config space PCI - вроде бочки с чистейшим медом, одна ложка дёгтя здесь присутствует - маршрутизация прерываний. Какой физический IRQ принадлежит устройству, записано по смещению 0x3C, но как это число прокладывается через логику чипсета до контроллера прерываний, это сплетение из PIRQ-роутеров, таблиц $PIR и особых случаев чипсета, которое в коде выглядит соответственно ОЧЕНЬ запутанным. И так же небольшой камень преткновения, в который я упирался - адрес, возвращающий 0xFFFF как ID производителя, это пустой слот, не устройство, фантом который надо последовательно отфильтровывать, иначе в списке устройств окажется мусор.

Что дальше

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

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

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

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