Как настроить VGA после UEFI?

Я загружаюсь с помощью UEFI (биоса нет). После передается управление ядру моей ОС, настраиваю Paging. GOP (Graphics Output Protocol) становится не доступным, а вместе с ним и любой вывод на экран.

Для настройки всего самостоятельно мне видится пока такой алгоритм:

  1. Находим на PCI любую видеокарту, совместимую с VGA
  2. Получаем физический адрес начала видеопамяти (у меня это 0x81020000, как в BAR'е, адресующем наибольшее пространство)
  3. Ставим биты Memory Space и I/O Space в Command Register (https://wiki.osdev.org/PCI)
  4. "Привязываем" найденный адрес по виртуальному адресу (например 0xA0000)
  5. Устанавливаем регистры VGA для графического режима, например для 12h (просто складываю их в определенные порты, без какой-либо привязки к PCI, видеокарте и чему-либо еще).
  6. Записываем некоторое значение в виртуальный адрес (0xA0000) и оно должно отобразиться на экране

После всех действий по адресу 0xA0000 появляется связный бред (QEMU Monitor...), но на экране ничего не происходит. Более того, почему-то не получается записать значение в отраженную память.

Здесь мой код. Основное в pci.c и vga.c. Память привязываю, при настройке пейджинга, до ее обнаружения, исхожу из предположения, что на одном эмуляторе между запусками ничего не изменится.

Что я упускаю?


Ответы (3 шт):

Автор решения: PRODIGY

0xA0000 - это физический адрес, куда я привязываю физический адрес LFB видеокарты (0x81020000)

Вы не можете назначить один физ.фрейм PFN двум физ.адресам как в вашем случае A0000=81020000. Можно назначить 2 разных фрейма одному вирт.адресу (классический маппинг + CopyOnWrite). Поскольку после пейджинга все адреса уже виртуальные, обращение по физ.адресу LFB=0xA0000 требует сохранение его PFN в записи РТЕ каталога страниц.

Но есть и другой вариант - просто скопировать уже готовую поддержку графики из ПЗУ адаптера VGA, и в таблице IDT восстановить вектор прерывания INT-10h. Это намного эффективнее со всех сторон. Вендор в/карты предлагает-же нам своё решение, так почему им не воспользоваться?

В PCI-Config-Space по смещению 30h лежит регистр xROMBAR, значение которого определяется так-же, как и BAR'ов (запись -1, с последующим чтением, а бит 0 активирует доступ). Формат содержимого VGA-ROM описан в книге Д.Салихан "BIOS: дизасм, модификация, программирование" в главе(7). Так получим сразу глифы шрифтов и все процедуры обслуживания, вплоть до поддержки режимов SVGA/VESA. Это касается и остальных Boot-девайсов с ПЗУ на борту, например HDD и LAN.

В реальном режиме без пейджинга, системный BIOS/EFI копирует VGA-ROM по физ.адресу начиная с C000:0000, а далее идут HDD-ROM и прочие, до адреса D000:0000. Так это выглядит на моей машине x64_Win7 со встроенной в/картой (дамп ПЗУ для дизасма сделан утилитой RWE):

введите сюда описание изображения

Отредактировано:

И какой же код будет скопирован? 16-ти битный?

Код в ПЗУ 32-битный, но на х64 да.. работать не будет. В таком случае вам придётся всё основное делать через порты VGA, а данные отправлять напрямую в видео-память (как вы и планировали). На эту тему есть хорошая дока от Зубкова (см.раздел 5.10.4.), но вкл.пейджинг добавляет массу проблем. Например ЦП может затенить/Shadow большинство доступных ранее участков физ.памяти, и их придётся восстанавливать. Здесь есть несколько известных мне нюансов:

  1. Физ.адрес VRAM и порта в/в хранятся в BAR PCI-Cfg, при этом порт из BAR будет отображаться на порты ЦП 03C0-03CFh.
  2. В регистре "Command" PCI-Cfg выставить биты IO/MIO/BusMaster = 7.

введите сюда описание изображения

  1. Бит(1) в порту 03CCh контроллёра VGA, разрешает ЦП доступ к VRAM (у меня это 0xD0000000 см.выше).
  2. MSR-регистры "MTRR_PhyBase/Mask" (всего 10 пар) могут устанавливать для регионов физ.памяти тип кэша, среди которых запрятался и отклоняющий доступ на запись WP=5. Эти атрибуты указываются в младшем байте регистра MTRR_PHYMASK_xx - как видно из скрина ниже, у меня для региона VRAM=0xD0000000 (как и для всех остальных) стоит "Uncacheable=0", хотя возможно это уже Windows выставила, а потому нужно проверить.
  • 0 = Uncacheable (UC)
  • 1 = Write-Combining (WC)
  • 4 = Write-Through (WT)
  • 5 = Write-Protected (WP)
  • 6 = Write-Back (WB)

Регистры MTRR_PHY позволяют задавать атрибуты произвольным областям памяти, а 11 доп.регистров MTRR_FIX_xx задают флаги строго предопределённым регионам, в т.ч. и кадровому буферу VRAM MTRR_FIX16K_A0000.

введите сюда описание изображения

А по поводу пейджинга... я, наверное, просто путаю физический и виртуальный адреса :) Более того, почему-то не получается записать значение в отраженную память.

Это потому, что вы неправльно создаёте каталог PML4 в файле "Paging.c". У вас сейчас по одной записи "Entry" в PML4 и PDP, далее 32 записи PDE, и 16384 записей PTE. Тогда выходит, что отсчёт адресуемых страниц памяти начинается с каталога PD.

Физ.фрейм(PFN) и вирт.страница(Page) всегда одинакового размера 4096-байт. Поскольку i шагает с шагом 0x1000, и его-же вы загоняете в запись РТE, то вирт.адрес у вас совпадает с физическим - это нормально. Но проблема в том, что если Page=4096 байт, а одна запись "Entry" размером 8-байт, значит в одной странице каталогов PML4/PDP/PD/PT может быть всего макс по 4096/8=512 записи. В режиме х64 это в сумме даёт: (512*512*512*512)*4096=256 ТераБайт, по 128 юзеру и ядру.

Теперь считаем ваши значения: (32*16384)*4096=2.147.483.648=0x80000000 (2 гига), в то время как VRAM у вас расположен по адресу 0x81020000. Ладно, что ваш каталог страниц не в силах адресовать VRAM, так ещё и кол-во записей в таблице РТ=16384, в то время как макс.значение 512. Для адресации 4ГБ, сейчас достаточно исправить значения на (4*512*512)*4096=0xFFFFFFFF, оставив PML4 без изменений (если тесты в QEMU, то в настройках ей тоже нужно 4ГБ). В идеале этим должен заниматься "диспетчер памяти" вашей ОС, вычислив на старте текущее значение ОЗУ. Для разделения вирт.памяти на User и Kernel (кольца 0/3), достаточно в записях РТЕ взводить бит U/S - при нулевом значении страница становится не доступной юзеру (Win выставляет в 1 нижнюю половину из общего пула).

введите сюда описание изображения

→ Ссылка
Автор решения: SurfaceStack

Результат моих ковыряний в коде - полуживой 12h. Минимальный код в файлах vga.c / vga.h.

Алгоритм следующий:

  1. Не трогаем ни PCI, ни пейджинг.
  2. Необходимо и достаточно установить регистры
  3. General registers: записываем в порт 0x3C2
  4. Sequencer: записываем индекс в 0x3C4, значение - 0x3C5
  5. CRTC registers: индекс - 0x3D4, значение - 0x3D5
  6. Graphics controller: индекс - 0x3CE, значение - 0x3CF
  7. Attribute controller: индекс - 0x3C0, значение - 0x3C0. Перед тем, как ставить индекс в 0x3C0 необходимо прочитать из 0x3DA, чтобы порт начал принимать индекс. Первые 16 регистров здесь отвечают за палитру.
  8. После установки режима видеопамять будет отображена по стандартному адресу - 0xA0000

Такой результат (правда, не очень похоже на 640x480, возможно из-за неправильной записи в память при очистке экрана):введите сюда описание изображения

→ Ссылка
Автор решения: SurfaceStack

Ответ несколько не в тему, но все же тоже про графику (для чего VGA и задумывалось).

Вместо VGA можно использовать GOP, если грузимся через UEFI. Для этого, предположим, что UEFI настраивает пейджинг 1 к 1, а это должно быть так:

UEFI uses identity mapping. You don't have to guess what the physical memory address is: it's the same as its virtual address.

В этом случае можно считать виртуальный адрес FrameBufferAddress физическим (они будут соответствовать) и привязать его к другому виртуальному адресу (я привязываю к 0x400000).

В итоге мы получим доступ ко всему разрешению экрана (вместо максимальных 640x480 VGA) и 24 битный цвет + 8 бит на альфу. Но будет ли это решение работать зависит только от конкретного UEFI

Мой код.

И результат: введите сюда описание изображения

→ Ссылка