ANTICHAT

ANTICHAT (https://forum.antichat.xyz/index.php)
-   Электроника и Фрикинг (https://forum.antichat.xyz/forumdisplay.php?f=21)
-   -   Системные таймеры, Часть[3] – HPET и таблица ACPI (https://forum.antichat.xyz/showthread.php?t=573127)

Marylin 20.04.2020 01:14

Предыдущие части:

1. Общие сведения. Legacy-таймеры PIT и RTC.
2. Шина PCI – таймер менеджера питания ACPI.
3. ACPI таблицы – таймер HPET.
4.Счётчики процессора – LAPIC и TSC.
5.Win - профилирование кода.
---------------------------------------

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. Визуально это можно представить так:


https://forum.antichat.xyz/attachmen...76bf7c4063.png

BIOS имеет специальную опцию, которая активирует Hpet-контролёр. Примечательным является то, что если эта опция отключена и таймер не задействован, некоторые функции Win32-API могут возвращать ошибку. В частности это касается привязанной к Hpet функции QueryPerformanceCounter(), а так-же всех остальных, где фигурирует термин Performance. Это самый ламерский способ обнаружения данного таймера в системе.

Программное включение HPET

Начиная с чипсетов ICH(6) таймер можно задействовать, даже если биос не имеет соответствующей опции (например требует обновления). Для этого находим базовый адрес рут-комплекса RCBA (как это сделать рассматривалось впредыдущейчасти),и сместившись от него к регистру
Код:

3404h
взводим в нём бит[7]. C этого момента рычаги управлением Hpet будут спроецированы в память, а по какому именно адресу – нужно будет указать в битах[1:0]. Биос предлагает нам на выбор один из 4-х регионов памяти и мы можем выбрать любой из них (как-правило первый). В даташите на ICH7 цепочка этих действий расписывается так:

https://forum.antichat.xyz/attachmen...37805547c3.png

Значит под регистры 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 определяет (приоритетный в данном случае) лимит:

https://forum.antichat.xyz/attachmen...07b4006c97.png

Если посмотреть на дескриптор с высоты птичьего полёта, то из 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 открыть всё адресное пространство, чтобы получить доступ к этому адресу:

https://forum.antichat.xyz/attachmen...8f21032e43.png


Вторая версияинтерфейса ACPI заточена под 64-битные системы, так-что форматы таблиц у них отличаются. В частности разрядность базовых адресов уже 8-байт вместо 4-х, и соответственно все оффсеты дружненько съезжают с насиженных мест. Поэтому перед разбором таблиц обязательно нужно проверять версию ACPI, которая лежит в поле по смещению[15] от начала RSDP (см.рис.выше), ..иначе рискуем вместо реальных данных получить винегрет.

На этапе тестирования своего кода, можно позвать на помощь
утилиту RW. Этот монстр прямо из прикладного уровня Win сдампит на экран любой/закрытый регион системной памяти, от нуля и до самого чердака 0xFFFFFFFF – рекомендую!

Структура таймера HPET

Будем считать, что на этом этапе имеем линейную базу ACPI-таблицы в памяти – теперь в сценарии появляется новый сюжетный поворот.. Продвигаясь поиском вглубь этой базы, мы будем натыкаться на информационные блоки различных устройств. HPET тоже в их числе и наряду со-всеми удостоился чести быть прописанным в этой "коммуналке" (обнаружить его можно по одноимённой сигнатуре). Вот в каком виде поймала структуру "HPET" утилита RW:

https://forum.antichat.xyz/attachmen...c90ba7c88f.png

Детальное описание всех полей этой структуры можно найти только в
спецификации на Hpet, ..лично я её больше нигде не встречал. Помимо прочего, в ней прошита база регистров контролёра таймера, которая лежит по смещению 0х2С и в данном случае равна 0xFED00000. Важно иметь в виду, что на материнской плате могут быть установлены несколько устройств HPET, тогда для каждого из них биос создаст свою структуру. Наиболее значимые её поля представлены на рисунке ниже:

https://forum.antichat.xyz/attachmen...14dcafecb5.png

Таким образом, чтобы через густые джунгли системных таблиц пробраться к регистрам таймера Hpet, нужно пройтись по цепочке указателей начиная с реального режима, и заканчивая верхними адресами расширенной памяти 0xFED00000. Это "увлекательное" путешествие закончится тем, что мы получим доступ к следующим регистрам таймера, список которых так-же можно найти только в спеках на HPET:

https://forum.antichat.xyz/attachmen...62e90d3e51.png

Здесь я специально упомянул регистры чипсета по адресу 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
100
h
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
,
10000000
b
;
//    ..бит[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
10000000
b
        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
29
h
;
//
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
29
h
;
//
;
//*****(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
,
34
h
;
// номер устройства Hpet
movzx  eax
,
byte
[
fs
:
esi
]
;
// читаем через FS
call  Hex2Asc
;
//
mov    dx
,
hptVen
;
//
call  Message
;
//
pop    esi
;
//
add    esi
,
24
h
;
// 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
,
11111
b
;
//
inc    al
;
//
call  Hex2Asc
;
//
mov    dx
,
hptMinVal
;
//
call  Message
;
//
pop    esi
;
//
add    esi
,
35
h
;
// мин.значение тайм-аута
movzx  eax
,
word
[
fs
:
esi
]
;
//
call  Hex2Asc
;
//
mov    dx
,
regBar
;
//
call  Message
;
//
pop    esi
;
//
add    esi
,
2
Ch
;
// база регистров 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
29
h
;
//
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
29
h
;
//
;
//end if
@exit
:
xor    ax
,
ax
;
// ждём клаву
int
16
h
;
//
int
20
h
;
// GAME-OVER!!!
;
//*************************************************
;
//***** Р А З Л И Ч Н Ы Е  П Р О Ц Е Д У Р Ы ******
;
//*************************************************
Message
:
mov    ah
,
9
;
//
int
21
h
;
//
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
29
h
;
//
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
,
30
h
;
//
int
29
h
;
//
loop  isOut
;
//
retn

https://forum.antichat.xyz/attachmen...f17a80db23.png

Обратите внимание на минимальное значение тайм-аута
Код:

=14318
– оно привязано к рабочей частоте Hpet = 14.318180 MHz. К сожалению все регистры не влезли в досовское окно, поэтому я вывел только 2 значения из них – это разрешение таймера ~70 ns, и факт отправки к праотцам устаревших PIT и RTC (их функции взял на себя сам Hpet).

Программировать в этом ключе довольно интересно. Мы становимся очевидцами многих системных событий, о которых можно только мечтать на прикладном уровне Win. Здесь нет надзирателей, зато есть неограниченные возможности знать о которых должен любой низкоуровневый программист. Жалко, что система всё больше абстрагирует нас от реального железа, предлагая в замен непонятно что.

В следующей части мы рассмотрим внутренние счётчики центрального процессора LAPIC и TSC. Не нужно путать их с таймерами, поскольку счётчик не является источником прерываний IRQ. Операционная система выстраивает на их основе программные таймеры, например для функций Win32-API типа Sleep() и прочие. До встречи..


Время: 23:28