Предыдущие части:
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 открыть всё адресное пространство, чтобы получить доступ к этому адресу:
На этапе тестирования своего кода, можно позвать на помощь
Структура таймера HPET
Будем считать, что на этом этапе имеем линейную базу ACPI-таблицы в памяти – теперь в сценарии появляется новый сюжетный поворот.. Продвигаясь поиском вглубь этой базы, мы будем натыкаться на информационные блоки различных устройств. HPET тоже в их числе и наряду со-всеми удостоился чести быть прописанным в этой "коммуналке" (обнаружить его можно по одноимённой сигнатуре). Вот в каком виде поймала структуру "HPET" утилита RW:
Детальное описание всех полей этой структуры можно найти только в
Таким образом, чтобы через густые джунгли системных таблиц пробраться к регистрам таймера 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 (см.рис.выше), ..иначе рискуем вместо реальных данных получить винегрет. На этапе тестирования своего кода, можно позвать на помощь
Ссылка скрыта от гостей
. Этот монстр прямо из прикладного уровня Win сдампит на экран любой/закрытый регион системной памяти, от нуля и до самого чердака 0xFFFFFFFF – рекомендую!Структура таймера HPET
Будем считать, что на этом этапе имеем линейную базу ACPI-таблицы в памяти – теперь в сценарии появляется новый сюжетный поворот.. Продвигаясь поиском вглубь этой базы, мы будем натыкаться на информационные блоки различных устройств. HPET тоже в их числе и наряду со-всеми удостоился чести быть прописанным в этой "коммуналке" (обнаружить его можно по одноимённой сигнатуре). Вот в каком виде поймала структуру "HPET" утилита RW:
Детальное описание всех полей этой структуры можно найти только в
Ссылка скрыта от гостей
, ..лично я её больше нигде не встречал. Помимо прочего, в ней прошита база регистров контролёра таймера, которая лежит по смещению 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() и прочие. До встречи..
Последнее редактирование: