Как настроить VGA после UEFI?
Я загружаюсь с помощью UEFI (биоса нет). После передается управление ядру моей ОС, настраиваю Paging. GOP (Graphics Output Protocol) становится не доступным, а вместе с ним и любой вывод на экран.
Для настройки всего самостоятельно мне видится пока такой алгоритм:
- Находим на PCI любую видеокарту, совместимую с VGA
- Получаем физический адрес начала видеопамяти (у меня это
0x81020000
, как в BAR'е, адресующем наибольшее пространство) - Ставим биты Memory Space и I/O Space в Command Register (https://wiki.osdev.org/PCI)
- "Привязываем" найденный адрес по виртуальному адресу (например
0xA0000
) - Устанавливаем регистры VGA для графического режима, например для 12h (просто складываю их в определенные порты, без какой-либо привязки к PCI, видеокарте и чему-либо еще).
- Записываем некоторое значение в виртуальный адрес (
0xA0000
) и оно должно отобразиться на экране
После всех действий по адресу 0xA0000
появляется связный бред (QEMU Monitor...
), но на экране ничего не происходит. Более того, почему-то не получается записать значение в отраженную память.
Здесь мой код. Основное в pci.c
и vga.c
. Память привязываю, при настройке пейджинга, до ее обнаружения, исхожу из предположения, что на одном эмуляторе между запусками ничего не изменится.
Что я упускаю?
Ответы (3 шт):
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 большинство доступных ранее участков физ.памяти, и их придётся восстанавливать. Здесь есть несколько известных мне нюансов:
- Физ.адрес VRAM и порта в/в хранятся в BAR PCI-Cfg, при этом порт из BAR будет отображаться на порты ЦП
03C0-03CFh
. - В регистре "Command" PCI-Cfg выставить биты IO/MIO/BusMaster = 7.
- Бит(1) в порту
03CCh
контроллёра VGA, разрешает ЦП доступ к VRAM (у меня это 0xD0000000 см.выше). - 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 нижнюю половину из общего пула).
Результат моих ковыряний в коде - полуживой 12h. Минимальный код в файлах vga.c
/ vga.h
.
Алгоритм следующий:
- Не трогаем ни PCI, ни пейджинг.
- Необходимо и достаточно установить регистры
- General registers: записываем в порт
0x3C2
- Sequencer: записываем индекс в
0x3C4
, значение - 0x3C5 - CRTC registers: индекс -
0x3D4
, значение -0x3D5
- Graphics controller: индекс -
0x3CE
, значение -0x3CF
- Attribute controller: индекс -
0x3C0
, значение -0x3C0
. Перед тем, как ставить индекс в0x3C0
необходимо прочитать из0x3DA
, чтобы порт начал принимать индекс. Первые 16 регистров здесь отвечают за палитру. - После установки режима видеопамять будет отображена по стандартному адресу -
0xA0000
Такой результат (правда, не очень похоже на 640x480, возможно из-за неправильной записи в память при очистке экрана):
Ответ несколько не в тему, но все же тоже про графику (для чего 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
Мой код.