Предыдущие части:
HPET – High Precision Event Timers
частота: 14.318180 MHz | счётчик: 64-бита
Высокоточный таймер событий HPET берёт на себя функции сразу двух устаревших таймеров PIT и RTC. Если у PIT'a было всего 3-канала, то у HPET их уже 32, добрая половина которых лежит в резерве и не используются. Работая на максимально возможной для таймеров частоте 14.31818 MHz он имеет разрешение (время между двумя тиками) равное
Hpet придерживается совершенно иной политики, нежели рассмотренные ранее таймеры. Он имеет один 64-битный счётчик и 32 компаратора, каждый из которых наделён своими регистрами и программируется отдельно. Такой подход позволяет одному устройству генерить до 32-х прерываний IRQ в произвольные интервалы времени.
Сначала мы записываем в регистр значение требуемого тайм-аута, и компаратор начинает сравнивать его с тиками центрального счётчика. В момент когда два эти значения совпадут, на выходе получим IRQ и если канал запрограммирован на периодичность, логика Hpet аппаратно добавит наш тайм-аут к предыдущему значению, тем-самым вычислив время следующего прерывания IRQ. Визуально это можно представить так:
BIOS имеет специальную опцию, которая активирует Hpet-контролёр. Примечательным является то, что если эта опция отключена и таймер не задействован, некоторые функции Win32-API могут возвращать ошибку. В частности это касается привязанной к Hpet функции QueryPerformanceCounter(), а так-же всех остальных, где фигурирует термин Performance. Это самый ламерский способ обнаружения данного таймера в системе.
Программное включение HPET
Начиная с чипсетов ICH(6) таймер можно задействовать, даже если биос не имеет соответствующей опции (например требует обновления). Для этого находим базовый адрес рут-комплекса RCBA (как это сделать рассматривалось в предыдущей части), и сместившись от него к регистру
Значит под регистры Hpet выделяется ровно 1 Кбайт памяти (0x03FF), причём базой для всех регионов служит адрес 0xFED00000. Проблема в том, что на программном уровне эта база не доступна нам как в защищённом Win (нужен драйвер), так и в реальном DOS (ограничение памяти 1Mb). Выкручиваться из данной ситуации при помощи виндозного драйвера не имеет смысла, т.к. системные guard'ы всё-равно прихлопнут его, и в лучшем случае нужно будет бежать за подписью в Microsoft. Так-что выбора у нас нет и придётся набивать руку в реальном режиме, открыв в нём доступ ко-всему 4Gb пространству памяти. Остановимся на этом чуть подробней...
UnReal Mode – преодолеваем 1 Мбайтный барьер
Если в защищённом режиме РМ память виртуальная, то в реальном RM она сегментная. По сути всё доступное ОЗУ делится на сегменты в обоих режимах, только в РМ их размер программно выставлен на максимум, а в RM имеется ограничение 64 Кб. Размеры определяются в дескрипторах сегментов, которые собраны в одну системную таблицу Global-Descriptor-Table или просто GDT. У каждого из шести сегментных регистров процессора
Для поиска таблицы GDT в системном пространстве, процессор имеет специальный регистр GDTR. Размер его 6-байт (на х32), где младшие два хранят длину этой таблицы в байтах, а старшие 4 – её адрес в памяти. Прочитать регистр можно инструкцией ассемблера
А что если взять редко-используемый в RM сегментный регистр FS и модифицировать его дескриптор так, чтобы снять с него 64К ограничение? Правда для этого нужно сначала перейти из реального в защищённый режим, ..сменить в нём дескриптор, и вернуться обратно в реальный. После этих манипуляций, нам будет доступна вся линейная память системы 4Gb, которую можно адресовать через хакнутый регистр FS. В своё время, этот недокументированный режим процессора был открыт сразу несколькими программистами в разных концах света, поэтому и называют его кому-как нравится, например: Un-Real (нереальный, что-то среднее), Flat-Real или плоский, Big-Real и т.д. в этом духе.
На форумах можно встретить горячие споры на эту тему, мол "раз-уж мы перешли в защищённый режим, то почему-бы просто не остаться в нём?". Всё правильно.. только в этом случае мы лишимся системных прерываний, т.к. нужно будет выстраивать новую таблицу
На рисунке ниже, подноготная 8-байтного дескриптора. Чтобы добиться своей цели, нужно сбросить базу в нуль, а лимит наоборот выставить на максимум
Если посмотреть на дескриптор с высоты птичьего полёта, то из 64К сегмента делает 4Gb всего один бит-гранулярности[G] – остальные биты нам не интересны, ..ну если только [E] (expand-down) при котором адрес растёт наоборот, как у стека. Например привилегия DPL напрочь отсутствует в R-моде, и введена только для защиты страниц виртуальной памяти ядра оси, в защищённом режиме. Это-же касается и битов
Поиск и сканирование ACPI-таблицы
Расширенный "интерфейс конфигурации и управления питанием ACPI" существует в системе не только как Power-Managament. Это корневой перечислитель всех устройств на мат.плате, о чём собственно и свидетельствует словосочетание "интерфейс конфигурации.." в его названии. Модуль ACPITLB.bin в прошивке биос содержит таблицу ACPI, которую он сбрасывает в память и заполняет при включении машины. В этой таблице находятся паспорта всех имеющихся на борту девайсов, в числе которых и герой этой статьи – таймер HPET.
Поиск ACPI-таблицы осуществляется по сигнатуре "RSD PTR" в 128-Кбайтном диапазоне адресов реального режима от E0000 до FFFFF. Если он даст результат, мы упрёмся в структуру под названием Root-System-Description-Pointer RSDP. На своей машине я обнаружил её по сегментному адресу F000:A4F0 таким кодом:
По смещению 10h от начала этой структуры, будет лежать линейная база ACPI-таблицы в памяти – как видим адрес её 0х7F7B0000 (у вас может быть другим), а это далеко за пределами жалкого метра реальной моды. Именно поэтому и нужно было модифицируя дескриптор FS открыть всё адресное пространство, чтобы получить доступ к этому адресу:
Вторая версия интерфейса ACPI заточена под 64-битные системы, так-что форматы таблиц у них отличаются. В частности разрядность базовых адресов уже 8-байт вместо 4-х, и соответственно все оффсеты дружненько съезжают с насиженных мест. Поэтому перед разбором таблиц обязательно нужно проверять версию ACPI, которая лежит в поле по смещению[15] от начала RSDP (см.рис.выше), ..иначе рискуем вместо реальных данных получить винегрет.
На этапе тестирования своего кода, можно позвать на помощь утилиту RW. Этот монстр прямо из прикладного уровня Win сдампит на экран любой/закрытый регион системной памяти, от нуля и до самого чердака 0xFFFFFFFF – рекомендую!
Структура таймера HPET
Будем считать, что на этом этапе имеем линейную базу ACPI-таблицы в памяти – теперь в сценарии появляется новый сюжетный поворот.. Продвигаясь поиском вглубь этой базы, мы будем натыкаться на информационные блоки различных устройств. HPET тоже в их числе и наряду со-всеми удостоился чести быть прописанным в этой "коммуналке" (обнаружить его можно по одноимённой сигнатуре). Вот в каком виде поймала структуру "HPET" утилита RW:
Детальное описание всех полей этой структуры можно найти только в спецификации на Hpet, ..лично я её больше нигде не встречал. Помимо прочего, в ней прошита база регистров контролёра таймера, которая лежит по смещению 0х2С и в данном случае равна 0xFED00000. Важно иметь в виду, что на материнской плате могут быть установлены несколько устройств HPET, тогда для каждого из них биос создаст свою структуру. Наиболее значимые её поля представлены на рисунке ниже:
Таким образом, чтобы через густые джунгли системных таблиц пробраться к регистрам таймера Hpet, нужно пройтись по цепочке указателей начиная с реального режима, и заканчивая верхними адресами расширенной памяти 0xFED00000. Это "увлекательное" путешествие закончится тем, что мы получим доступ к следующим регистрам таймера, список которых так-же можно найти только в спеках на HPET:
Здесь я специально упомянул регистры чипсета по адресу RCBA+3404h. Дело в том, что это поле вызывает сомнения и всецело доверять ему не нужно. Самый универсальный вариант поиска базы регистров Hpet – это парсинг таблицы ACPI. Например у меня поле 3404h было приговорено к расстрелу без объяснения причин и в нём лежит зеро, хотя в ACPI-таблице устройство таймера присутствует – имейте это в виду.
Перед практикой, кратко ознакомимся с назначением регистров Hpet.
Как видно из скрина выше, под них выделяется 400h-байт памяти, или ровно 1-Кбайт в привычном нам виде. Каждый из 32-х таймеров одного устройства Hpet наделён тремя 8-байтными регистрами (выделены цветом), которыми можно задавать опции компараторам (тайм-аут, периодичность, прерывания). Четыре первых регистра считаются глобальными и больше относятся к самому устройству Hpet, нежели к отдельным его таймерам. Тема эта достаточно внятно расписывается в спеке на Hpet, поэтому не буду её здесь дублировать.
Практическая часть
Чем созирцать всё это со-стороны, лучше напишем демонстрационным пример того, как можно реализовать чтение/запись регистров Hpet на практике. На чём здесь нужно заострить внимание, так это на преодоление 1-Мбайтного барьера в реальном режиме. Значит подготавливаем таблицу дескрипторов для сегментного регистра FS, а остальные регистры типа CS/DS не трогаем – в скрытой их части уже лежит соответствующий дескриптор с дефолтным лимитом RM =64К.
При переходе в защищённый режим для записи в FS нового значения, обязательно нужно запрещать все прерывания, включая немаскируемые. Если этого не сделать, то при первом-же IRQ (например от часов RTC или таймера PIT) система моментально уйдёт в ребут даже не успев указать на причину. Снять обычные прерывания можно инструкцией
Обратите внимание на минимальное значение тайм-аута
Программировать в этом ключе довольно интересно. Мы становимся очевидцами многих системных событий, о которых можно только мечтать на прикладном уровне Win. Здесь нет надзирателей, зато есть неограниченные возможности знать о которых должен любой низкоуровневый программист. Жалко, что система всё больше абстрагирует нас от реального железа, предлагая в замен непонятно что.
В следующей части мы рассмотрим внутренние счётчики центрального процессора LAPIC и TSC. Не нужно путать их с таймерами, поскольку счётчик не является источником прерываний IRQ. Операционная система выстраивает на их основе программные таймеры, например для функций Win32-API типа Sleep() и прочие. До встречи..
3. ACPI таблицы – таймер HPET.
---------------------------------------HPET – High Precision Event Timers
частота: 14.318180 MHz | счётчик: 64-бита
Высокоточный таймер событий HPET берёт на себя функции сразу двух устаревших таймеров PIT и RTC. Если у PIT'a было всего 3-канала, то у HPET их уже 32, добрая половина которых лежит в резерве и не используются. Работая на максимально возможной для таймеров частоте 14.31818 MHz он имеет разрешение (время между двумя тиками) равное
1/14318180 ~70 ns. Помимо поддержки часов реального времени, Hpet является источником прерываний для мультимедийных приложений, что позволяет им плавно воспроизводить видео/аудио. Не остаётся в стороне и сама система, отслеживая по щелчкам этого таймера разнообразные свои событий типа оснастки "Performance monitor".Hpet придерживается совершенно иной политики, нежели рассмотренные ранее таймеры. Он имеет один 64-битный счётчик и 32 компаратора, каждый из которых наделён своими регистрами и программируется отдельно. Такой подход позволяет одному устройству генерить до 32-х прерываний IRQ в произвольные интервалы времени.
Сначала мы записываем в регистр значение требуемого тайм-аута, и компаратор начинает сравнивать его с тиками центрального счётчика. В момент когда два эти значения совпадут, на выходе получим IRQ и если канал запрограммирован на периодичность, логика Hpet аппаратно добавит наш тайм-аут к предыдущему значению, тем-самым вычислив время следующего прерывания IRQ. Визуально это можно представить так:
BIOS имеет специальную опцию, которая активирует Hpet-контролёр. Примечательным является то, что если эта опция отключена и таймер не задействован, некоторые функции Win32-API могут возвращать ошибку. В частности это касается привязанной к Hpet функции QueryPerformanceCounter(), а так-же всех остальных, где фигурирует термин Performance. Это самый ламерский способ обнаружения данного таймера в системе.
Программное включение HPET
Начиная с чипсетов ICH(6) таймер можно задействовать, даже если биос не имеет соответствующей опции (например требует обновления). Для этого находим базовый адрес рут-комплекса RCBA (как это сделать рассматривалось в предыдущей части), и сместившись от него к регистру
3404h взводим в нём бит[7]. C этого момента рычаги управлением Hpet будут спроецированы в память, а по какому именно адресу – нужно будет указать в битах[1:0]. Биос предлагает нам на выбор один из 4-х регионов памяти и мы можем выбрать любой из них (как-правило первый). В даташите на ICH7 цепочка этих действий расписывается так:Значит под регистры Hpet выделяется ровно 1 Кбайт памяти (0x03FF), причём базой для всех регионов служит адрес 0xFED00000. Проблема в том, что на программном уровне эта база не доступна нам как в защищённом Win (нужен драйвер), так и в реальном DOS (ограничение памяти 1Mb). Выкручиваться из данной ситуации при помощи виндозного драйвера не имеет смысла, т.к. системные guard'ы всё-равно прихлопнут его, и в лучшем случае нужно будет бежать за подписью в Microsoft. Так-что выбора у нас нет и придётся набивать руку в реальном режиме, открыв в нём доступ ко-всему 4Gb пространству памяти. Остановимся на этом чуть подробней...
UnReal Mode – преодолеваем 1 Мбайтный барьер
Если в защищённом режиме РМ память виртуальная, то в реальном RM она сегментная. По сути всё доступное ОЗУ делится на сегменты в обоих режимах, только в РМ их размер программно выставлен на максимум, а в RM имеется ограничение 64 Кб. Размеры определяются в дескрипторах сегментов, которые собраны в одну системную таблицу Global-Descriptor-Table или просто GDT. У каждого из шести сегментных регистров процессора
CS,DS,SS,ES,FS,GS свой 8-байтный дескриптор в этой таблице, а значит мы можем оперировать ими в отдельности.Для поиска таблицы GDT в системном пространстве, процессор имеет специальный регистр GDTR. Размер его 6-байт (на х32), где младшие два хранят длину этой таблицы в байтах, а старшие 4 – её адрес в памяти. Прочитать регистр можно инструкцией ассемблера
sgdt, а записать в него новые значения инструкцией lgdt (store/load соответственно).А что если взять редко-используемый в RM сегментный регистр FS и модифицировать его дескриптор так, чтобы снять с него 64К ограничение? Правда для этого нужно сначала перейти из реального в защищённый режим, ..сменить в нём дескриптор, и вернуться обратно в реальный. После этих манипуляций, нам будет доступна вся линейная память системы 4Gb, которую можно адресовать через хакнутый регистр FS. В своё время, этот недокументированный режим процессора был открыт сразу несколькими программистами в разных концах света, поэтому и называют его кому-как нравится, например: Un-Real (нереальный, что-то среднее), Flat-Real или плоский, Big-Real и т.д. в этом духе.
На форумах можно встретить горячие споры на эту тему, мол "раз-уж мы перешли в защищённый режим, то почему-бы просто не остаться в нём?". Всё правильно.. только в этом случае мы лишимся системных прерываний, т.к. нужно будет выстраивать новую таблицу
IDT для РМ (Interrupt-Descriptor-Table), а это лишние проблемы. Более того, некоторые инструкции процессора являются привилегированными и работают только в реальном режиме – терять их было-бы не разумно.На рисунке ниже, подноготная 8-байтного дескриптора. Чтобы добиться своей цели, нужно сбросить базу в нуль, а лимит наоборот выставить на максимум
0хFFFFFFFF. Однако под лимит здесь выделяется всего 20-бит (синий блок, 2^20=1.048.576 или 1Мб), а для адресации 4Gb нам нужны все 32-бита. Расширить лимит до максимума позволяет бит гранулярности G (выделен красным), который и выставим в единицу. Теперь процессор будет считать лимит не в байтах, а в 4К-блоках (фактически это множитель 4096). В итоге, новоиспечённый дескриптор FS должен иметь у нас 8-байтное значение 0х00CF93000000FFFF, где число F определяет (приоритетный в данном случае) лимит:Если посмотреть на дескриптор с высоты птичьего полёта, то из 64К сегмента делает 4Gb всего один бит-гранулярности[G] – остальные биты нам не интересны, ..ну если только [E] (expand-down) при котором адрес растёт наоборот, как у стека. Например привилегия DPL напрочь отсутствует в R-моде, и введена только для защиты страниц виртуальной памяти ядра оси, в защищённом режиме. Это-же касается и битов
S-W-A, т.к. в реальном режиме нет никакой защиты и подкачки страниц [P]. Однако правила этикета лучше соблюдать и оформлять дескрипторы согласно документации.Поиск и сканирование ACPI-таблицы
Расширенный "интерфейс конфигурации и управления питанием ACPI" существует в системе не только как Power-Managament. Это корневой перечислитель всех устройств на мат.плате, о чём собственно и свидетельствует словосочетание "интерфейс конфигурации.." в его названии. Модуль ACPITLB.bin в прошивке биос содержит таблицу ACPI, которую он сбрасывает в память и заполняет при включении машины. В этой таблице находятся паспорта всех имеющихся на борту девайсов, в числе которых и герой этой статьи – таймер HPET.
Поиск ACPI-таблицы осуществляется по сигнатуре "RSD PTR" в 128-Кбайтном диапазоне адресов реального режима от E0000 до FFFFF. Если он даст результат, мы упрёмся в структуру под названием Root-System-Description-Pointer RSDP. На своей машине я обнаружил её по сегментному адресу F000:A4F0 таким кодом:
C-подобный:
mov ax,0xE000 ;// стартовый сегмент для поиска
xor di,di ;// ..смещение в нём нуль.
@findAcpiTable: ;//
mov es,ax ;// AX в сегментный регистр
cmp dword[es:di],'RSD ' ;// сравнить поле с сигнатурой!
je @found ;// если совпало..
add di,16 ;// иначе: переходим к сл.параграфу памяти
or di,di ;// весь 64К-сегмент проверили?
jnz @findAcpiTable ;// продолжить, если нет..
xor di,di ;// иначе: сбрасываем смещение
add ax,0x1000 ;// переходим к сл.сегменту 0хF000.
or ax,ax ;// уже проверили его?
jnz @findAcpiTable ;// нет - повторить..
call ERROR ;// иначе: прокол!
@found: ;// в ES:DI лежит адрес структуры RSDP
По смещению 10h от начала этой структуры, будет лежать линейная база ACPI-таблицы в памяти – как видим адрес её 0х7F7B0000 (у вас может быть другим), а это далеко за пределами жалкого метра реальной моды. Именно поэтому и нужно было модифицируя дескриптор FS открыть всё адресное пространство, чтобы получить доступ к этому адресу:
Вторая версия интерфейса ACPI заточена под 64-битные системы, так-что форматы таблиц у них отличаются. В частности разрядность базовых адресов уже 8-байт вместо 4-х, и соответственно все оффсеты дружненько съезжают с насиженных мест. Поэтому перед разбором таблиц обязательно нужно проверять версию ACPI, которая лежит в поле по смещению[15] от начала RSDP (см.рис.выше), ..иначе рискуем вместо реальных данных получить винегрет.
На этапе тестирования своего кода, можно позвать на помощь утилиту RW. Этот монстр прямо из прикладного уровня Win сдампит на экран любой/закрытый регион системной памяти, от нуля и до самого чердака 0xFFFFFFFF – рекомендую!
Структура таймера HPET
Будем считать, что на этом этапе имеем линейную базу ACPI-таблицы в памяти – теперь в сценарии появляется новый сюжетный поворот.. Продвигаясь поиском вглубь этой базы, мы будем натыкаться на информационные блоки различных устройств. HPET тоже в их числе и наряду со-всеми удостоился чести быть прописанным в этой "коммуналке" (обнаружить его можно по одноимённой сигнатуре). Вот в каком виде поймала структуру "HPET" утилита RW:
Детальное описание всех полей этой структуры можно найти только в спецификации на Hpet, ..лично я её больше нигде не встречал. Помимо прочего, в ней прошита база регистров контролёра таймера, которая лежит по смещению 0х2С и в данном случае равна 0xFED00000. Важно иметь в виду, что на материнской плате могут быть установлены несколько устройств HPET, тогда для каждого из них биос создаст свою структуру. Наиболее значимые её поля представлены на рисунке ниже:
Таким образом, чтобы через густые джунгли системных таблиц пробраться к регистрам таймера Hpet, нужно пройтись по цепочке указателей начиная с реального режима, и заканчивая верхними адресами расширенной памяти 0xFED00000. Это "увлекательное" путешествие закончится тем, что мы получим доступ к следующим регистрам таймера, список которых так-же можно найти только в спеках на HPET:
Здесь я специально упомянул регистры чипсета по адресу RCBA+3404h. Дело в том, что это поле вызывает сомнения и всецело доверять ему не нужно. Самый универсальный вариант поиска базы регистров Hpet – это парсинг таблицы ACPI. Например у меня поле 3404h было приговорено к расстрелу без объяснения причин и в нём лежит зеро, хотя в ACPI-таблице устройство таймера присутствует – имейте это в виду.
Перед практикой, кратко ознакомимся с назначением регистров Hpet.
Как видно из скрина выше, под них выделяется 400h-байт памяти, или ровно 1-Кбайт в привычном нам виде. Каждый из 32-х таймеров одного устройства Hpet наделён тремя 8-байтными регистрами (выделены цветом), которыми можно задавать опции компараторам (тайм-аут, периодичность, прерывания). Четыре первых регистра считаются глобальными и больше относятся к самому устройству Hpet, нежели к отдельным его таймерам. Тема эта достаточно внятно расписывается в спеке на Hpet, поэтому не буду её здесь дублировать.
Практическая часть
Чем созирцать всё это со-стороны, лучше напишем демонстрационным пример того, как можно реализовать чтение/запись регистров Hpet на практике. На чём здесь нужно заострить внимание, так это на преодоление 1-Мбайтного барьера в реальном режиме. Значит подготавливаем таблицу дескрипторов для сегментного регистра FS, а остальные регистры типа CS/DS не трогаем – в скрытой их части уже лежит соответствующий дескриптор с дефолтным лимитом RM =64К.
При переходе в защищённый режим для записи в FS нового значения, обязательно нужно запрещать все прерывания, включая немаскируемые. Если этого не сделать, то при первом-же IRQ (например от часов RTC или таймера PIT) система моментально уйдёт в ребут даже не успев указать на причину. Снять обычные прерывания можно инструкцией
CLI (clear int), а для немаскируемых придётся лезть в порт 70h и взводить в нём старший бит[7]. Вот пример, где конструкции if 0 / end if позволяют комментировать целые блоки кода (на этапе тестирования):
C-подобный:
org 100h
jmp start
caption db 13,10,' HPET info v.01 '
db 13,10,' ====================================='
db 13,10,' Set 4Gb unreal-mode.............: OK!'
db 13,10
db 13,10,' Old Global-Desc-Table...........: Limit = 0x$'
newMess db 13,10,' New Global-Desc-Table...........: Limit = 0x$'
findAcpi db 13,10
db 13,10,' Find ACPI-table'
db 13,10,' RSDP (pointer)...............: $'
rsdt db 13,10,' RSDT (table linear address)..: 0x$'
revision db 13,10,' ACPI revision................: $'
findHpet db 13,10
db 13,10,' Find HPET struct in ACPI-table..: $'
hptNum db 13,10,' Device 0-31..................: $'
hptVen db 13,10,' Vendor ID....................: 0x$'
countSize db 13,10,' Main counter size (bit)......: $'
hptCount db 13,10,' Total comparators............: $'
hptMinVal db 13,10,' Comparators min.value........: $'
regBar db 13,10,' HPET registers base addr.....: 0x$'
registers db 13,10
db 13,10,' **** HPET registers value ****'
db 13,10,' Name *General Capabilities*'
db 13,10,' Timer resolution.............: $'
legacyRt db 13,10,' Kill legacy PIT/RTC..........: $'
ok db 'Found! $'
mError db 'ERROR! $'
space db '. Base = 0x$'
nsec db ' ns $'
rev db 0 ;// версия ACPI-интерфейса
offs dw 0 ;// указатель на структуру RSDP
acpiBase dd 0 ;// база acpi-таблицы
hpetBase dd 0 ;// база регистров Hpet
oldGdt dq 0 ;// под текущую GDT
align 16 ;// выравнивание на 16-байт границу
descTable dq 0 ;// нулевой декскриптор в новой таблице GDT
dq 0x00cf93000000ffff ;// 4Gb-дескриптор для регистра FS
newGdt dw $ - descTable ;// размер новой таблицы
gdtBase dd 0 ;// будет указателем на неё.
;//******************
start: mov ax,3 ;// ставим в/режим 80х25
int 0x10 ;//
sgdt fword[oldGdt] ;// считать текущий GDTR
;//*****(1) Вычисляем линейную базу новой таблицы GDT *****
xor eax,eax ;//
mov ax,ds ;// её сегмент
shl eax,4 ;// сдвинуть на 4-бита влево
add ax,descTable ;// добавить адрес начала
mov [gdtBase],eax ;// вписать в переменную
;//if 0
lgdt fword[newGdt] ;// обновить регистр GDTR!
;//*****(2) Переход в защищённый режим ********************
;//===== и обновляем дескриптор регистра FS =4Gb ==========
cli ;// запретить все М-прерывания
in al,0x70 ;// ..включая NMI-прерывания
or al,10000000b ;// ..бит[7] =1
out 0x70,al ;// ..
mov eax,cr0 ;// управляющий регистр
or al,1 ;// взвести мл.бит РЕ
mov cr0,eax ;// теперь процессор в P-Mode!
jmp $+2 ;// очистить конвейер CPU
mov ax,8 ;// смещение дескриптора в GDT
mov fs,ax ;// записать его в регистр FS
mov gs,ax ;// ..(можно и в GF для пары)
mov eax,cr0 ;// управляющий регистр
and al,not 1 ;// сбросить в нём бит[1]
mov cr0,eax ;// процессор вернулся в R-Mode!
jmp $+2 ;// сбросить все инструкции с конвейера
xor ax,ax ;// записать любое значение в FS,
mov fs,ax ;// ..чтобы изменения вступили в силу.
in al,0x70 ;// снять запрет с прерываний
and al,not 10000000b
out 0x70,al ;//
sti ;// Set Interrupt.
;//end if
mov dx,caption ;// выводим шапку программы,
call Message ;// ..Un-Real mode OK!!!
;//*****(3) Различная информация для юзера **************
;//======== Old Global Desc-Table =======================
mov ax,word[oldGdt] ;// старое значение регистра GDTR
mov ecx,2 ;// размер в байтах для вывода на консоль
call PrintHex ;// выводим лимит старой таблицы GDT
mov dx,space ;// ..(разделитель)
call Message ;//
mov eax,dword[oldGdt+2] ;// адрес старой таблицы
mov ecx,4 ;// размер = dword
call PrintHex ;// Print EAX
;//==== New Global Desc-Table ====
mov dx,newMess ;// обновлённые данные
call Message ;//
mov ax,[newGdt] ;// лимит
mov ecx,2 ;//
call PrintHex ;//
mov dx,space ;//
call Message ;//
mov eax,[gdtBase] ;// база
mov ecx,4 ;//
call PrintHex ;//
;//*****(4) Find ACPI-table ****************************
;//===== Ищем сигнатуру "RSD" в диапазоне E0000:FFFFF ==
mov dx,findAcpi ;//
call Message ;//
mov ax,0xE000 ;// сегмент для поиска
xor di,di ;// ..смещение в нём нуль.
@findAcpiTable: ;//
mov es,ax ;// AX в сегментный регистр
cmp dword[es:di],'RSD ' ;// сравнить поле с сигнатурой!
je @found ;// если совпало..
add di,16 ;// иначе: переходим к сл.параграфу
or di,di ;// весь 64К-сегмент проверили?
jnz @findAcpiTable ;// повторить, если нет..
xor di,di ;// иначе: сбрасываем смещение
add ax,0x1000 ;// переходим к сл.сегменту 0хF000.
or ax,ax ;// уже проверили его?
jnz @findAcpiTable ;// нет - повторить..
call ERROR ;// иначе!
;//*****(5) Нашли структуру "RSDP" !!! ****************
;//===== выводим мессагу "RSDP (pointer)" =============
@found: mov [offs],di ;// запомнить смещение
push di ;//...^^^
mov bl,byte[es:di+15] ;// версия ACPI
mov [rev],bl ;// запомнить
mov ecx,2 ;// в АХ лежит сегмент RSDP
call PrintHex ;// вывести его на консоль
mov al,':' ;// ..(разделитель)
int 29h ;//
pop ax ;// АХ = смещение
mov ecx,2 ;//
call PrintHex ;//
;//===== выводим мессагу "RSDT (базу ACPI-таблицы)" ===
mov dx,rsdt ;//
call Message ;//
mov di,[offs] ;// офсет
add di,16 ;// сместится к полю 10h
mov eax,dword[es:di] ;// взять его значение
mov [acpiBase],eax ;// запомнить линейную базу!!!
mov ecx,4 ;//
call PrintHex ;// вывести её на консоль.
mov dx,revision ;// версия ACPI
call Message ;//
mov al,[rev] ;//
add al,'0' ;//
int 29h ;//
;//*****(6) Поиск сигнатуры "HPET" в ACPI-таблице *****
;//===== адрес её линейный, поэтому через регистр FS ==
mov dx,findHpet ;//
call Message ;//
;//if 0
mov ecx,(256*1024)/16 ;// область поиска = 256 Kb в параграфах
mov esi,[acpiBase] ;// адрес базы ACPI
and si,0xfff0 ;// делаем его кратным 16
@findHpetSidnature:
cmp dword[fs:esi],'HPET' ;// первый пошёл!
je @f ;// выйти, если нашли
add esi,16 ;// иначе: прыг на сл.параграф
loop @findHpetSidnature ;// промотать ECX-раз..
call ERROR ;// облом :((((
;//end if
;//*****(7) Нашли!!! Парсим структуру HPET ***************
;//===== выводить будем поля 24,2C,34,35 (см.рис.выше) ===
@@: push esi esi esi esi ;// запомнить линейную базу Hpet
mov dx,ok ;//
call Message ;//
mov dx,hptNum ;//
call Message ;//
pop esi ;//
add esi,34h ;// номер устройства Hpet
movzx eax,byte[fs:esi] ;// читаем через FS
call Hex2Asc ;//
mov dx,hptVen ;//
call Message ;//
pop esi ;//
add esi,24h ;// Vendor ID
mov eax,[fs:esi] ;//
mov ebp,eax ;//
shr eax,16 ;//
mov ecx,2 ;//
call PrintHex ;//
mov dx,countSize ;//
call Message ;//
mov eax,64 ;//
bt ebp,13 ;// разрядность счётчика
jc @f ;//
shr eax,1 ;//
@@: call Hex2Asc ;//
mov dx,hptCount ;//
call Message ;//
mov eax,ebp ;// кол-во компараторов
shr eax,8 ;//
and eax,11111b ;//
inc al ;//
call Hex2Asc ;//
mov dx,hptMinVal ;//
call Message ;//
pop esi ;//
add esi,35h ;// мин.значение тайм-аута
movzx eax,word[fs:esi] ;//
call Hex2Asc ;//
mov dx,regBar ;//
call Message ;//
pop esi ;//
add esi,2Ch ;// база регистров Hpet
mov eax,[fs:esi] ;//
mov [hpetBase],eax ;// запомнить её для чтения!!!
mov ecx,4 ;//
call PrintHex ;//
;//if 0
;//*****(8) Читаем регистры HPET *************************
;//=======================================================
mov dx,registers ;//
call Message ;//
mov esi,[hpetBase] ;//
add esi,4 ;// разрешение таймера
mov eax,[fs:esi] ;// ..(оно указывается,
mov ebx,1000000 ;// ..в наносек.блоках).
xor edx,edx ;//
div ebx ;// ЕАХ = длительность тика Hpet!
push edx ;//
call Hex2Asc ;//
mov al,'.' ;//
int 29h ;//
pop eax ;// прицепить тысячные
call Hex2Asc ;//
mov dx,nsec ;//
call Message ;//
mov dx,legacyRt ;// проверить на замену PIT/RTC
call Message ;//
mov esi,[hpetBase] ;//
mov ebx,[fs:esi] ;//
mov al,'1' ;//
bt bx,15 ;//
jc @f ;// OK! если бит[15] =1
dec al ;//
@@: int 29h ;//
;//end if
@exit: xor ax,ax ;// ждём клаву
int 16h ;//
int 20h ;// GAME-OVER!!!
;//*************************************************
;//***** Р А З Л И Ч Н Ы Е П Р О Ц Е Д У Р Ы ******
;//*************************************************
Message: mov ah,9 ;//
int 21h ;//
retn
;//-----------
PrintHex: ;// Вывод ЕАХ в 16-тиричном
cmp ecx,4 ;// Аргумент: ЕСХ = разрядность в байтах
je @f ;//
shl eax,16 ;//
@@: shl ecx,1 ;//
xchg eax,ebx ;//
@@: xor al,al ;//
shld eax,ebx,4 ;//
add al,'0' ;//
cmp al,'9' ;//
jbe @miss ;//
add al,7 ;//
@miss: int 29h ;//
shl ebx,4 ;//
loop @b ;//
retn
;//-----------
ERROR: pop ax ;//
mov dx,mError ;//
call Message ;//
jmp @exit ;//
;//----------- ;//
Hex2Asc: mov ebx,10 ;//
xor ecx,ecx ;//
isDiv: xor edx,edx ;//
div ebx ;//
push edx ;//
inc ecx ;//
or eax,eax ;//
jnz isDiv ;//
isOut: pop eax ;//
add al,30h ;//
int 29h ;//
loop isOut ;//
retn
Обратите внимание на минимальное значение тайм-аута
=14318 – оно привязано к рабочей частоте Hpet = 14.318180 MHz. К сожалению все регистры не влезли в досовское окно, поэтому я вывел только 2 значения из них – это разрешение таймера ~70 ns, и факт отправки к праотцам устаревших PIT и RTC (их функции взял на себя сам Hpet).Программировать в этом ключе довольно интересно. Мы становимся очевидцами многих системных событий, о которых можно только мечтать на прикладном уровне Win. Здесь нет надзирателей, зато есть неограниченные возможности знать о которых должен любой низкоуровневый программист. Жалко, что система всё больше абстрагирует нас от реального железа, предлагая в замен непонятно что.
В следующей части мы рассмотрим внутренние счётчики центрального процессора LAPIC и TSC. Не нужно путать их с таймерами, поскольку счётчик не является источником прерываний IRQ. Операционная система выстраивает на их основе программные таймеры, например для функций Win32-API типа Sleep() и прочие. До встречи..
Последнее редактирование: