Статья Системные таймеры, Часть[3] – HPET и таблица ACPI

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

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


hpet_scheme.png


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


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

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

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

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

rsd_ptr.png


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

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


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

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

hpet_RW.png


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

hpetStruct.png


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

hpet_reg.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 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

soft_result.png


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

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

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

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab