Himik писал(а):
Пиши, на Ассемблере оно понятнее получается.
Представляю небольшой туториал, в котором я попытаюсь простым рабоче-крестьянским языком описать что такое PAT и как его попользовать в свое удовольствие.
Итак PAT (Page Attribute Table), в моем вольном переводе означает "Таблица аттрибутов страниц(ы)".
Зачем это еще, а можно без этого?
Да можно, если писать ядро с текстовым интерфейсом ONLY или совсем уж ущербной графикой с низким разрешением и убогой цветовой гаммой.
А ежели хочется красивостей всяких, прозрачностей там или еще чего (3D Pipeline к примеру), хорошей графики стало быть, то без PAT все еще можно обойтись, но отрисовка пикселей будет очень медленной, что обязательно вызовет у юзера бесконечный поток нецензурной брани в адрес разраба ядра.
Что бы этого избежать, и не только этого, необходимо ускорить вывод графики одним из трех известных способов:
1. Аппаратное ускорение (Hardware acceleration) графики, то что надо, но есть некоторые ньюансы, а именно, как это программно реализовать в виде кода. Видеокарт миллионы, доков на абсолютное большинство нет и не предвидиться, а одного желания не достаточно.
2. Программное ускорение (Software acceleration) графики, на порядки хуже HW, но за неимение лучшего пойдет и так, срединный путь как говориться.
3. Взять цветные фломастеры и рисовать графику вручную прямо на экране монитора, только это очень медленно, а и не у всякого получиться, красиво что бы, да и в башню может прилететь, если моник чужой.
- Замутим?
- Ну пожалуй, а что надо делать?
Да сущие пустяки, для начала почитать, например, вот этот туториал:
http://forum.osdev.org/viewtopic.php?p=257947&sid=70ea17364d329129a5a4cd029db18032#p257947,
равно как и многие другие, да собственно в самой спецификации VESA BIOS EXTENSION (VBE) "vbe3.pdf" (гуглиться за один клик), все разжевано до состояния бульона, не по нашему правда написано, но понять можно.
После того как станет понятным и родным словосочетание LFB (Linear Frame Buffer) можно и этот туториал прочитать, тут уже по нашему, для себя, что бы значит просто было и понятно.
Начнем.
PAT входит в состав так называемых MSR (Model-Specific Registers), это кусок памяти внутри камня, который не у всех камней есть, проверять надо, а как?
Проверить поддержку камнем MSR вообще и PAT в частности можно очень просто:
Код:
mov eax, 0x01 ; EAX = 0x01
cpuid
bt edx, 5 ; бит №5 в регистре EDX должен быть возведен
jnc @MSR_IS_NOT_SUPPORTED ; если это не так, то MSR не поддерживается
bt edx, 16 ; бит №16 в регистре EDX должен быть возведен
jnc @PAT_IS_NOT_SUPPORTED ; если это не так, то PAT не поддерживается
PAT нельзя включить или выключить, он активен всегда (даже если нам это не надо или мы вооще ничего о нем не знаем или не хотим знать), при условии, что включена страничная адресация памяти (в LONG MODE это без вариантов, стало быть обязательно ДА).
У PAT есть свой собственный регистр, он условно называется IA32_PAT, условно потому, что к нему нельзя обратиться по имени, а только по смещению 0x277 относительно памяти, которая называется MSR (Model-Specific Registers).
Прямого доступа к этой памяти разумеется нет, но мы можем запросить содержимое интересующнго нас регистра с помощью инструкции RDMSR (Read MSR), внести туда любые изменения (под нашу прямую ответственность) и записать обратно с помощью инструкции WRMSR (Write MSR).
Как уже было отмечено ранее, нас интересует исключительно регистр IA32_PAT расположенный по смещению 0x277. Давайте посмотрим что там:
Код:
mov ecx, 0x277 ; в регистр ECX заносим смещение
rdmsr ; читаем в RAX MSR по смещению ECX = 0x277
Интересующая нас информация находиться в регистре RAX, именно RAX, т.к. регистр IA32_PAT 64-битный, не смотря на цифру 32 в названии.
Для нашей цели (ускорение вывода пикселей в LFB (Linear Frame Buffer)), нам будет вполне достаточно регистра EAX.
Итак запрос сделан, каков же ответ. В моем случае, на эмуляторе QEMU, регистр EAX содержит число 0x00070406. Ну и что, а зачем это? Читаем дальше.
Что же представляет из себя регистр IA32_PAT, рассмотрим для начала регистр RAX:
Код:
+--------+--------+--------+--------+--------+--------+--------+--------+
| RAX |
+--------+--------+--------+--------+--------+--------+--------+--------+
| | EAX |
+--------+--------+--------+--------+--------+--------+--------+--------+
| | | | AX |
+--------+--------+--------+--------+--------+--------+--------+--------+
| | | | | | | AH | AL |
+--------+--------+--------+--------+--------+--------+--------+--------+
Как видно из рисунка, регистр RAX состоит ровно из 8 одинаковых однобайтовых (8 битовых) частей, к некоторым из них можно обратиться по имени, к большинству нельзя, но ведь это нас не останавливает, мы можем обратиться по смещению и получить значение любого байта или даже бита.
По аналогии с регистром RAX составим схему регистра IA32_PAT:
Код:
+--------+--------+--------+--------+--------+--------+--------+--------+
| | | | | | | AH | AL |
+--------+--------+--------+--------+--------+--------+--------+--------+
| PAT7 | PAT6 | PAT5 | PAT4 | PAT3 | PAT2 | PAT1 | PAT0 |
+--------+--------+--------+--------+--------+--------+--------+--------+
|00000bbb|00000bbb|00000bbb|00000bbb|00000bbb|00000bbb|00000bbb|00000bbb|
+--------+--------+--------+--------+--------+--------+--------+--------+
Где 00000bbb это двоичное представление данных (8 бит), из которых 00000 = 5 нулевых бит и bbb = 3 значащих бита, которые, в свою очередь, так же могут быть нулевыми, а могут и не быть.
Из рисунка-схемы видно, что регистр IA32_PAT так же как и RAX состоит из 8 одинаковых однобайтовых (8 битовых) частей, к которым нельзя обратиться по имени, а только по смещению, но мы то это умеем делать.
Напомню, что пять старших битов каждого байта PAT0 ... PAT7 ВСЕГДА равны нулю, значащими являются только 3 младших бита, с ними и будем работать.
А как же тогда понять в каких случаях младшие три бита не нулевые и что все это означает?
Как раз для такого случая Intel составила табличку, которую мы тиснем из "Intel(R) 64 and IA-32 Architectures Software Developer's Manual, Volume 3A, 11.12 PAGE ATTRIBUTE TABLE (PAT)",
я только чутка ее модифицировал, для простоты.
Код:
+-----------------------------------------------------------------------+
| БИНАРНЫЙ КОД | РАСШИФРОВКА |
+-----------------------------------------------------------------------+
| 000b = 0x00 | Uncacheable (UC) |
+-----------------------------------------------------------------------+
| 001b = 0x01 | Write Combining (WC) |
+-----------------------------------------------------------------------+
| 010b = 0x02 | Reserved |
+-----------------------------------------------------------------------+
| 011b = 0x03 | Write Through (WT) |
+-----------------------------------------------------------------------+
| 100b = 0x04 | Write Protected (WP) |
+-----------------------------------------------------------------------+
| 101b = 0x05 | Write Back (WB) |
+-----------------------------------------------------------------------+
| 110b = 0x06 | Uncached (UC-) |
+-----------------------------------------------------------------------+
| 111b = 0x07 | Reserved |
+-----------------------------------------------------------------------+
Как видно из таблицы, столбец "БИНАРНЫЙ КОД" аккурат представлен тремя значащими битами, а столбец "РАСШИФРОВКА" дает нам однозначное толкование этих самых трех битов.
Давайте припомним наш прошлый запрос, который мы так и не распарсили, EAX = 0x00070406 = 0x00 0x07 0x04 0x06
Смотрим на табличку и видим что:
PAT0 = 0x06 = Uncached (UC-)
PAT1 = 0x04 = Write Protected (WP)
PAT2 = 0x07 = Reserved
PAT3 = 0x00 = Uncacheable (UC)
Старшие PAT'ы (PAT4 ... PAT7) нам вообще не интересны.
Ну допустим, узнали мы, что код Write Combining (WC) = 0x01, а что с ним делать то, куда прописать и главное как?
Для начала следует определиться какой из PAT'ов тиснуть, они НЕ зависимы друг от друга и брать можно любой, мы же пишем ядро мы и решаем.
PAT0 мы трогать не будем, почему, станет понятно из дальнейшего текста.
А что там у нас следующее на очереди, так ведь PAT1, а можно? Нужно!
Решено замутим с PAT1 (он ПЕРВЫЙ справа, соответствует регистру AH, а PAT0 НУЛЕВОЙ справа, соответствует регистру AL):
Код:
mov ecx, 0x277 ; в регистр ECX заносим смещение
rdmsr ; читаем в RAX MSR по смещению ECX = 0x277
mov ah, 0x01 ; Write Combining (WC) = 0x01
wrmsr ; пишем RAX = IA32_PAT обратно в MSR
Ну что ты все? Почти, потерпи еще немного, я скоро.
Далее я буду излагать свое личное мнение на то как устоен этот мир, что правильно и что не правильно.
Итак, ось должна быть строго 64-битной, память внутри ядра должна быть расчехлена на 2-х мегабайтные страницы, я бы расчехлил на 1-гигабайтные, но не все камни это держат, печаль.
Тема расчехления памяти на страницы тянет на отдельный туториал, может кто его и напишет (подключайтесь), а может даже это буду я, посмотрим, а сейчас голый код со скромными коментами:
Код:
;-----------------------------------------------------------------------
; Это мне так удобно, можно поменять адреса на свой вкус
;-----------------------------------------------------------------------
%define PML4T 0x2000 ; P4 = корневая директория х512 GB
%define PDPT 0x3000 ; P3 (4 страницы по 1 GB каждая)
%define PDT0 0x4000 ; P2.0 (512 страниц по 2 MB каждая)
%define PDT1 0x5000 ; P2.1 (512 страниц по 2 MB каждая)
%define PDT2 0x6000 ; P2.2 (512 страниц по 2 MB каждая)
%define PDT3 0x7000 ; P2.3 (512 страниц по 2 MB каждая)
;-----------------------------------------------------------------------
; Сначала корневая директория PML4T - каждая запись это 512 Гигабайт
;-----------------------------------------------------------------------
; Set up PML4T (P4) ; 0x2000
mov dword eax, PDPT ; адрес PDPT (P3)
or dword eax, 11b ; R/W / PML4E present
mov dword [PML4T], eax
mov dword [PML4T + 4], 0
;-----------------------------------------------------------------------
; Следующая за корнем директория PDPT - каждая запись это 1 Гигабайт
;-----------------------------------------------------------------------
; Set up PDPT (P3) ; 0x3000
mov ecx, 4 ; 4 страницы по (2 * 512 = 1 GB) = 4 GB
xor ebx, ebx
mov edi, PDPT ; P3
mov eax, PDT ; адрес PDT (P2) = 0x4000
or al, 00000011b ; R/W / PDPT present
@NEXT_P3:
mov dword [edi], eax
mov dword [edi+4], ebx
add eax, 0x1000
add edi, 8
loop @NEXT_P3
;-----------------------------------------------------------------------
; Собственно страницы - каждая запись (страница) это 2 мегабайта
;-----------------------------------------------------------------------
; Set up PDT ; 0x4000
mov edi, PDT
mov eax, 10000011b ; 2MiB / ... / R/W / PDT present
mov ecx, 512*4 ; отжимаем 4GB: 4 PDT по 1GB (512 * 2MB)
xor ebx, ebx
@NEXT_P2:
mov dword [edi+0], eax
mov dword [edi+4], ebx
add eax, 0x200000 ; + 2 MiB
add edi, 8
loop @NEXT_P2
;-----------------------------------------------------------------------
; Определим диапазон адресов занятых под LFB как Write Combining (WC)
;-----------------------------------------------------------------------
mov eax, dword [param.lfb] ; LFB
shr eax, 21 ; количество 2М страниц (0x480 = 1152)
shl eax, 3 ; (количество 2М страниц) * 8 байт
mov edi, PDT
add edi, eax
mov eax, dword [param.lfb] ; LFB
add eax, 10001011b ; 2MiB / ... / PWT / ... / R/W / PDT present
mov ecx, 8 ; 16M / 2M = 8
xor ebx, ebx
@SET_WC:
mov dword [edi+0], eax
mov dword [edi+4], ebx
add eax, 0x200000 ; + 2 MiB
add edi, 8
loop @SET_WC
Как память расчехлять на сраницы отдельный разговор, а вот слив LFB в WC рассмотрим подробнее.
Итак через VESA мы получили адрес LFB, я сохранил его в структуру 'param', можно назвать по своему, а разве может быть иначе.
Адрес LFB выровнен, уж не знаю насколько точно, но на 2 MB можно расчитывать, поэтому делим значение адреса LFB на 2 MB, что бы узнать смещение от НАЧАЛА памяти, т.е. от НУЛЯ в 2-х мегабайтных страницах.
Например, пусть адрес LFB = 0x90000000, тогда делим 0x90000000 на 0x200000 и получаем 0x480 = 1152 2-х мегабайтных страниц. Их трогать НЕЛЬЗЯ, пропускаем.
Код:
mov eax, dword [param.lfb] ; LFB
shr eax, 21 ; количество 2М страниц (0x480 = 1152)
Напомню, что каждая запись в таблице PDT занимает 8 байт, а у нас этих записей, как мы только что посчитали, 0x480 = 1152 и их трогать НЕЛЬЗЯ, а как узнать какие можно, так просто:
0 запись = 8 * 0 = 0
1 запись = 8 * 1 = 8
...
343 запись = 8 * 343 = 2744
...
1152 запись = 8 * 1152 = 9216 = 0x2400
То есть нам нужно пропустить 1152 записи в таблице PDT, каждая запись это 8 байт, значит умножаем 1152 на 8 и получаем 9216 байт, это смещение от начала PDT в байтах.
Код:
shl eax, 3 ; (количество 2М страниц) * 8 байт
Я знаю, что можно сразу сделать так:
Код:
mov eax, dword [param.lfb] ; LFB
shr eax, 18 ; количество 2М страниц (0x480 = 1152) * 8
Но как-нибудь потом лениво будет ломать голову, а почему я здесь написал 18, а как же так вышло ..., чем проще, тем лучше.
Далее EDI присваиваем адрес PDT в памяти и смещаемся на расчитанное количество байт:
Код:
mov edi, PDT
add edi, eax
Далее нам нужно расчитать "стартовое" значение, которым мы начнем перезаписывать атрибуты конкретно взятых страниц занятых под LFB.
По тупому берем еще раз адрес LFB из структуры 'param' и прибавляем к нему магическое число 10001011b в котором зарыта вся суть.
Распарсим эту магию:
самый младший бит №0 означает, что страница реально существует из нее можно читать и в нее можно писать
следующий бит №1 означает, как раз то, что страница R/W, т.е. и читаемая и писаемая
следующий бит №2 означает, посмотрите сами что он означает, или по тупому ставим ноль
следующий бит №3 означает, ОДИН ИЗ ТРЕХ значащих битов (PAT, PCD и PWT), в данном случае это PWT = 1
следующий бит №4 означает, ОДИН ИЗ ТРЕХ значащих битов (PAT, PCD и PWT), в данном случае это PCD = 0
самый старший бит №7 означает, что это 2-х мегабайтная страница, а если страницы 4-х килобайтные, то это и есть PAT (ОДИН ИЗ ТРЕХ значащих битов (PAT, PCD и PWT)) и он ДОЛЖЕН быть нулевым!
Но т.к. у нас страницы 2-мегабайтные, то 7 бит это НЕ PAT, а PAT это бит №12, и он у нас как раз нулевой.
Подитожим:
мы имеем следующее сочетание PAT|PCD|PWT = 001b, это
НЕ ОЗНАЧАЕТ, что страница слита в WC, это означает, что аттрибут страницы берется из регистра PAT1, код которого = 001b = 0x01, а вот в него то мы как раз и записали атрибут WC, который тоже, по чистой случайности = 001b = 0x01.
Далее в цикле изменяем аттрибуты 8 страниц, а почему 8, да потому что у меня размер foreground+background буфера = 1920*1080*4*2 = 16 мегабайт, делим на 2 мегабайта (размер страницы) и получаем 8, количество страниц занятых под LFB.
Ну и напоследок, почему не рекомендуется трогать PAT0, потому что его код в формате PAT|PCD|PWT = 000b = 0x00 и именно этим кодом (все нули) мы по дефолту аттрибутим ВСЕ наши страницы и только потом переаттрибутиваем нужные на WC.
Т.е. ВСЕ наши страницы при инициации получают аттрибут 000b = 0x00 = PAT0, а в нем код 0x06 = Uncached (UC-), поэтому вывод пикселей так ужастно тупит.
Не вздумайте слить ВСЕ страницы в WC, это косяк!
На этом все, проще объяснить не могу, не имею такого таланта, надеюсь будет кому то полезно.