0. Предисловие...................................................  1
1. Защита от копирования, основанная на ключевом диске...........  1
 1.1 MFM запись данных гибкого диска.............................  1
  1.1.1 Разделение двоичных разрядов.............................  2
  1.1.2 Поле синхронизации и байт идентификатора.................  2
  1.1.3 Полный формат дорожки....................................  3
 1.2 Контроллер гибкого диска INTEL 8272A........................  3
  1.2.1 Регистры 8272A...........................................  4
  1.2.2 Обзор команд 8272A.......................................  4
  1.2.3 Описание команд контроллера 8272A........................  7
  1.2.4 Контроллер гибкого диска AT.............................. 10
  1.2.5 "Нормальные" форматы дискеты PC.......................... 11
 1.3 Способ времянезависимой защиты.............................. 12
  1.3.1 Дополнительные или  отсутствующие сектора................ 12
  1.3.2 Слабые двоичные разряды.................................. 13
  1.3.3 Данные в промежутке...................................... 13
  1.3.4 Сектора без метки адреса данных.......................... 14
  1.3.5 Сектора без адресной метки идентификатора сектора........ 14
  1.3.6 Сектора с плохой адресной меткой идентификатора сектора.. 15
  1.3.7 Поле данных, передаваемое по адресной метке индекса...... 15
  1.3.8 Многоскоростные дорожки.................................. 16
  1.3.9 Доступ к данным через промежуток......................... 16
  1.3.10 Сумасшедшие идеи........................................ 17
 1.4 Основанные на таймере способы защиты........................ 17
  1.4.1 Порядок контроля секторов................................ 17
  1.4.2 Измерение скорости пересылки данных...................... 17
 1.5 Защита, основанная на специальных аппаратных средствах...... 18
  1.5.1 Модифицированные MFM-форматы............................. 18
  1.5.2 Переразмещенные дорожки данных........................... 18
  1.5.3 Нестандартные скорости пересылки......................... 18
 1.6 Примеры схем защиты......................................... 19
  1.6.1 Помощник, чувствующий IBM................................ 19
  1.6.2 SuperLok................................................. 19
  1.6.3 Cops CopyLock II......................................... 20
  1.6.4 Щит PC................................................... 20
2. Защита, основанная на жестком диске........................... 21
 2.1 Защита на уровне чипа....................................... 21
 2.2 Защита на уровне BIOS....................................... 23
  2.2.1 Изменения интерлива...................................... 23
  2.2.2 Измененные номера сектора................................ 24
  2.2.3 Неиспользуемые дисковые области.......................... 24
 2.3 Защита на уровне DOS........................................ 24
  2.3.1 Зависимость от номера кластера........................... 24
  2.3.2 Неиспользуемые (зарезервированные) дисковые области...... 25
  2.3.3 Неиспользуемые (округленные до размера кластера)......... 25
3. Система защиты на основной  плате и системе BIOS.............. 25
 3.1 Способ, основанный на данных................................ 25
 3.2 Способ, основанный на времени............................... 25
4. Приложение A. Простая программа 8272A......................... 26
5. Приложение B. Простая программа тестирования HDC.............. 46
6. Приложение C. Как выяснить номер кластера..................... 55
7. Приложение D. Доступ к хвосту файла........................... 60
8. Приложение E. Как отличать основные платы..................... 60
    
                           0. Предисловие

   Следующее обсуждение проблем защиты копии почти полностью базиру-
ется на наших собственных опытах с защищенным от  копирования  прог-
раммным обеспечением PC и личными контактами нашими коллегами.  Сле-
довательно,  приведенные здесь факты не  могут  рассматриваться  как
абсолютная истина (или истина вообще).  Этот документ не руководство
на разработке схем защиты копии,  но краткое  введение  в  проблему.
Кто-либо,  использующий этот документ или поставляемые с ним примеры
для разработки программных или аппаратных средств защиты от  копиро-
вания будет нарушать лицензионное соглашение.
   Никакие аспекты защиты от копирования здесь не скрываются.  Одна-
ко,  вы можете использовать эти данные только согласно закону.  Если
нет закона о программном обеспечении ЭВМ (как в Советском Союзе), вы
должны  всегда  руководствоваться  моральными  соображениями.  Мы не
несем ответственность за любое неподходящее применение  этого  доку-
мента и поставляемых с ним программ.
   Вы увидите, что примеры защиты несколько малочисленны и не всегда
современны.  (Например, замок параллельного порта - в настояще время
наиболее популярное устройство защиты - не рассматривается  вообще).
Принимая  во внимание географическое положение Новосибирска,  в этом
вряд ли есть наша вина.
   Любая работа этого вида всегда базируется на поддержке других эн-
тузиастов,  потому что никто не может приобрести все (или некоторые,
потому  что они продаются за твердую валюту) новые защищенные от ко-
пирования программы. Поэтому Мы выражаем нашу глубокую благодарность
всем, кто обеспечил нас защищенным программным обеспечением PC.

   Сергей Пашкович, Новосибирск                        06 Июнь 1991


       1. Защита от копирования, основанная на ключевом диске

   Этот тип (или,  скорее,  типы) защиты имеет тот же самый  возраст
как  и  сам PC.  Годы развития и сложные аппаратные средства гибкого
диска вызывают появление многочисленных методов защиты. Хотя недавно
появился  метод замка параллельного порта,  методика ключевого диска
кажется традиционной,  что обусловлено, по крайней мере, двумя преи-
муществами. Во-первых,  ключевой  диск  может одновременно использо-
ваться как дистрибутивный диск и во-вторых,  этот тип  защиты  очень
дешевый (но, тем не менее, мало чувситвительный к взлому). Так, клю-
чевые диски еще могут использоваться  для  широко  распространяемого
программного  обеспечения персонального использования.  По-моему,  в
СССР преобладает ключевая защита.
   Чтобы понять способ защиты контроллера гибкого диска (FDC), нужно
знать основные данные и операции FDC.  Давайте теперь рассмотрим их.
(Примечание:  Разделы 1.1 и 1.2 главным образом базируются на  доку-
ментах фирмы INTEL "Интеллектуальная система базы данных, использую-
щая 8272 ","8272 контроллер гибкого  диска  одинарной/двойной  плот-
ности ",  "8272A контроллер гибкого  диска  одинарной/двойной  плот-
ности").

                1.1 MFM запись данных гибкого диска

   Формат изменяемой   частотной   модуляции   (MFM)  гибкого  диска
представлен в системе IBM 34 и часто называется "двойной  плотностью
записи".  Термин " одинарная плотность записи" соответствует обычной
частотной модуляции формата IBM 3740  (FM),  который  использовал  4
мкс,  чтобы  записать один бит данных.  Оригинал MFM записывает один
бит в 2-мкс ячейку, но для пятидюймовых дискет IBM PC использовалась
4-мкс ячейка. Следовательно, бесформатный размер одной дорожки будет
6.1 КБ. Так называемые диски PC "высокой плотности" просто выполняют
2-мкс битовую ячейку оригинальной MFM-спецификации.

                                - 2 -

                 1.1.1 Разделение двоичных разрядов

   Запись данных в формате FM простая: начало каждого одноразрядного
регистра определяется так называемым битом синхронизации,  и  факти-
ческие данные записаны в центре каждой ячейки (бит данных) (См. рис.
1.1.1a ). Такая методика позволяет просто различать биты, но частот-
ные  затраты возрастают вдвое,  так как необходимо сохранять данные.
Однако,  полное удаление  битов  синхронизации  вызывовет  появление
большого  числа нерасшированных нулевых битов из-за произвольных из-
менений скорости вращения диска и генератора котроллера.

           │  ┌┐      ┌┐           │  ┌┐                   │
           │  ││      ││           │  ││                   │
           │  ││      ││           │  ││                   │
     ──────┼───────────────────────┼───────────────────────┼─────
                     '1'                      '0'
                   Рис. 1.1.1a FM-запись данных.

    Из-за такого  недостатка,  наибольшее количество битов в MFM за-
писывается следующим образом: бит синхронизации записывается в нача-
ле  битовой  ячейки,  если в предыдущей и текущей ячейке не записаны
биты (См.  рис.  1.1.1b).  Такое кодирование делает разделение битов
рядов более трудной задачей, но перемещает биты синхронизации близко
к началу битовой ячейки.

           │   |┌┐     │   |┌┐     │   |       │ ┌┐ |      │
           │   |││     │   |││     │   |       │ ││ |      │
           │   |││     │   |││     │   |       │ ││ |      │
     ──────┼───────────┼───────────┼───────────┼───────────┼─────
               '1'          '1'        '0'          '0'
                   Рис. 1.1.1b MFM-запись данных.

    Легко увидеть,  что  значение и FM- и MFM-кодирования зависит от
начальной позиции битовой ячейки.  Например, если мы разместим биты,
как показано подчеркнутыми штриховыми линиями на рис. 1.1.1b, после-
довательность "1100" станет "0010". Поэтому, для обеспения однознач-
ного декодирования, каждое поле данных на дорожке сопровождается по-
лем синхронизации.

           1.1.2 Поле синхронизации и байт идентификатора

    MFM-поля синхронизации состоят из 96 битов нуля (то есть , ячей-
ки с битом синхронизации) и без битов данных),  с последующими тремя
байтами A1h (10100001b).  Нулевые  биты  позволяют  правильно  найти
ячейку  данных,  и  A1  - идентифицировать начало фактических байтов
данных .  Хотя во время форматимрования записывается 12 нулевых бай-
тов,  (это  значение  не может изменяться программным обеспечением),
фактически нужен только 1 байт (8 битов), чтобы синхронизировать би-
товую ячейку. Другие 11 нулевых байтов нужны "на всякий случай".
    FM-поля синхронизации просты - они состоят из 48  нулевых  битов
нуля. (Напротив, FDC нуждно 8 битов для безопасности) .
    Различные поля данных (пользовательские и дополнительные)  могут
различаться  по  единственному байту сразу после поля синхронизации.
Эти байты не могут смешиваться с  данными  пользователя,  даже  если
последние  содержат  точную  последовательность  байтов  синхрониза-
ции/идентификатора,  потому что эти байты (и только эти байты  )  не
используют стандарт синхронизирующих битов.  К сожалению ,  мы имеем
информацию относительно соответствующих битов  синхронизации  только
для FM-кодирования.  Байты идентификатора, по всей видимости, приме-
няются также и в MFM.  (Отметим,  что в поле данных  первым  записан
старший байт).


                                - 3 -
    Описание байта идентификатора поля синхронизации:

   FC D7    адресная метка индекса
   FE C7    метка адреса идентификатора Сектора
   FB C7    данные сектора
   F8 C7    удаленные данные
   FE C7    идентификатор дефектной дорожки IBM

    Хотя в  документации  фирмы  INTEL поле синхронизации всегда от-
носят к части предшествующего промежутка, мы будем рассматривать его
как часть последующего поля данных.

                    1.1.3 Полный формат дорожки

    Точка ссылки  для всех дисковых операций - физическая индексная
метка,  которая генерируется индексным отверстием дискеты. Весь фор-
мат  дорожки  начинающийся с физической индексной метки и может быть
описыван следующим образом:
   - Физическая индексная метка
   - Прединдексный промежуток (GAP 5)
   - Индексная метка адреса (IAM)
   - Послеиндексный промежуток (GAP 1)
   Для n от 1 до N-1, где N - число секторов на дорожке :
   - Идентификатор сектора n
   - Промежуток после идентификатора (GAP 2)
   - Данные сектора n
   - Промежуток после данных (GAP 3)
   Для последнего сектора данных на дорожке :
   - Идентификатор сектора n
   - Промежуток после идентификатора (GAP 2)
   - Данные сектора n
   - Последний промежуток (GAP 4)
   Индексная метка  адреса  (которая не используется для другой цели
8272A) имеет несколько различных  полей  синхронизации:  вместо  A1h
используется C2h (11000010b), за которым следует байт идентификатора
FCh (11111100b).
   Поле идентификатора сектора содержит FEh за которым следуют одно-
байтовые значения C,  H, R, N, где C - номер цилиндра, H - номер го-
ловки,  R  -  номера  сектора  и N - код размера сектора.  Эти байты
(включая FEh )  сопровождаются  16-битовой  циклической  контрольной
суммой (CRC), Размер данных пользователя в следующем поле данных мо-
жет вычисляется как 128 * 2^N, то есть, N=0 определяет размер данных
в  128  байтов,  N=1  - 256 байтов,  N=2 - 512 байтов,  и так далее.
C=H=R=N=FFh определяет дефектную дорожку IBM.
   Поле Данных содержит FBh,  за которым следуют 128 * 2^N байт дан-
ных пользователя и два байта CRC.  Как в идентификаторе сектора, так
и в поле данных CRC вычисляется по формуле:  x^16 +x^12+ x^5+1 с на-
чальным значением FFh (как всегда, старший бит первый) .

              1.2 Контроллер гибкого диска INTEL 8272A

    Rumors сообщает,  что первоначально PC FDC  выполнялся  на  чипе
INTEL 8272. Конечно, это может быть любой совместимый по выводам чип
- 8272A,  NEC цPD765,  и так далее.  Мы никогда не видели такого PC,
так что Мы только можем сообщить об этом.  Однако,  чтобы обеспечить
совместимость по регистрам (хорошая особенность !) с первыми PC, все
большинство  современных  контроллеров  почти одинаково для програм-
миста.
    Команды выполняются 8272A в три последовательных фазы:  фазу ко-
манды,  фазу выполнения и фаза результата. В течение фазы команды ЦП
инструктирует 8272A, что нужно делать. В течение фазы выполнения FDC
выполняет запрашиваемое действие.  Все пересылки данных пользователя
(если они есть) производятся в течение фазы выполнения.

                                - 4 -
    За фазой выполнения следует фазоа результата,  когда FDC возвра-
щает данные состояния.
    В то время как FDC запросы данных в течение фаз  команды  и  ре-
зультата могли  задерживаться  до бесконечности (данные будут сохра-
няться в внутренних регистрах контроллера 8272A) , все запросы FDC в
течение  фазы выполнения должны удовлетворяться немедленно,  или FDC
будет генерировать ошибку ожидания и завершит операцию. Строго гово-
ря , запрос данных не может задерживаться дольше времени пересылки 8
битов. Следовательно, на 360-KБ дисководе , который работает на 250K
(1K  здесь  = 1000) бит в секунду (Kбод),  FDC будет передавать байт
данных каждые 32 цs или 31250 байтов на секунду.
   Хотя сам  8272A  может дейстовоать как в режиме прямого доступа к
памяти (DMA),  так и без него, только относительно быстрый ЦП спосо-
бен  на  передачу данных с такой скоростью.  Выполнение FDC в режиме
не-DMA на дисках двойной плотности требует по крайней мере ЦП  80286
с тактовой частотой 6 MГц,  в то время,  как дискета с высокой плот-
ностью AT будет требовать все 10 МГц.

                        1.2.1 Регистры 8272A

   Чип 8272A связан с ЦП через два регистра:  главный регистр состо-
яния (MSR) и регистр данных (DR). MSR доступен только для чтения. DR
может читаться или записываться,  что определяется  битом  RQM  MSR.
Значение битов в MSR приведено на рис. 1.2.1a.

 ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
 │ RQM │ DIO │ NDM │ CB  │ D3B │ D2B │ D1B │ D0B │
 └──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘
    │     │     │     │     │     │     │     └── Привод 0 занят
    │     │     │     │     │     │     └──────── Привод 1 занят
    │     │     │     │     │     └────────────── Привод 2 занят
    │     │     │     │     └──────────────────── Привод 3 занят
    │     │     │     └────────────────────────── FDC занят
    │     │     └──────────────────────────────── не-DMA режим
    │     └────────────────────────────────────── Ввод/вывод данных
    └──────────────────────────────────────────── Запрос на Мастера
           Рис. 1.2.1a Главный регистр состояния 8272A.

   Разряды DxB  будут  устанавливаться  в  1,  тогда соответствующий
дисковод выполняет команду  установки  или  перекалибровки.  Бит  CB
устанавливается, тогда FDC выполняет операцию чтения или записи. NDM
устанавливается,  когда FDC находится в фазе выполнения и  запрещены
операции  DMA  (это  указываеттакже,  что  большее количество данных
должны подаваться или читать из DR).  DIO будет  единицей,  если  ЦП
должен читать данные из DRА и нулем,  если ЦП должен подавать данные
в DR. RQM = 1 указывает, что DR готов переслать следующий байт. Нап-
равление пересылки зависит от значения DIO.
   8272A сохраняет текущий номер цилиндра  во  внутренних  регистрах
для каждого дисковода, соединенного с ним. Так как дисковод для гиб-
ких дискет не может отличать один цилиндр от другого (кроме цилиндра
0),  это  необходимо,  чтобы  выполнять операции установки,  которые
транслируются FDC в импульс шага дисковода.  Эти регистры  очищаются
сбросом  FDC (но дисковод гибкого диска не будет возвращаться на до-
рожку 0 при сбросе).
   Другие важные  внутренние  регистры содержат время скорости шага,
выгрузки и загрузки головки (см. команду Определить).

                      1.2.2 Обзор команд 8272A

   Команды 8272A могут быть разделены на три группы:  пересылка дан-
ных пользователя,  управление димком и диагностические средства. Пе-
ресылка данных пользователя включает чтение данных, чтение удаленных
данных,  запись  данных,  запись  удаленных  данных  и  три  команды

                                - 5 -
просмотра: просмотр на равно, просмотр на меньше или равно и
просмотр на большег или равно. Команды управления диском включа-
еют перекалибровку,  установку,  чтение состояния дисковода,  чтение
состояния прерывания и форматирование дорожки.  Команды чтения иден-
тификатора сектора и чтения дорожки могут рассматриваться, как диаг-
ностика.  Все команды с нераспознаваемым первым байтом будут обраба-
тываться как недопустимая команда.
   Команды 8272A  можно  легко  идентифицировать  младшим полубайтом
первого байта команды - см.  таблицу 1.2.2 (хотя некоторые из  таких
команд будут недопустимыми).

              Таблица 1.2.2 . 8272A Коды операции 8272А.
   X1  Просмотр на равно
   X2  Чтение дорожки
   X3  Определить
   X4  Определить состояние дисковода
   X5  Записать данные
   X6  Читать данные
   X7  Прекалибровка
   Х8  Опросить состояние прерывания
   X9  Записывать удаленные данные или просмотр на меньше или равно
   XA  Читать идентификатор
   XC  Читать удаленные данные
   ХC  Форматировать дорожку или просмотр на больше или равно
   XF  Установка

   Главная последовательность  выполнения  команды  8272А состоит из
следующих шагов:
   0. Если DMA будет использоваться в операции,  программируется ка-
нал 2 микросхемы 8237A для режима пересылки одиночного байта.  (При-
мечание:  сигнал  счетчика  терминала (TC) из DMA будет вызывать не-
посредственное завершение FDC-операций).
   1. Для  каждого байта в команде ,ждите,  пока бит RQM не станет =
1,  затем проверьте DIO: значение 0 указывает, что FDC готов принять
команду, 1 означает, что ваша команда не распозналась FDC (последую-
щее чтение из DR будет возвращать 80h) или что  вы  уже  подали  все
данные в FDC (а также, что ваша команда недопустима).
   Если ваша команда не имеет фаз выполнения и  кончания  (например,
Определтить), вы останавливаетесь здесь.
   2. Если вы запиустили FDC в режиме не-DMA  и  команда  пересылает
данные в течение фазы выполнения (чтение,  запись,  форматирование),
вы ждете, пока бит NDM равен нулю. Когда NDM станет = 1, для каждого
чтитаемого  или  записываемого байта вы ждете установки бита RQM,  и
тогда записываее следующий байт в (или читаете его из) DR.
   Если вы  не  используете пересылку DMA-режима (или не пересылаете
данные вообще), вы просто переходите к шагу 3.
   3. Конец  фазы выполнения указывается IRQ 6 (int 0eh).  Вы можете
или разрешить 8272A прерывание и обнаруживать его в  вашей  подпрог-
рамме  обработки,  или  можете  постоянно опрашивать регистр запроса
прерывания 8259A (IRR).
   Если команда  не имеет фазы выполнения (Опросить состояние диско-
вода), вы переходите к шагу 4.
   4. В фазе результата вы читаете состояние команды из FDC,  тести-
руя бит RQM (убедитесь, что DIO = 1). 8272A может возвращать до трех
байтов  состояния (которые описываются ниже) наряду с другими данны-
ми, которые будут изменяться от команды к команде.

    ┌───────────┬─────┬─────┬─────┬─────┬─────┬─────┐
    │    IC     │ SE  │ EC  │ NR  │  H  │ DS1 │ DS0 │       ST0
    └───────────┴─────┴─────┴─────┴─────┴─────┴─────┘
    ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
    │ EN  │  0  │ DE  │ OR  │  0  │ ND  │ MW  │ MA  │       ST1
    └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

                                - 6 -
    ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
    │  0  │ CM  │ DD  │ WC  │ SH  │ SN  │ BC  │ MD  │       ST2
    └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
    ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
    │ FT  │ WP  │ RDY │ T0  │ TS  │  H  │ DS1 │ DS0 │       ST3
    └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
          Рис. 1.2.2 0-3 байты состояния контроллера 8272A

                                ST0
   IC - код прерывания:
        00 - Нормальное завершение команды.
        01 - Аварийное завершение команды (операция еще  может  быть
             выполнена, но должны проверяться другие коды условий).
        10 - Недопустимая команда.
        11 - Аварийное завершение (сигнал готовности диска изменился
             в течение выполнения).
   SE - Операция установки закончена.
   EC - Оборудование проверяет ошибку (получен сигнал  неисправности
        от дисковода или цилиндр 0 не найден после  77  импульсов  в
        течение перекалибровки).
   NR -  Не готов (Дисковод стал не готовым в течение чтения или за-
         писи или запрошена сторона 1 для одностороннего диска).
   H  - Адрес головки.
   DS0, DS1 - адрес дисковода.

                                ST1
   EN -  Ошибка  конца дорожки (FDC пытается обратиться к сектору за
        последним сектором дорожки). Этот флаг будет устанавливаться
        в 1 (и,  следовательно,  IC будет равен 01 ), если FDC читал
        сектор, определяемый как параметр команды EOT  и  сигнал  TC
        низкий,так что  каждая операция чтения в режиме не-DMA будет
        c ошибкой EN.
   DE -  Ошибка  данных.  Адресная метка сектора или данных содержит
        недопустимую CRC.
   OR - Ошибка ожидания. 8272A не получил доступа к ЦП или DMA в те-
        чение определенного  интервала  времени  (32  мкс  на  360KБ
        дисководе) и данные были потеряны.
   ND - Сектор не обнаруженный .  Определяемый сектор не был обнару-
        жен в течение 2-х оборотов диска (то есть, с начала операции
        пришли два индексных импульса). Для многосекторных пересылок
        требуется 2-оборотное ожидание для каждого сектора отдельно.
   NW - Ошибка защиты записи .  Сигнал защиты записи был обнаружен в
        течение операций запииси или форматирования.
   MA - Отсутствует метка адреса.  Адресная метка cектора или данных
        не обнаружена.

                                ST2
   CM - Контрольная Метка. Обнаружена удаленная адресная метка в те-
        чение  команды чтения данных или обнаружена адресная метка в
        течение команды чтения удаленных данных,  и бит SK не  уста-
        новлен.
   DD - Ошибка данных.  Неверная CRC  сектора  данных.  Также  будет
        установлен DE.
   WC - Ошибка адресации цилиндра.  Адрес  цилиндра  на  дорожке  не
        соответствует определяемому.
   SH - Успешный просмотр. Условия команды просмотра были удовлетво-
        рены.
   SN  - Просмотр не удовлетворен.
   BC - Ошибка дефектной дорожки.  Аналогично WC,  но адрес цилиндра
        дорожки - FFh.
   MD -  Ошибка  отсутствия  адресной метки.  Также будет устанавли-
         ваться MA.

                                - 7 -

                                ST3
   FT - Неисправность. Дисковод обнаружил неисправность.
   WP - Защита записи.
   RDY - Сигнал готовности дисковода.  На PC и подсистемах дискет AT
        он будет  всегда установлен,  независимо,  готов ли дисковод
        (или установлен вообще).
   T0 - Сигнал нудевой дорожки.
   TS - Двусторонний.  На PC и подсистемах дискет AT он будет всегда
        0.

              1.2.3 Описание команд контроллера 8272A

   Мы приводим все команды 8272A в том порядке, в котором они даны в
таблице "Список команд 8272A" руководства по INTEL  8272A.  Конечно,
это описание не достаточно для написания драйвера гибкого диска. См.
руководство INTEL для более полной информации или Приложения A,  где
дан простой пример.

Чтение данных
─────────────
    Команда:     MT    MFM   SK     0     0     1     1     0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C/H/R/N/EOT/GPL/DTL
    Исполнение: Поля данных на дорожке считываются.
    Результат : ST0/ST1/ST2/C/H/R/N
(Здесь и далее:
   MT -  (MultiTrack)  инструктирует  продолжение операции чтения на
      дорожке 1 того же цилиндра.
   SK  - заставляет FDC пропускать поля удаленных данных.
   HDS - указывает номер головки, используемой в операции.
   DS1, DS0  - представдяют номер дисковода.
   C/N/R/N - идентификатор начального сектора.
   EOT - количество секторов на дорожке.
   GPL - длина межсекторного промежутка.
   DTL - должен быть FFh при установленном MFM.)

Чтение идентификатора сектора
─────────────────────────────
    Команда:     MT    MFM   SK     0     0     1     1     0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C/H/R/N/EOT/GPL/DTL
   Исполнение: Считывается первый корректный  идентификатор  сектора
(первый - значит "первый достигнутый от текущего положения головки",
а не "первый после физической адресной метки")
   Результат: ST0/ST1/ST2/C/H/R/N

Чтение удаленных данных
───────────────────────
    Команда:     MT    MFM   SK     0     1     1     0     0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C/H/R/N/EOT/GPL/DTL
   Исполнение:  Считываются удаленные данные на дорожке.
   Результат: ST0/ST1/ST2/C/H/R/N

Чтение дорожки
──────────────

                                - 8 -
    Команда:      0    MFM   SK     0     0     0     1     0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C/H/R/N/EOT/GPL/DTL
   Исполнение:  Читываются поля данных на дорожке.
   Результат: ST0/ST1/ST2/C/H/R/N

   Эта команда  будет  читать  поля  данных  независимо  от значений
C/H/R/N, хранящихся в идентификаторах секторов. Поле данных, которое
имеет допустимый идентификатор,  может считаться этой командой. Хотя
документация INTEL указывает,  что команда чтения дорожки останавли-
вается,  когда на всей дорожке не обнаружены поля данных или счетчик
секторов достиг значения EOT,  эта команда будет завершаться,  когда
не  обнаружена  метка адреса данных был обнаружен после метки адреса
идентификатора сектора с плохой CRC.
   Команда чтения дорожки принимает ЛЮБОЕ значение N,  так что может
читать межсекторный промежуток (или всю дорожку,  если N  достаточно
большое)  наряду с сектором данных.  Сектора будут считываться в по-
рядке их появления под головкой,  то есть, если дорожка форматирова-
лась с 8 секторами с 512 байтами (интерлив 1:1),  и вы начинаете ко-
манду дорожки чтения с R = 1,  N = 3,  EOT = 4,  сектора 1, 3, 5 и 7
будут считаны.

Запись данных
─────────────
    Команда:     MT    MFM    0     0     0     1     0     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C/H/R/N/EOT/GPL/DTL
   Исполнение:  На дорожку записываются поля данных
   Результат: ST0/ST1/ST2/C/H/R/N

Запись удаленных данных
───────────────────────
    Команда:     MT    MFM    0     0     1     0     0     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C/H/R/N/EOT/GPL/DTL
   Исполнение:  На дорожку записываются поля удаленных данных
   Результат: ST0/ST1/ST2/C/H/R/N

Форматирование дорожки
──────────────────────
    Команда:     MT    MFM    0     0     1     1     0     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               N/SC/GPL/D
   Исполнение: Команда форматирует дорожку с SC  секторами  размером
N. Сектора заполняются байтом D. Значения C/H/R/N для каждого секто-
ра задаются пользователем (как в команде записи данных).
   Результат: ST0/ST1/ST2/C/H/R/N
   ПРИМЕЧАНИЕ: GPL=0 понимается как 100h.

Просмотр на равно
─────────────────
    Команда:     MT    MFM   SK     1     0     0     0     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

                                - 9 -
               C/H/R/N/EOT/GPL/STP
   Исполнение:  Поля  данных на дорожке читаются и сравниваются по-
байтно с данными,  поставляемыми ЦП или DMA.  Если условие просмотра
не удовлетворено, просмотр продолжается в секторе R+STP.
   Результат : ST0/ST1/ST2/C/H/R/N

Просмотр на меньше или равно
────────────────────────────
    Команда:     MT    MFM   SK     1     1     0     0     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C/H/R/N/EOT/GPL/STP
   Исполнение:  аналогично команде "Просмотр на равно"
   Результат : ST0/ST1/ST2/C/H/R/N

Просмотр на больше или равно
────────────────────────────
    Команда:     MT    MFM   SK     1     1     1     0     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C/H/R/N/EOT/GPL/STP
   Исполнение:  аналогично команде "Просмотр на равно"
   Результат : ST0/ST1/ST2/C/H/R/N

Перекалибровка
──────────────
    Команда:      0     0     0     0     0     1     1     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0     0    DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
    Исполнение: пепекалибруется выбранный дисковод.

    Все 4 дисковода могут  перекалибровываться  одновременно.  8272A
может  выполнять операции чтения или записи на любом другом дисково-
де.  Таким  образом,  команда  опроса  состояния  прерывания  должна
использоваться,  чтобы  различать прерывание,  вызванное завершением
перекалибровки от прерывания при завершении операции.
   FDC не будет разрешать любую команду на перекалибруемом дисководе
до того,  как проведен опрос состояния прерывания для установки  за-
вершения прерывания.

Опрос состояния прерывания
──────────────────────────
    Команда:      0     0     0     0     1     0     0     0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
    Результат: STO0/C (Примечание:  когда IC = 10 (недопустимо), C не бу-
дет возвращаться).
   8272A будет генерировать запрос прерывания в любом из следующих
случаев:
   A) Фаза результата начинается для команд чтения/записи/форматиро-
вания/просмотра.
   B) Изменился сигнал готовности на одном из дисководов.
   C) Выполняется установка/перекалибровка.
   D) Требуется не-DMA пересылка данных.
   Программа обработки прерывания может легко отличать эти случаи:
Если NDM = 1,
 Тогда это - запрос пересылки данных,
 Иначе
  Если CB = 1
   Тогда начинается фаза результата,
   Иначе

                                - 10 -
    Если SE = 0
     Тогда это - изменение сигнала готовности,
     Иначе это - конец установки или перекалибровки.

Определить
──────────
    Команда:      0     0     0     0     0     0     1     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                          SRT                    HUT
               └───────────────────────┴───────────────────────┘
                                    HLT                    ND
               └─────────────────────────────────────────┴─────┘
   SRT - интервал скорости шага.  Устанавливает минимальную задержку
         между двумя импульсами шага дисковода.  (1-16 мс, SRT = 0Fh
         соответствует 1 мс). Более старые дисководы PC не выполняли
         3- или 4-мс скорость шага, а некоторые не могут выполнить с
         2- или даже 1-мс скорость шага.
   HUT - время разгрузки головки.  Задержка между завершением опера-
         ции чтения - записи и подъемом головки. (16-240 мс, HUT = 0
         соответствует 16 мс). Оно обычно устанавливается в 240 мс.
   HLT -  время  загрузки головки.  Задержка между командой загрузки
         головки и началом операции  чтения  -  записи.  (2-254  мс,
         HLT=1 - 2 ms). Обычно устанавливается в 2 мс.
   ND  - 0: режим DMA. 1: не-DMA режим.
   Все таймеры  внутри  8272A фактически синхронизируются по сигналу
WR CLK, так что все значения, показанные здесь, правильны для WR CLK
500 КГц,  или скорость пересылки данных составит 500 КБод. Понижение
частоты WR CLK будет удлинять все внутренние счетчики задержек.

Опрос состояния дисковода
─────────────────────────
    Команда:      0     0     0     0     0     1     0     0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
    Результат: ST3.

Установка
─────────
    Команда:      0     0     0     0     1     1     1     1
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                  0     0     0     0     0    HDS   DS1   DS0
               └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
               C
    См. перекалибровку.

                 1.2.4 Контроллер гибкого диска AT

   Первоначальные PC  и  XT контроллеры гибкого диска были способный
поддерживать 4 дискеты.  AT FDC, хотя и совместимы по регстрам, мож-
жет поддерживать только 2, так что можно устанавливать два FDC в AT.
Большинство версий BIOS не поддеоживает второй FDC,  и у нас никогда
не  было  IBM  AT,  чтобы проверить,  что же конкретно делает IBM AT
BIOS.  Первый FDC занимает диапазон адресов 3F0-3F7h, второй FDC на-
ходится в 370-377h.  Вся приведенная здесь информация по первому FDC
также применима и ко второму при установке этого диапазона  адресов.
8272A (или его интегральный аналог) отображается к портам 3F4h (MSR)
и 3F5h (DR).
   Как вы уже видели, 8272A не может сам управлять
состоянием двигателя дисковода, так что порт 3F2 (цифровой регистр
вывода) обеспечивает такие операции. (См. рис. 1.2.4a).


                                - 11 -
 ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
 │  D  │  C  │  B  │  A  │ IE  │ EC  │ DS1 │ DS0 │
 └──┬──┴─────┴─────┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘
    └────────┬────────┘     │     │     └──┬──┘
             │              │     │        └─Выбор дисковода
             │              │     └──────────Контроллер доступен
             │              └────────────────Прерывания разрешены
             └───────────────────────────────Двигатель доступен
            Рис. 1.2.4a Цифровой регистр вывода (3F2h).

   Биты DS1 и DS0 цифрового регистра вывода отменяют бит DS, определя-
емый в команде 8272A, так что можно изменять (заменять) дисковод в те-
чение выполнения команды. Хотя это опасный метод, многие способы защи-
ты от копирования базируются на этой особенности.
   Установка бита EC в нуль сбрасывает контроллер,  так любая опера-
ция (например , форматирование дорожки или запись данных) могут быть
остановлены в любой момент. (Хотя должна соблюдаться особая осторож-
ность, чтобы выполнить резкое завершение без биений).
   Бит IE педназначен для применения в многоконтроллерных  системах.
Т.к.  все  FDC  используют  тот  же самый IRQ 6 и канал 2 DMA,  без-
действующий контроллер может ( или не может - нельзя сказать опреде-
ленно) установить эту линию в низкий уровень, таким образом запрещая
другим контроллерам доступ к системному обслуживанию.
   Бит доступа к двигателю дисковода - другой источник способа защи-
ты (См. 1.4.2).
   Второй дополнительный регистр находится в 3F7 (регистр управления
дискеты).  (Этот регистр не существует на старых XT,  но может часто
быть обнаружен на поздних аналогах). Как мне хорошо известно, только
два младших бита значимы для записи,  и одиночн старший  бит  -  для
чтения.
   3F7 (запись) выбирает скорости пересылки:
   00 - 500 КБод (MFM)
   01 - 300 КБод (MFM)
   10 - 250 КБод (MFM)
   11 - 125 КБод (FM)
Первые три значения (00-10) приведены в руководствах. Хотя последнее
значение неописано,  оно работало на любом AT FDC, с которыми я ког-
да-либо  сталкиваться.  (Это  существенная  скорость пересылки - она
разрешает чтение-запись SD-дисков в дисководе на 360  КБ).  Значения
00-10  предназначаются  для применения с MFM-дискетами,  так что они
имеют импульсы в 1/4 длины цикла.  Синхронизация на 125  КБод  имеет
импульсы в 1/8 длины цикла.
   Т.к. FDC не имеют FM-синхронизации на 175 КБод,  диски  одинарной
плотности  не  могут  записываться на дисководе с высокой плотностью
(360 оборотов/мин.). Благодаря высокой защищености от ошибок FM-фор-
мата данных, диски одинарной плотности еще могут читаться в дисково-
де на 360 об./мин.  FM-дискеты могут быть как читаться,  так  и
писаться на 360Kб (300 об./мин.) дисководе.
   3F7 (чтение) - изменение состояния дисковода:  установка  бита  7
(проверяют al,  80h) значит, что дискета заменялась. Индикатор смены
MEDIA будет сбрасываться после первой установки цилиндра,  отличного
от нулевого.
   Очевидное применение регистра 3F7 в  защите  от  копирования  от-
носится к определению позиции битовой ячейки. Т.к. FDC переключается
на новомю скорость пересылки почти мгновенно,  переключая скорость в
расчетный  момент,  можно  сдвигать битовую ячейку на малую часть ее
длины.  Эта методика разрешает читать любую часть дорожки,  если су-
ществует  по  крайней  мере  одно поле достоверных данных и известно
приблизительно местоположение представляющей интерес области.

               1.2.5 "Нормальные" форматы дискеты PC

   Первые стандарты PC предназначались для  однодвухсторонних  пяти-

                                - 12 -
дюймовых дискет на 40 дорожек.  Следуя спецификации INTEL, они имели
8 512-байтовых секторов на дорожке,  таким образом,  используя  4096
байтов (65%) из 6250-байтовой несформатной емкости дорожки.  Сектора
на дорожке располагались последовательно (интерлив 1:1), как во всех
других  стандартных  форматах.  Позднее диски с 9 и 10 сектораами на
дорожке используют соответственно 74% и 82% общего пространства. Те-
оретическое  ограничение  для  форматируемой емкости дорожки двойной
плотности - примерно 89.2%  (5575 байтов или 10.89 сектора на дорож-
ке).
   Такое увеличение емкости  дисков  сделалось  возможным  благодаря
высокой стабильности вращения в современных приводах.  Хотя допуска-
ется отклонение в 2.5%  от номинального времени вращения, мы никогда
не  встречали дисковод для гибких дискет с отклонением временем вра-
щения  выше  0.2%.  Такая  стабильность  позволяет  уменьшить  длину
межсекторного промежутка.
   Например ,  если возможное отклонение скорости вращения -  ё2.5%,
необходимо резервировать мехсекторный промежуток, достаточный, чтобы
поддерживать любую длину сектора. Предположм, что дискета форматиро-
вана на дисководе с наименьшией скоростью вращения,  и будет модифи-
цирована на дисководе с самой быстрой скоростью  вращения.  В  таком
случае необходимо резервировать по крайней мере ( 512 + 62 ) * ( 2 *
2.5%  ) байт для межсекторного промежутка (62 -  минимальный  размер
заголовка сектора для формата MFM). Это приведет к минимальному про-
межутку в 29 байтов,  так что даже формат 10 секторов на дорожку бу-
дет нормальным при таких отклонениях.
   Чтобы поддерживать совместимость  (по  крайней  мере,  частичную)
формата  на  40 дорожек с 80-дорожечным дисководом высокой плотности
(HD), первый моделируются путем пропуска всех нечетных дорожек. Сле-
довательно,  360K диски,  записываемые в 360K дисководе всегда можно
прочитать на HD-дисководе,  но 360K дискета записываемая в HD-диско-
воде может быть несовместимый с 360K дисководами.
   Т.к. ключевые дискеты почти всегда выполняются на  360K  дисково-
дах,  мы  будем не будем рассматривать все форматы высокой плотности
вообще.

                 1.3 Способ времянезависимой защиты

   Мы будет рассматривать дисковые метки защиты,  которые  не  могут
быть  проверены без применения других устройств,  как сам FDC.  Этот
способ должен работать на любом системном оборудовании с совместимым
FDC.
    Примечание: всегда, когда мы будем упоминать бит-копировщик, это
будет  относиться  последней знакомой нам версии.  Во всяком случае,
это не последние существующие версии. Мы имеем копии (запрещенные по
причинам,  упомянутым  в  предисловии)  Copy  II реализации PC 7.10,
CopyWrite редакции сентября 1988 и TeleDisk версии 2.11.
    Примечание: утверждение "не может воспроизводиться", применяемое
автором к той или другой метке защиты,  указывает не  на  физические
ограничения аппаратных средств PC,  но на ограниченность моих знани-
ий. Поэтому я буду благодарен всем, кто расширит их.

          1.3.1 Дополнительные или  отсутствующие сектора

   Так как первые дискеты МС-ДОС имели много неиспользуемого  прост-
ранства на дорожке, очевидная идея - сохранять дополнительный сектор
на дорожке наряду с стандартными секторами МС-ДОС. Даже гораздо луч-
ше упакованные форматы 10 секторов на дорожке способны размещать до-
полнительный 256-байтный сектор.  Другое место для размещения  таких
дополнительных секторов - 41-я дорожка, которая не используется ДОС,
но к которой может обращаться большинство дисководов. На 80-дорожеч-
ных  дисководах  дополнительные сектора могут скрываться на нечетных
дорожках.
   Теперь, такая метка сама по себе не может рассматриваться как хо-

                                - 13 -
рошая защита от копирования, потому что дополнительные сектора могут
быть легко обнаружены командой чтения идентификатора сектора.  Любой
поставляемый на рынок бит-копировщик (CopyWrite, CopyIIPC, TeleDisk,
и т.д.) способен сделать это. (Хотя, сектора на нечетных дорожках не
будут обнаружены некоторыми программ).  Хорошие  разновидности  этой
схемы приведены в разделах 1.3.4 - 1.3.8.

                   1.3.2 Слабые двоичные разряды

   Другой старый хороший способ защиты - данные , которые будут раз-
личными в последующих операциях чтения (т.н.  "слабые данные"). Сла-
бые данные могут быть любыми данными, расположенными в цифровой "не-
определенной области" или длинным рядом нулей с пропущенными  битами
синхронизации.  В первом случае,  FDC будет управляться произвольным
шумом. В последнем случае, произвольные изменения во вращении диско-
вода  будут перемещать битовую ячейку за синхронизацию (такие данные
не могут записываться без модификации аппаратуры FDC,  так что мы не
будем обсуждать их).
   Один хороший способ генерировать слабые  данные  состoит  в  том,
чтобы помещать их около дефекта поверхности.  Такие слабые данные не
будут исчезать после команды записи данных в этот сектора. К сожале-
нию (ха!), заводские дефекты поверхности теперь редки, так что нужно
делать их вручную.  Мы видели много таких способов  -  от  царапанья
диска  ржавым  гвоздем до прожигания верхности инфракрасным лазером.
Однако, это едва ли программистская проблема.
   Слабые двоичные разряды могли создаваться также программно.  Пер-
вый способ - управлять битом выбора/отмены дисковода в регистре циф-
рового вывода (3F2h).  Например, если вы хотите создавть слабый байт
на дисководе A:,  вы должны начать операцию записи,  подождать, пока
желаемый байт не будет перемещен на дисковод (но не на FDC !) и пов-
торить посылку цифровому регистру значений 1Dh (выбор дисковода 1) и
1Ch  (выбор дисковода 0) во время перемещения байта.  Такая операция
будет модулировать все данные (включение биты синхронизации) записы-
ваемые  на диск прямоугольными импульсами,  перемещая их в неопреде-
ленную область.
   Второй метод требует почти такой же операции с регистром управле-
ния дискеты (3F7h).  Переключение скорости передачи данных будет пе-
реразмещать  данные  и биты синхронизации и деформировать их,  также
перемещая в неопределенную область.
   Бит-копировщик, сталкиваясь со слабыми данными,  встает перед ин-
тересной дилеммой:  являются ли слабые биты следствием неопознанного
дефекта  поверхности,  и операция должна повторяться,  пока первона-
чальные данные не будут восстановлены, или это метка защиты, которая
должна быть воспроизведена во имя его полной славы?
   Из всех бит-копировщиков,  с  которыми  мы  сталкивались,  только
CopyWrite  был  способнен  справляться со слабыми данными,  хотя это
преобразовывает один слабый байт в 10- 12 байтов,  так что  первона-
чальную метку можно легко отличить от копии.  Таким образом,  слабые
данные - не такая плохая вещь для дешевой защиты и будет,  очевидно,
популярна в СССР на отечественном программном обеспечении.

                     1.3.3 Данные в промежутке

   Можно легко  сохранять  небольшое  количество данных в промежутке
после поля данных сектора (GAP 3). Делая этот этот сектор не переза-
писываемым,  метка  будет постоянно находиться в промежутке в полной
сохранности. Верхний край для числа байтов данных, которое записыва-
ется в GAP 3, задается значением GPL в команде формата дорожки. Про-
верка такой метки проста (по крайней мере,  для первого  сектора  на
дорожке) - читают команду дорожки с N на единицу больше фактического
значения в идентификаторе сектора, что будет загружать данные проме-
жутка в память.
   Сохранение данных в промежутке требует больее сложной  процедуры.

                                - 14 -
Предположим,  что вы желаете сохранить 10 байтов данных в промежутке
первого 512-байтового сектора на дорожке 0 головки 0.  Нужно сначала
форматировать  дорожку  0,  определяя  код длины 3 (1024 байтов) для
первого идентификатора сектора, но код длины 2 для параметров форма-
та. Затем начинается операция записи на имитации 1024-байтового сек-
тора,  но останавливается после пересылки 526 байтов (данные сектора
с 512 байтами + 2-байтовая CRC + 10-байтовый промежуток + 4-байтовый
конец для безопасности).  Затем необходимо начать  операцию  формата
дорожки кодом длины 2 (как для параметров формата, так и для иденти-
фикатора сектора) и остановить ее где-нибудь внутри GAP 2 (идентифи-
катор  сектора уже записан,  но поле данных еще не существует).  Как
операция формата,  так и записи могут останавливаться  либо  сбросом
контроллера (посылка 0 на регистр цифрового вывода 3F2h),  или изме-
нением выбранного дисковода (см. 1.3.2).
   К сожалению,  иногда бытвает трудно различить "пустой" промежуток
от промежутка, содержащего данные защиты. Старые дисководы имели от-
носительно  большие коэффициенты ослабления сигнала записи,  так что
промежутки, записываемые на таких дисководах, заполнялись произволь-
ным  мусором,  который можно неверно интерпретировать,  как двоичные
данные защиты.  Проектировщик схем защиты может  улучшить  этот  не-
достаток, используя слабые биты (1.3.2) внутри промежутка защищенных
данных.
   Среди упомянутых бит-копировщиков только CopyWrite  был  способен
обнаруживать данные в промежутке.  Одно любопытное исключение - пер-
вый сектор дорожки 0,  головка 0, который преднамеренно игнорируется
CopyWrite как место для данных промежутка.

               1.3.4 Сектора без метки адреса данных

   Сектора без  метки  адреса  данных  будут   генерировать   ошибку
отсутствия  данных (будет установлен бит MA в ST0 и бит MD в ST2) во
время оперций чтения и записи. Команда чтения идентификатора сектора
будет на таком секторе будет завершаться обычно.
   Сохранение сектора без адресной метки данных требует простой опе-
рации формата,  которая должна останавливаться после записи адресной
метки идентификатора сектора, но перед записью адресной метки данных
(как в 1.3.3).  Должна соблюдаться особаяая осторожность чтобы  уда-
лить  ранее  существовавшую адресную метку данных,  используя диски,
стертые или электромагнитно,  или предварительным форматом с различ-
ной скоростю пересылки данных. Альтернативный способ (который не бу-
дет работать для первого сектора на дорожке):  сначала  форматируйте
дорожку со значением GPL,  выбранным так, чтобы помстить поле данных
там, где синхронизатор адреснаой метки поля данных должен начинаться
на целевом диске.  Во-вторых, переформатируйте дорожку с желательным
значением GPL и остановом прежде,  чем запишется адресная метка сек-
тора.

      1.3.5 Сектора без адресной метки идентификатора сектора

   Сектора без адресной метки идентификатора можно записать в следу-
ющим образом:
   Форматируйте дорожку,  сохраняя  код  длины  N  +  1 для сектора,
расположенного перед интересующим.  Затем считайте содержание  этого
фиктивного  сектора  и запишите его обратно,  останавливая операцию,
когда адресная метка идентификатора сектора уже  переписана,  а  ад-
ресная метка данных - еще нет.  Теперь вы имеете сектор без адресной
метки идентификатора.  Такой сектор не будет принимать участие в лю-
бых FDC-операциях. Он не может быть, вообще говоря, считан любой ко-
мандой без специальных мер, так как он почти не существует.
   Ни один  из проверенных бит-копировщиков не был способный распоз-
навать такие сектора (но любой  аппаратный  бит-копировщик  способен
сделать это), так что отсутствие адресной метки идентификатора может
рассматриваться как хорошая защита .  Однако, трудности проверки де-

                                - 15 -
лают  такую  метку в "живых" схемх защиты крайне маловероятной.  (Но
см. 1.3.10).

   1.3.6 Сектора с плохой адресной меткой идентификатора сектора

   Эта метка отличается от 1.3.5 только степенью искажения  адресной
метки  идентификатора.  Здесь FDC еще способен распознавать адресную
метку идентификатор,  но CRC проверяет неправильно. Эта метка не мо-
жет быть обнаружена командой чтения идентификатора сектора, но будет
устанавливать бит DE в ST1 и сбрасывать бит DD в ST2 в  течение  вы-
полнения команды чтения сектора,  так что значения C/H/R/N будут из-
вестн и проверка не составит никакой проблемы.  Сектор с плохой  ад-
ресной  меткой  идентификатора будет,  однако,  появляться в команде
чтения дорожки.  Тогда точные значения будут C/H/R/N не известны, но
их еще можно получить следующей процедурой: зная точной позиции поля
данных из размеров времени,  можно вычислить приблизительную позицию
адресной метки идентификатора и читать ее, используя методику досту-
па к битовой ячейке (см. 1.2.4) .
   Ценной модификацией этой метки для разработчикаика защиты от коп-
рования будет недопустимая CRC идентификатора сектора  и  отсутствие
адресной метки данных. Такой сектор будет вызывать завершение коман-
ды чтения дорожки,  таким образом предотвращая обнаружение бит-копи-
ровщиком "нормальных" меток такого типа, размещенных после него.
   Чтобы записать плохой идентификатор, нужно останавливать операцию
формата  во  время записи CRC адресной метки идентификатора сектора.
(Отметим,  что FDC имеет внутренний буфер приблизительно на 3 байта,
так что контроллер начинает запись CRC не когда последний байт иден-
тификатора подан в DR, но спустя некоторое время). CopyWrite распоз-
нает сектор с плохой адресной меткой идентификатора и копирует его.

     1.3.7 Поле данных, передаваемое по адресной метке индекса

   Метки защиты этого типа появились,  когда кто-то спросил: что бу-
дет  делать FDC^ если команда формата дорожки будет определять общую
длину данных на дорожке немного болше,  чем дорожка может  вместить?
Рассмотрим   следующие   результаты  полученные  при  форматировании
DD-диска в HD-дисководе с 13-ю 256-байтовыми секторами и  различными
значениями  Gap  3  (смещения  cектора  измерялись по индексному от-
верстию). Обратите внимание, что время вращения используемого диско-
вода - 166.52 мс.

                           Таблица 1.3.7
    ┌───────────────┬────────────────────┬──────────────────────┐
    │   Значение    │  Начало смещения   │ Смещение сектора 13h │
    │     GAP 3     │     1 сектора      │    начало    конец   │
    ├───────────────┼────────────────────┼──────────────────────┤
    │     01h       │       3.849        │   156.981    165.46  │
    │     08h       │       3.844        │   160.334      2.29  │
    │     10h       │    отсутствует     │   164.180      6.14  │
    │     14h       │    отсутствует     │      отсутствует     │
    │     18h       │    отсутствует     │     1.519     10.00  │
    └───────────────┴────────────────────┴──────────────────────┘

   Можно увидеть,  что  имеется  маленькая область в начале дорожки,
которая не используется данными сектора (фактически,  она  использу-
ется  адресной  меткой индекса).  Эта область может перезаписываться
последним сектором на дорожке (как на второй строке таблицы  1.3.7),
но если последний сектор значнительно накладывается на начало дорож-
ки, сектор 1 будет стерт (строка 3). Если начало поля данных послед-
него  сектора будет переходить через индексное отверстие (строка 4),
контроллер запишет GAP 4 до  следующего  местонахождения  индексного
отверстия,  таким  образом перезаписывая все существующие сектора на
дорожке.  Если начало адресной метки идентификатора сектора пререхо-

                                - 16 -
дит через индексное отверстие,  (строка 5),  этот сектор также будет
сохранен, перезаписывая все записанные перед этим сектора.
   Сектор, передающийся по IAM, может вызывать значительные проблемы
для бит-копировщика,  не распознающего  его  существование,  поэтому
большинство меток защиты создаются повторной операцией формата,  ко-
торая будет разрушать данные в секторе за IAM.  Ни одит из проверен-
ных  бит-копировщиков  не был способный воспроизводить такие сектора
(то есть , сохранять и данные сектора и его позицию) , поэтому такая
метка экстенсивно используемый в схемах защиты копии в СССР.

                    1.3.8 Многоскоростные дорожки

   Как мы  видели в предыдущем разделе,  FDC игнорирует IAM в начале
дорожки,  так что сектора могут появляться в любом месте внутри  до-
рожки. Следовательно, очевидная идея - получить участки дорожки, за-
писываемые с различной скоростью пересылки данных.  (лучше всего вы-
полнить это не при переключении скорости пересылки в течение  опера-
ции формата,  а двумя последующими форматами с различной скоростью).
Например,  записать 9 секторов по 512 байтов на дорожке 0 в  300КБод
(HD-дисковод),  один 512-байтовый секторами в 500КБод. (Т.к. эта до-
рожка дискеты самая дальняя, использование высокой скорости пересыл-
ки на DD-дисках не будет слишком уменьшать сохранность данных).
   Хотя различные скорости пересылки данных полностью поддерживаются
AT,  подобная метка может генерироваться при FM- и MFM-форматах дан-
ных (то есть, 9 MFM сектора и 1 FM) на PC и XT. Ни один из проверен-
ных бит-копировщиков не был способный воспроизвести  многоскоростную
дорожку, так что это - хороший способ защиты.
   Однако, нам не пока не встречалась защита,  основанная  на  такой
метке.

               1.3.9 Доступ к данным через промежуток

   Теперь мы подошли к наиболее популярному (и,  возможно, наилучше-
му) способу защиты - доступу к данным  через  промежуток.  Т.к.  все
идентификаторы секторов и поля данных синхронизируются отдельно, две
битовых ячейки в двух последующих полях  могут  перемещаться  произ-
вольно.  Этот сдвиг управляется произвольными изменениями в WR CLK и
скорости вращения дисковода,  так что они не могут контролироваться.
(Конечно,  кно-нибудь может изобрести аналоглвый копировщик, который
будет способен делать это.  Я нкогда не слышатл о таком.) Любая опе-
рация  чтения  дорожки с достаточно большой длиной кода (скажем,  6)
будет давать "след дорожки".  Число различных "следов"  может  грубо
оцениваться  следующим образом:  примите,  что каждое соединение от-
дельно синхронизированных полей может иметь  2  различных  состояния
(очевидно, это недооценка).
   На дорожке обычной DD-дискеты  имеются  19  таких  соединений  (9
идентификаторов,  9 данных,  1 IAM),  так что число различных следов
для каждой дорожки - по крайней мере 2^19 ў 500,000.  Чтобы  cделать
жизнь  еще  более интересной для аналогового копирующего устройства,
можно еще увеличить длину кода (скажем, до 7). Это не увеличит число
следов,  но включит дорожку более одного раза. Т.к. даже лучшее уст-
ройство должно где-нибудь начинать и останавливать операцию,  данные
будет частично разрушены. Это устанавливает одно ограничение на при-
менение защищенного диска:  ключевая дорожка  не  может  перезаписы-
ваться.
   Другая менее  интересная разновидность этой идеи - читать следую-
щую адресную метку идентификатора сектора через GAP 3  и  проверять,
изменилась  ли  она.  Такая  проверка запрещает запись только одного
сектора.  Очевидно,  проектировщик защиты не  ограничтся  получением
"следов"  командой  чтения  дорожки  (и таким образом,  обращением к
уровню чипа аппаратуры).  Это может быть выполнено также  и  простым
добавлением  фиктивного сектора с кодом длины 6 или 7 в конце дорож-
ки, и задча будет выполнена BIUOS'ом.

                                - 17 -
   Ни одна из этих проверок не может воспроизводиться на стандартном
оборудовании PC.  Однако,  большое количество программ, использующих
вторую  разновидность,  можно перехитрить при расширении GAP 3 после
представляющего интерес сектора и при сохранении идентификатора сек-
тора этого промежутка,  как это необходимо при чтении через промежу-
ток. Если программа не проверяет точные позиции секторов на дорожке,
она будет принимать такую "копию" как ключевой диск.

                      1.3.10 Сумасшедшие идеи

   Все обсаждавшиеся  пред этим способы защиты допускали неразрушаю-
щую проверку. Теперь вообразим метку защиты, которая не может прове-
ряться  или  обнаруживаться бит-копировшиком без предварительной за-
писи  на  ключевой  дис.  Вспомните  сектора  без  идентификатора  -
(1.3.5).  Что будет мы будем делать,  если на представляющей интерес
дорожке нет других секторов? (По крайней мере, в той же скорости пе-
ресылки).  Этот сектор не будет читаться до формата дорожки, который
установит адресные  метки  идентификаторов.  Любая  попытка  доступа
внутрь такаой программы отладчиком будет разрушать метку защиты, по-
тому что точный выбор определенного времени остановки операции  фор-
мата будут сбит самим временем отладки.  Этот простой пример показы-
вает что имеются метки защиты,  которые не могут быть прочитаны  без
разрушения  части их содержимого или без знания способа проверки за-
щиты.

              1.4 Основанные на таймере способы защиты

   Все PC оборудованы весьма хорошим чипом таймера  INTEL  8253  или
его  функциональным  аналогом.  Работая с частотой 1,193,180 Гц,  он
позволяет измерять интервалы времени с разрешением нс (то есть,  мо-
жет  измерять время выполнения ОДИНОЧНОЙ команды отделения по 16 386
МГц , который может рассматривать(принимать) к 2.4 гц). Такой таймер
более  чем  адекватен  для  выбора  определенного  времени на гибких
дисках (Передача одиночного байта данных с  самой  большой  скорости
(500 КБ) требует 16 гц), так что точность таких интервалов будет ог-
раничиваться не разрешающей способностью  таймера,  а  произвольными
изменениями скорости вращения.

                  1.4.1 Порядок контроля секторов

   Измеряя время  завершения  команд чтения дорожки,  можно получить
точную позицию каждого сектора на дорожке.  В терминах битов, вы мо-
жете получить точность положения сектора,  по крайней мере,  в однин
бит.  Т.к.  команда формата дорожки FDC управляет позицией сектора с
разрешением в байт, нельзя воспроизвести такой точный порядок секто-
ров.  Таким образом,  расположение секторов может служить как  метка
защиты.  Однако,  эта проверка будет слишком чувствительна к быстро-
действию ЦП и стабильности вращения дисковода и будет часто искажать
ключевой  диск.  Следовательно,  позиция сектора обычно используется
как добавление к другой метке защиты (см. 1.3.9).

              1.4.2 Измерение скорости пересылки данных

   Скорость пересылки данных выбираемая регистром управления дискеты
(3F7h) - только начальная частота,  используемая FDC для декодирова-
ния битов. Специальная аналоговая схема, называемая замкнутый по фа-
зе  цикл  (PLL),  регулирует скорость дорожки,  в которой фактически
прибывают биты.  PLL допускает отклонения,  по крайней мере, в 4% от
центральной  частоты  (разрешенное  отклонение  в  скорости вращения
дисководов - ё2%). Практически, PLL будет справляться с 10%-м откло-
нением на MFM-дискетах и почти 100% на FM.
   Скорость поступления двоичных разрядов определяется  как  угловой
плотностью данных на диске,  так и скоростью вращения дисковода, так

                                - 18 -
что для определения характеристик диска (угловой  плотности  данных)
необходимо измерять время пересылки одного сектора и время вращения.
Максимальная точность такого измерения  может  быть  легко  оценена:
один  512-байтовый сектор будет перемещаться с частотой 500 КБ в те-
чение 8.2 мс,  разрешающая способность таймера 0.84 мкс даст относи-
тельную точность 0.01%. Относительная погрешность в определении вре-
мени вращения будет по крайней мере на порядок меньше.  При  10  для
крайней  безопасности,  0.1%  кажется приемлемой оценкой.  Так,  все
дисководы для гибких дискет PC будут разбиваться на 40 (2 * 2%/0.1%)
различных групп,  и дискеты, записываемые на дисководе одной группы,
можно легко отличать от дискет, записываемых на другой.
   К сожалению (ха !), большинство современных дисководов попадает в
ё0.2%-ю область и,  таким образом, разобьются на 4 группы вместо 40,
почти уничтожая всю "защиту". Некоторые FDC (например, мой старый WD
HDC/FDC 1986 года) разрешают простой трюк с регистром цифрового  вы-
вода (3F2h):  выведите 0Ch в 3F2h (остановить двигатель привода A:),
ждите остановки 10 мс, выведите 1Ch в 3F2h (запустить двигатель при-
вода  A:) и немедленно выполните операцию записи.  Скорость вращения
дисковода будет немного меньше номинальной в течение примерно 20 мс,
разрешая записать один сектор.  Мой новый IDE HDC/FDC 1990 года, од-
нако, ждет, пока скорость вращения не достгнет номинальной, задержи-
вая операцию записи.
   Интересные разновидности  этой метки могут получаться при неболь-
ших модификациях аппаратных средств PC (см. 1.5.3).

     1.5 Защита, основанная на специальных аппаратных средствах

   Я не  специалист  в  аппаратных  средствах защиты копии,  так что
обсуждение почти целиком базируется на rumors и предположениях.

                 1.5.1 Модифицированные MFM-форматы

   Чип FDC  INTEL  8272A  не имеет возможности изменения программным
обеспечением длины GAP1,  GAP2 и GAP5,  но будет принимать дискеты с
этими  промежутками,  отличающимися от стандарта и имеет способность
измерять фактическую длину промежутка.  Например,  GAP2  (промежуток
после идентификатора) может измеряться или командой чтения дорожки с
кодом длины больше фактической длина  сектора,  или  при  измерением
различия  во времени завершения чтения идентификатора идентификатора
и команды чтения данных.  Дискеты с различными значениями промежутка
могли создаваться для PC на других системах ЭВМ, которые могут изме-
нять эти параметры.  (Мы слышали, что некоторое системы DEC способны
делать это.)

                1.5.2 Переразмещенные дорожки данных

   Некоторые контроллеры  и дисководы имеют большее строгое управле-
ние позиционированием головки для чтения/записи,  чем этого  требует
PC. (Я уверен, что требуемые модификации аппаратных средств не слиш-
ком большие.) Это может быть первоначально выполнено  не  для  целей
защиты  от  копирования,  но  чтобы  обеспечивать способность читать
дискеты, записываемые на плохо регулируемых дисководах. (Мы слышали,
что ICL FDC имеет такую способность.) Следовательно, на такой систе-
ме можно подготовить ключевой диск с нестандартным размещением доро-
жек  (и программное обеспечение будет проверять это).  Такой диск не
сможет проверяться на другой системе ЭВМ, так что эта методика могла
бы иметь только ограниченное применение.

               1.5.3 Нестандартные скорости пересылки

   Как мы видели в 1.4.2, FDC допускает значительные изменения в уг-
ловой плотности данных на дорожке (и имеет способность измерять их).
Небольшие  модификации  FDC или дисковода дают возможность управлять

                                - 19 -
вручную средней частотой WR CLK генератора  или  скоростью  вращения
дисковода  и таким образом дают возможность записывать данные с нем-
ного нестандартный (но еще приемлемый для большинства FDC)  скорость
пересылки данных.
   Некоторые изготовители  аппаратных  средств  (например,  научного
оборудования  со встроенными микро-ЭВМ) делают этот "подарок" заказ-
чикам.  (Мы видели 3-дюймовые дисководы со скоростью  360  об./мин.,
которые были несовместимые с "нормальным" на 300 об./мин.)

                      1.6 Примеры схем защиты

   Приводимые здесь  схемы  защиты  основанный на нашем личном опыте
защиты программ (кроме некоторых указанных) не могут рассматриваться
как  законченые  и не способны покрывать все аспекты и версии защиты
программного  обеспечения.  Все  защищенные  программы,  обсужденные
здесь, исследовались с помощью Copy Unprotector Toolkit (C.U.T).

                  1.6.1 Помощник, чувствующий IBM

   Это действительно простая и непосредственная защита, датированный
1986 г.  Она не имеет никакого практического  значения  и  приведена
здесь  только  чтобы  показать  развитие  приемов  защиты  в течение
последних лет.  Это "защищенное" программное  обеспечение  проверяет
присутствие  сектора  8Fh  с  длиной кода 2 на дорожке 39d стороны 1
ключевой дискеты.  Никакие другие проверки не делаются.  Т,к. первые
версии CopyWrite который мы видели, появились в 1985 (и были способ-
ные копировать  такие  метки),  только  надежды  на    "этих
русских" могут быть причиной разработки такой защиты.

                          1.6.2 SuperLok

   Диск с программой SuperLok,  который я смог  приобрести,  датиро-
вался 10 октября 1986 г. Защищенный диск содержал три дорожки, отме-
ченые как "ненормальные" ( все на головке 0). Первой была дорожка 5,
которая имела необычный интерлив и два сектора с короткими данными ,
так что идентификаторы секторов  представляли  следующую  последова-
тельность (все значения шестнадцатеричные):

   05/00/01/02, 05/00/06/02, 05/00/8A/03, 05/00/02/02, 05/00/07/02,
   05/00/65/03, 05/00/03/02, 05/00/08/02, 05/00/04/02, 05/00/09/02,
   05/00/05/02

   Как было замечено,  SuperLok не проверяет эту дорожку вообще, так
что  это представляет вид ловушки для заинтересованного исследовате-
ля.
   Вторая защищенная дорожка на диске была 12-ая. Она содержала сек-
тор  со  скрытой  адресной  меткой  (см.  1.3.6)  с  идентификатором
7B/46/05/00,  который SuperLok проверял  командой  чтения  данных.
Другая  проверка защиты на этой дорожке была выполнена командой чте-
ния дорожки с кодом длины 6 (один 8192-байтовый  сектор)  и  3  (два
4096-байтовых  сектора).  16-битовая контрольная сумма вычислялась в
обоих случаях и сравнивалась с сохраненным значением (см. 1.3.9).
   Третья дорожка защиты содержала четыре специальных сектора.  Пер-
вый из их имел скрытую адресную метку сектора  и  не  имел  адресной
метки данных, предотвращая обнаружение другими программами. Три сек-
тора  имели  скрытые  адресные  метки:  21/47/05/00,  A5/86/81/04  и
EB/76/EE/04. После их проверки, SuperLok читает дорожку с кодами 6 и
3, снова вычисляя 16-битовые контрольные суммы.
   Таким образом,  SuperLok представляет хорошую защиту от копирова-
ния.  Ни один из проверенных бит-копировщиков не был способный копи-
ровать  такой  диск  "буквально"  без  модификации  областей  данных
SuperLok. Вероятно, такая копия просто невозможна (см. 1.3.9).
   Позже я  имел  возможность чтобы делать быстрый glance на диске с

                                - 20 -
SuperLok,  датированном 1990 г. Адресные метки скрытых секторов были
удалены  (?),  доступ к чтению дорожки с меткой защиты заменялся (?)
дополнительным сектором с кодом длины 6 в самом конце дорожки.  Весь
(?) доступ к диску был выполнен BIOS'ом.

                       1.6.3 Cops CopyLock II

   COPS CopyLock  II  основывает проверки защиты на замерах времени.
Он использует дополнительный сектор 00/00/6A/01 на дорожке 0 головки
0, чтобы сохранять переменные данные защиты. ( COPS CopyLock создает
защищенные программные продукты "повышенного качества" без ключевого
диска,  так что нет необходимости сохранять что-либо еще,  кроме се-
рийного номера в теле программы).  Второй дополнительный  сектор  на
дорожке 0 (00/00/F6/02),  у которого была частично перезаписана IAM,
использовался,  чтобы проверить идентификатор адресной метки сектора
1  через промежуток (см.  1.3.9).  Чтобы предотвратить обход защиту,
совершаемый по 1.3.9,  CopyLock проверяет позицию всех (?)  секторов
на  дорожке выбором определенного времени команды чтения идентифика-
тора сектора.
   Дорожки 1 к 6 очень схожи: они имеют два дополнительных сектора в
конце: XX/00/14/01,  который не используется (?),  и XX/00/13/02,  у
которого перезаписана IAM,  и проверяется тем  же  способом,  что  и
00/00/F6/02.  Дорожки  7 к 9 имеют по одному дополнительному сектору
(XX/00/14/01).  Эти сектора не используются  (?).  На  исследованной
дискете  имелся  поверхностный  дефект  в  дорожке 32 головки 1,  но
CopyLock не проверила его наличие, так что мы не думаем что это дру-
гая метка защиты.
   Это - еще один пример хорошей схемы защиты,  хотя  и  достигнутой
средствами,  отличными от 1.6.2. Однако , с помощью C.U.T. ыы смогли
восстановить незащищенную версию  представляющей  интерес  программы
(Это  был  Paradisk  из  JV ParaGraph ) в течение приблизительно 2-х
часов.  Мы не думаем, что разработка CopyLock заняла меньше времени.
Хотя  мы  имеем копию COPS CopyLock III,  еще не было времени для ее
исследования.

                            1.6.4 Щит PC

   Эта дисковая схема защиты отечественного производства, изобретен-
ная Александром Симкиным,  получила большую рекламу в Советском Сою-
зе. Мы имели возможность исследовать пол-дюжины версий этой програм-
мы, и, таким образом, наблюдать ее постепенное развитие.
   Первые версии Щита PC использовали простую метку сектора, переза-
писывая IAM только на дорожке 0.  Это обманывало почти все бит-копи-
ровщики,  которые определяли эту метку как короткие данные (половина
сектора - после индексного отверстия, не так ли ?) и, таким образом,
разрушали ее. Однако, эта метка еще может легко записываться обычны-
ми вызовами BIOS'а (не говоря уже о новых версиях бит-копировщиков),
и эта защита "раскалывалась".
   Следующий предпринятый шаг - обращаться к идентификатору адресной
метки первого сектора через промежуток (влияние COPS?). Первоначаль-
но для этого дополнительного сектора выбирался код длины 2 (512 бай-
тов).  Эта метка "раскалывалась" методикой, описанной в 1.3.9 (снова
,  не требуется ничего кроме BIOS'а). И теперь , последний шаг - код
длинаы   дополнительного   сектора   увеличивался   до   6  (влияние
SuperLok?),  делая трудным (но еще не  невозможным)  воспроизведение
этой  метки  BIOS'ом.  Очевидно,  это  не  препятствие  для хорошего
бит-копировшика, и C.U.T будет легко имитировать такую метку.
   Дальнейшая разработка  этой схемы защиты может пойти по двум нап-
равлениям: включение проверки позиции сектора (как в 1.6.3) или пол-
ное чтение дорожки с промежутками (как в 1.6.2). Второе направление,
хотя требует сохранения всей дорожки для целей защиты, кажется более
вероятным,  потому  что такая разработка не требует доступа к FDC на
уровне чипа (все известные нам версии Щита  PC  пользуются  исключи-

                                - 21 -
тельно процедурами доступа к диску BIOS'а).


               2. Защита, основанная на жестком диске

   Ключи на гибих дисках могут обеспечивать хороший уровень  защиты,
но  они  слегка  утомительны  для  ежедневного применения,  занимают
впустую дисковод и могут быть легко повреждены неподходящим  обраще-
нием.  Следовательно,  большинство защищенных от копирования пакетов
могут устанавливаться на жестком диске, используя любые метки защиты
жесткого диска (эта глава) или метки основной платы (глава 3).  Бла-
годаря существованию большого числа несовместимых  физически  интер-
фейсов жесткого диска, метки жесткого диска - обычно "наименьший об-
щий знаменатель" различных возможностей и более просты в  изготовле-
нии.

                     2.1 Защита на уровне чипа

   Этот уровень  доступа  к жесткому диску весьма маловероятен,  так
что мы только кратко обсдим AT WDC (контроллер винчестерного диска),
с которым Мы имеем небольшой личный опыт обращения. К первому AT WDC
можно обращаться по адресам 1F0-1F7 и 3F6 (этот порт находится в ад-
ресном пространстве первых AT FDC, и является причиной для конфликта
аппаратных средств в системах с FDC и WDC,  выполненных на различных
дополнительных платах). Второй AT WDC занимает адреса 170-177 и 376.
AT WDC использует IRQ 14 (int 76 в  МС-ДОС)  и  не  использует  DMA.
Структура AT WDC выглядит очень схоже с чипом INTEL 82062, действую-
щем в расширенном режиме с 4-байтовым  кодом  ECC  (код  исправления
ошибок), добавленным к каждому сектору. (К сожалению, мы имеем толь-
ко информацию по усовершенствованию на  82062,  так  что  приводимок
описание ниже не может быть точным или законченным).
   1F0 - регистр данных,  используемый для буфера чтения/записи сек-
торов  (512  байтов).  Хотя  для данных сектора может использоваться
словный доступ только байтовый  приемлем  для  чтения/записи  байтов
ECC.
   1F1 - чтение:  регистр флагов ошибок

 ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
 │ BBD │ CRC │  0  │ ID  │  0  │ AC  │ TK0 │ DM  │
 └──┬──┴──┬──┴─────┴──┬──┴─────┴──┬──┴──┬──┴──┬──┘
    │     │           │           │     │     └ АМ данных не найдена
    │     │           │           │     └─── Неудачная перекалибров-
    │     │           │           │         ровка:сектор 0 не найден
    │     │           │           └──────────── Команда прервана
    │     │           └──────────────────────── ID AM не найден
    │     └──────────────────────────────────── Неисправимая  ошибка
    │                                        или AM данных не надена
    └────────────────────────────────────────── Найден  плохой  блок
            Рис 2.1a Регистр флагов ошмбок (1F1)

   1F1 - запись:  пуск уменьшает запись текущего цилиндра / 4,  0FFh
отключает RWC.
   1F2 - регистр счетчика секторов.  Используется,  чтобы определять
число пересылаемых секторов в многосекторных операциях - 1,  то есть
,  значение 1 значит 2 сектора.  WDC будет принимать более одной до-
рожки, состоящей из секторов, модифицируя номера головки и цилиндра,
соответственно.  В течение операции формата дорожки определяет число
секторов на дорожке (0FFh - 255 секторов).
   1F3 - регистр номера сектора.  В течение операции формата дорожки
определяет значение GAP 1 и GAP 3 (см. описание MFM в 1.1.3) минус 3
байта.
   1F4 - 8 младших битов номера цилиндра.  (Tech Help! 4.0 здесь не-
верен).

                                - 22 -
   1F5 - 2 старшего разряда номера цилиндра (биты 0-1 используются).
Некоторые WDC не будут принимать более 2-х бит в этом регистре,  та-
ким образом,  поддерживая жесткие диски с более 1023 цилиндрами,  но
большинство BIOS'ов всегда удаляет эти старшие биты.
   1F6 - выбор сектора/привода/головки.

 ┌─────┬───────────┬─────┬───────────────────────┐
 │ EXT │   SIZE    │ DRV │         HEAD          │
 └──┬──┴────┬──────┴──┬──┴─────┴─────┼─────┴─────┘
    │       │         │              └────── Выбор головки (0-15)
    │       │         └───────────────────── Выбор привода (0-1)
    │       └──────────────────────────────── Код размера сектора:
    │                                           00 =  256 байтов
    │                                           01 =  512 байтов
    │                                           10 = 1024 байтов
    │                                           11 =  128 байтов
    └──────────────────────────────────────── 0: использовать CRC
                                              1: использовать ECC
          Рис. 2.1b SDH (Sector/Drive/Head) регистр (1F6).

   В первоначальной  спецификации  82062  3 бита резервировались для
поля выбора головки и 2 бита - для выбора дисковода,  но эти сигналы
обрабатывались внешними схемами,  так что это действительно не имеет
значения.
   1F7 - чтение : регистр состояния

 ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
 │BUSY │READY│ WF  │ SC  │ DRQ │ ECC │ CIP │ERROR│
 └──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┴──┬──┘
    │     │     │     │     │     │     │     └ Резюме ошибки (OR со
    │     │     │     │     │     │     │       всеми битами 1F1)
    │     │     │     │     │     │     └────── Команда в работе
    │     │     │     │     │     └─────────── Данные ECC-исправлены
    │     │     │     │     └────────────────── Запрос данных (буфер
    │     │     │     │                         ожидает данные)
    │     │     │     └──────────────────────── Установка выполнена
    │     │     └────────────────────────────── Ошибка записи
    │     └──────────────────────────────────── WDC готов
    └────────────────────────────────────────── WDC занят (все дру-
                                                гие биты неверны)
                 Рис. 2.1c Регистр состояния (1F7)

   1F7 -  запись:  регистр  команд.  Здесь записывается код команды.
Система команд AT FDC (которая является надмножеством 82062),  вклю-
чает следующие:
    0     0     0     1           R3 - R0          Установка  дорож-
 └─────┴─────┴─────┴─────┴───────────────────────┘ ки 0

    0     1     1     1           R3 - R0          Установка
 └─────┴─────┴─────┴─────┴───────────────────────┘

    0     0     1     0     I     M     E     T    Читать сектор
 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

    0     0     1     1     0     M     E     T    Писать сектор
 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

    0     1     0     0     0     0     0     0    Просмотр  иденти-
 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ фикатора

    0     1     0     1     0     0     0     0    Форматирование
 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘


                                - 23 -
    1     0     0     1     0     0     0     0    Диагностика
 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘

    1     0     0     1     0     0     0     1    Установка параме-
 └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘ тров привода

   R3-R0 определяют скорость шага дисковода, которая будет 0.035+0.5
* R мс для 5 МГц скорости синхронизации. (Наши эксперименты с AT WDC
указывают, что это значение игнорируется контроллером, который уста-
навливает головку в максимальное возможное быстродействие, используя
аппаратную интерфейсную линию Seek Completed).
   I = 0 разрешает запрос прерывания на активном DRQ,  как  и  после
завершения команды, I = 1 отключает прерывание на активном DRQ.
   M = 1 определяет многосекторную пересылку,  число секторов  нахо-
дится в регистре счетчика сектора.
   E = 1 отключает исправление данных и передает 4 байта ECC  наряду
с данными секторов.
   T = 0 допускает повторные пересылки,  то есть,  если определяемый
сектор  не  был обнаружен после 6 оборотов,  WDC выполняет автомати-
чески команду просмотра идентификатора,  модифицирует внутренний но-
мер цилиндра, выполняет в случае необходимости установку и повторяет
операцию.  При T = 1 ошибка возникает,  если определяемый сектор  не
был обнаружен после 2 оборотов.
   3F6 - запись:  регистр опций.  Запись 02h отключает WDC IRQ 14 от
системной шины,  00h продолжает нормальные операции,  04h сбрасывает
контроллер.
   Система команд WDC гораздо менее гибкая,  чем у 8272, и разрешает
выбирать вручную только номер сектора. Хотя адресная метка идентифи-
катора сектора содержит также значения цилиндра,  головки и размера,
они производятся из регистра команд WDC и  не  можгут  явно  опреде-
ляться.  Поэтому  все  команды WDC (за исключением команды просмотра
идентификатора) используют параметры установки,  эти поля трудно из-
менять.
   Особенности AT WDC,  очевидно,  выгодны для  нормальных  операций
ЭВМ, но сокращают возможности защиты от копирования дополнительными/
пропущенными секторами (см. 1.3.1) и порядком проверки секторов (см.
1.4.1). Исправление данных ECC позволяет выполнить третий трюк защи-
ты жесткого диска, изменяя бит в определенной позиции, который будет
скрываться ECC.
   Все эти  метки  могут  также  генерироваться  и  проверяться  при
использовании  доступа к диску на уровне BIOS,  так что обращение на
уровне чипа обращаются к WDC не дает  никаких  существенных  преиму-
ществ, которые могут покрыться плохой переносимостью программ.

                     2.2 Защита на уровне BIOS

   Это, очевидно, основной уровень для защиты, основанной на жестком
диске.  Достаточная защита из резидентных  программ,  контролирующих
вызовы int 13h BIOS,  может достигаться в этом случае при отслежива-
нии обращения к прерыванию,  13h,  являющегося точкой входа BIOS при
обращении к жесткому диску (см.  пример в Приложении B). Другая при-
чина для такого удаления наблюдения - возможная потребность для точ-
ного выбора точного времени операции в определении интерлива.

                     2.2.1 Изменения интерлива

   Этот метод  очень  сходен  с  защитой гибких дисков,  описанной в
1.4.1.  В отличие от дискет,  где сектора,  как правило, размещаются
последовательно (интерлив 1:1),  сектора жестких дисков часто разме-
щены форматерами низкого уровня прецизионно,  чтобы обеспечивать са-
мую  быструю  из возможных скорость пересылки данных.  Следовательно
более трудно увидеть метку интерлива на жестком диске, чем на диске-
те.

                                - 24 -

                  2.2.2 Измененные номера сектора

   Снова, это  двойник  1.3.1.  Однако,  жесткие  диски обычно имеют
плотнее упакованные  сектора  данных,  и  чтобы  добавить  сектор  с
нестандартным  номером,  нужно  перемещать  один из секторов данных.
Этоа ситуация может легко определяться.

               2.2.3 Неиспользуемые дисковые области

   На уровне BIOS имеются две неиспользуемых области на почти  любом
жестком диске: в самом начале и в самом конце дисковода. Первый сек-
тор каждого жесткого диска (по крайней мере, не SCSI или ESDI) занят
таблицей  разделов,  в то время как все другие сектора на цилиндре 0
дорожки 0 не используются в IBM и схеме разделов Microsoft. Еще, эта
область может быть занята частным программным обеспечением выделения
разделов. Например, DiskManager (dmdrvr.bin) использует сектор 0/0/8
для  расширенной  таблицы разделов,  Olivetty МС-ДОС начинает первый
раздел DOS в 0/0/2, и т.д.
   Вторая неиспользуемая  область - цилиндр диагностики пользователя
, который размещается за в последним дисковым цилиндр на ЭВМ AT и на
последнем  цилиндре  на  PS/2  (Но они еще совместимы,  не так ли?).
Что-нибудь, записанное здесь, имеет очень небольшие возможности сох-
раниться  долго,  потому  что любая дисковая программа теста низкого
уровня может обрабатывать эту дорожку как ей захочется.  (Я  уверен,
Norton  DiskTreet и Gibbson Research's SpinRite определяют здесь оп-
тимальный интерлив).

                      2.3 Защита на уровне DOS

   Защита на уровне BIOS жесткого диска обеспечивает не слишком мно-
го вариантов, в то время как DOS имеет удобные (и относительно пере-
носимые)  способы  вызова  разделов  DOS   (интерфейсом   прерываний
25h/26h,  ExtendedOpen  (6Ch)  в DOS 4.0+),  которые могут использо-
ваться для защиты от копирования.
   Структура файловой системы DOS широко известна,  так что мы будем
только кратко упоминать наиболее существенные ее части.
   Сектор 0 - сектор загрузчика.  Он содержит код который, загружает
операционную систему и таблицу,  описывающую характеристики раздела,
как число секторов, число секторов на кластер, номер FAT, число вхо-
дов в корневой каталог.
   Таблица размещения  файлаов (FAT),  который следует за загрузчим,
содержит 12- или 16-битовые значения для каждого модуля  распределе-
ния (кластера),  указывая номер следующего кластера в цепочке. Обыч-
но, на диске есть более одной (две) FAT, обеспечивая, таким образом,
сохранность данных. (Однако, DOS не обрабатываем копии FAT отдельно,
а просто сохраняет первую FAT в дополнительных  областях,  дублируюя
любую  ошибку в 1-ой копии во всех FAT,  так что защита,  в действи-
тельности, воображаемая).
   Корневой каталог  (после  последней копии FAT) содержит таблицу с
32 байтами (называемый входом каталога) для каждого файла в  катало-
ге. Вход каталога содержит имя файла, размеры, дату модификации фай-
ла и первый номер кластера.  Другие каталоги используют те же  самые
методы распределения, как обычные файлы.

                2.3.1 Зависимость от номера кластера

   Стандартные инструментальные  средства  ДОСА  не  разрешают  пок-
ластерный контроль файлов,  так что эта информация  может  использо-
ваться чтобы кодировать изображение программы и/или данных.  Старто-
вый номера кластера файла может быть  получен  CP/M-образом,  вызвав
прерывание 11h ( FindFirst через FCB). (Вызовы CP/M могут скрываться
от большинства наблюдателей выполнением  дального  вызова  к  адресу

                                - 25 -
0:0C0h  с  кодом  функции в CL вместо AL).  Выяснение других номеров
кластеров требует просмотра FAT (См. пример в Приложении C).

     2.3.2 Неиспользуемые (зарезервированные) дисковые области

   В действительности, имеется только одна такая область по смещению
0Ch  входа  каталога (длиной 10 байтов).  К сожалению ,  эта область
часто используется "DOS-совместимый" операционными системами. Напри-
мер,  Digital  Research  DOS использует это поле для хранения пароля
файла,  PC-MOS/386 (Software Liks)  запоминает  здесь  идентификатор
владельца файла, права доступа и дату/время создания.
   Другая зарезервированная область,  которая может существовать  на
диске,  является остатком последнего сектора в FAT 1, который сохра-
няется DOS (и даже копируется во все другие копии FAT).

       2.3.3 Неиспользуемые (округленные до размера кластера)
                          дисковые области

   Т.к. DOS  распределяет дисковое пространство в кластерах (которые
содержат 2^N секторов),  в то время как размер  файла  измеряется  в
байтах,  большинство файлов имеет неиспользуемым (и обычно невидимый
на уровне файловой системы) хвост,  который может использоваться для
целей  защиты.  Курьезная ошибка в DOS,  которая разрешает установку
(функция DOS 42h) за концом файла,  делает  доступ  к  этому  хвосту
простым даже из языков высокого уровня (См. Приложение D).

        3. Система защиты на основной  плате и системе BIOS

   Другая часть PC, которая, наряду с дискетой и жестким диском, яв-
ляется всегда доступной для защиты от копирования - основная  плата.
Хотя основные платы теперь производятся в тысячами, почти каждыая из
их имеет (или может приобрести) индивидуальные качества.

                  3.1 Способ, основанный на данных

   Каждая основная плата имеет собственный BIOS.  Так что это  может
использоваться  для  защиты  копии ,  хотя мы рекомендуем это прежде
всего для производителей аппаратных средств,  а не  для  независимых
разработчиков программного обеспечения.  (Так, начинка BIOS содержит
много неописанных функций и таблиц,  идентификация которых может яв-
ляться видом защиты от копирования).
   Другая область данных доступная на основных платах -
энергонезависимая память CMOS, которая, как правило имеет много
(около 6) неиспользуемого пространства. Некоторые
виды чипов (например,  из C&T) могут отображать часть памяти CMOS  в
адресном пространстве системного ОЗУ,  таким образом, создавая появ-
ление "программ, перепрограммирующих ROM BIOS".

                 3.2 Способ, основанный на времени

   Более интересные  метки  защиты - основанный на определении внут-
реннего времени основной платы. Три основных подсистемы доступны для
таких измерений:  ЦП, память и ввод/вывод (см. Приложение E). Разли-

    Таблица 3.2. Метки основной платы на четырех различных ЭВМ.
  ┌─────────────────────┬────────────┬─────────────┬───────────┐
  │      Система        │     ЦП     │   Память    │  Вв./выв. │
  ├─────────────────────┼────────────┼─────────────┼───────────┤
  │ 25 МГц 80386 (A)    │   35972    │    24576    │    47292  │
  │ 25 МГц 80386 (B)    │   35972    │    24576    │    49154  │
  │ 20 МГц 80386        │   44958    │    30112    │    59990  │
  │ 12 МГц 80286        │    3544    │    41018    │    46646  │
  └─────────────────────┴────────────┴─────────────┴───────────┘

                                - 26 -

чия могут быть удивительно большими (см. таблицу 3.2).
   Значения, показанные  в таблице 3.2,  средние для многих запусков
программы из Приложения E.  Фактические значения отличаются от сред-
него приблизительно ё4. Системы (A) и (B) имели два последовательных
заводских номера,  но еще могут легко отличаться меткой ввода/вывода
(порт 0Ch,  использовался 1-ый контроллер DMA). Необходимо также от-
метить, что метка ввода/вывода очень чувствительна к режиму действия
ЦП,  в то время, как метки ЦП и памяти почти одинаковы в обоих режи-
мах.

              4. Приложение A. Простая программа 8272A

   Текст программы находится в test_fdc.c.  Для компиляции требуется
Турбо-С 2.0 и Турбо Ассемблер (любая версия). Минимальные аппаратные
средства,  требуемые чтобы выполнить этот пример - 8  МГц/0  WS  ЭВМ
80286  оборудованная,  по крайней мере,  одним дисководом для гибких
дискет, привод для выполнения теста определяется параметром програм-
мы  (0 для A:,  1 для B:).  Этот тест может использоваться на втором
FDC,  что обеспечиваться заменой FDC_BASE в исходном файле на 0x370.
Команда  'T'  команда  (анализировать дорожку) будет работать непра-
вильно с дисководами на 300 оборотов в минуту  (360K,  720K,  1.44M)
пока  REVOLUTION_TIME  в источнике программы не не будет заменена на
200L*2*1193 (но тогда она будет работать неправильно с 1.2M дисково-
дами).

#if ! defined( __TURBOC__ )
#error Программа использует Turbo C 2.00 с внутренним асемблером!
#else
#pragma inline
#endif
#if ! defined( __SMALL__ )
#error Компилировать на модели Small!
#endif
#include <stdio.h>
#include <dos.h>
#include <bios.h>
#include <limits.h>
#include <stdlib.h>
#include <ctype.h>
#include <setjmp.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <mem.h>
/*
 * FDC_BASE выбирает номер контроллера.
 * 0x3F0 для первого FDC, 0x370 для второго FDC.
 */
#define FDC_BASE        0x3F0
#define FDC_MSR         (FDC_BASE+4)
#define FDC_DATA        (FDC_BASE+5)
#define FDC_DIGITAL     (FDC_BASE+2)
#define FDC_RATE        (FDC_BASE+7)
/*
 * RATE_??? должен быть установлен по FDC_RATE для  выбора  значения
   WR CLK . */
#define RATE_250        2
#define RATE_300        1
#define RATE_500        0

/*
 *  Следующие определения описывают формат регистра состояния 8272A.

                                - 27 -
 */
typedef struct        {
        unsigned        ds : 2 ;        /* Выбор привода        */
        unsigned        h  : 1 ;        /* Выбор головки        */
        unsigned        nr : 1 ;        /* Не готов             */
        unsigned        ec : 1 ;        /* Проверка оборудования*/
        unsigned        se : 1 ;        /* Установка закончена  */
        unsigned        ic : 2 ;        /* Код прерывания       */
        } ST0 ;

typedef struct        {
        unsigned        ma : 1 ;        /* Нет адресной метки   */
        unsigned        nw : 1 ;        /* Защита записи        */
        unsigned        nd : 1 ;        /* Сектор не найден     */
        unsigned        _1 : 1 ;
        unsigned        or : 1 ;        /* Ошибка переполнения  */
        unsigned        de : 1 ;        /* Ошибка данных        */
        unsigned        _2 : 1 ;
        unsigned        en : 1 ;        /* Ошибка конца дорожки */
        } ST1 ;

typedef struct        {
        unsigned        md : 1 ;        /* Нет адресной метки   */
        unsigned        bc : 1 ;        /* Плохая дорожка       */
        unsigned        sn : 1 ;        /*Сканирование не успешно*/
        unsigned        sh : 1 ;        /* Сканирование успешно */
        unsigned        wc : 1 ;        /* Неверный цилиндр     */
        unsigned        dd : 1 ;        /* Ошибка данных сектора*/
        unsigned        cm : 1 ;        /* Контрольная метка    */
        unsigned        _1 : 1 ;
        } ST2 ;

typedef struct        {
        unsigned        ds : 2 ;        /* Выбор привода        */
        unsigned        h  : 1 ;        /* Выбор головки        */
        unsigned        ts : 1 ;        /* Двухсторонняя        */
        unsigned        t0 : 1 ;        /* Дорожка 00           */
        unsigned        rdy: 1 ;        /* Готов                */
        unsigned        wp : 1 ;        /* Защита записи        */
        unsigned        ft : 1 ;        /* Неисправность        */
        } ST3 ;

/*
 *      FDC_MSR fields
 */
typedef struct        {
        unsigned        A_buzy : 1 ;
        unsigned        B_buzy : 1 ;
        unsigned        C_buzy : 1 ;
        unsigned        D_buzy : 1 ;
        unsigned        cb : 1 ;        /* Котнтроллер занят    */
        unsigned        ndm : 1 ;       /* Режим не-DMA         */
        unsigned        dio : 1 ;       /* Направления I/O      */
        unsigned        rqm : 1 ;       /* Требуется для мастера*/
        } STATE ;
/*
 *      Параметры выбранной команды.
 */
typedef struct        {
        unsigned        hut : 4 ;       /*Время выгрузки головки*/
        unsigned        srt : 4 ;       /* Уровень шага         */
        unsigned        nd  : 1 ;       /* не-DMA               */
        unsigned        hlt : 7 ;       /*Время загрузки головки*/

                                - 28 -
        } SPECIFY ;

/*
 *      Блок параметров дискеты BIOS, адресуемый вектором 1Eh.
 */
typedef struct        {
        SPECIFY specify ;
        char    motor_wait ;            /* 55-мс инкремены      */
        char    sector_size ;           /* Длина кода           */
        char    EOT ;
        char    GAP1 ;                  /* запись/чтение        */
        char    DTL ;                   /* FFh                  */
        char    GAP2 ;                  /* формат               */
        char    fill_char ;             /* формат               */
        char    head_settle ;           /* мс                   */
        char    motor_startup ;         /* 1/8 сек.             */
        } BIOS_DISK ;

/*
 *      Параметры операций чтения/записи 8272A
 */
typedef struct        {
        char        c ;
        char        h ;
        char        r ;
        char        n ;
        char        eot ;
        char        gpl ;
        char        dtl ;
        } RW_INPUT ;

/*
 *      Тиков до выключения мотора привода
 */
#define MOTOR_COUNT     (*(unsigned char far *)MK_FP(0,0x440))

/*
 *      write_data() and read_data() пишет/читает данные 8272A
 */

#define write_data(x)                \
        {\
        while( ( inportb( FDC_MSR ) & 0xc0 ) != 0x80 ) ;\
        outportb( FDC_DATA, (x) ) ;\
        }

#define read_data(x)               \
        {\
        while( ( inportb( FDC_MSR ) & 0xc0 ) != 0xc0 ) ;\
        (x) = inportb( FDC_DATA ) ;\
        }

/*
 *      Определение LOOK_TIME разрешение времени исполнения команд
 *      8272A.
 */
#define LOOK_TIME

/*
 * Определение HANG_ABORT разрешение жесткого завершения процедуры
 * после ABORT_WAIT тиков системного таймера.
 */
#define HANG_ABORT

                                - 29 -

#ifdef        LOOK_TIME
        #define START   start_time = get_exact_time()
        #define END     elapsed_time = get_exact_time() - start_time
#else
        #define START
        #define END
#endif

#ifdef        HANG_ABORT
        #define HANG_START(x)   hang_start(x)
        #define HANG_END        hang_end()
#else
        #define HANG_START(x)
        #define HANG_END
#endif

char    FDD     = 0 ;                   /*Номер текущего привода*/

#define ABORT_WAIT        (2*16)        /* Около 2 сек.         */

#define BUFFER_SIZE        (32*1024)

#define MAX_SECTORS        40

/*
 *      REVOLUTION_TIME установлена для 1.2 Mb AT HD привода.
 *      Для всех остальных приводов меняется от 166L до 200L
 */
#define REVOLUTION_TIME (166L*2*1193)

BIOS_DISK        far        *bios_disk ;
union        {        char        c ;        ST0        x ;  } st0 ;
union        {        char        c ;        ST1        x ;  } st1 ;
union        {        char        c ;        ST2        x ;  } st2 ;
union        {        char        c ;        ST3        x ;  } st3 ;
unsigned char   r_c ;        /* # цилиндра из предыдущей операции */
unsigned char   r_h ;                /* Головка ...               */
unsigned char   r_r ;                /* Сектор ...                */
unsigned char   r_n ;                /* Размер кода сектора       */
unsigned long        start_time ;
unsigned long        elapsed_time ;
unsigned char   *buffer ;
unsigned        buffer_bytes = 0 ;
volatile unsigned long bios_time = 0 ;
jmp_buf         hang_reset ;
char                *current_function ;
unsigned        rest_ticks ;            /* До прекращения         */
char                abort_on_hangup ;
FILE                *out ;
unsigned char   mfm = 0x40 ;            /* 0 для FM, 0x40 для MFM */

void        interrupt        (*old_int_0eh)( void ) ;
void        interrupt        (*old_int_08h)( void ) ;

/*
 * get_exact_time() возвращает 32-битовое время после пуска програм-
 * мы. Для перевода в секунды делить на 2,386,360.
 */
static        unsigned        long
get_exact_time( void )
{
asm        pushf

                                - 30 -
asm        sti
asm        jmp        $+2
asm        cli
asm        mov        al, 0c2h
asm        out        43h, al
asm        jmp        $+2
asm        jmp        $+2
asm        in        al, 40h
asm        mov        bl, al         /* Байт счетчика состояния   */
asm        jmp        $+2
asm        jmp        $+2
asm        in        al, 40h
asm        mov        ah, al
asm        jmp        $+2
asm        jmp        $+2
asm        in        al, 40h
asm        xchg        ah, al
asm        neg        ax
asm     mov     dx, word ptr bios_time
asm     rcl     bl, 1
asm     cmc
asm     rcl     dx, 1
asm        popf
return( ( (unsigned long)_DX << 16 ) + _AX ) ;
}

void interrupt
int_0eh( void )
{
/*
 *      Это никогда не должно произойти!
 */
outportb( 0x20, 0x20 ) ;
printf( "Неопределенное прерывание FDC!\n" ) ;
}

void
report_st0( void )
{
printf( "Привод %c, головка %d :\n", 'A' + st0.x.ds, st0.x.h ) ;
switch( st0.x.ic ){
        case 0: break ;
        case 1:
                printf( "Ненормальное завершение операции\n" ) ;
                break ;
        case 2:
                printf( "Неверная команда\n" ) ;
                break ;
        case 3:
                printf( "Изменилось условие готовности диска\n" ) ;
                break ;
        }
if( st0.x.se )
        printf( "Установка завершена\n" ) ;
if( st0.x.ec )
        printf( "Поломка привода\n" ) ;
if( st0.x.nr )
        printf("Привод не готов или выбор головки 1 на одностороннем
               приводе\n");
}

void
hang_start( char *f )

                                - 31 -
{
current_function = f ;
rest_ticks = ABORT_WAIT ;
abort_on_hangup = 1 ;
}

void
hang_end( void )
{
abort_on_hangup = 0 ;
}

void
install_fdc_driver( void )
{
_AH = 0 ; _DL = FDD ;
geninterrupt( 0x13 ) ;                        /* Сброс диска BIOS */
delay( 500 ) ;
disable() ;
bios_time = biostime( 0, 0 ) ;
old_int_0eh = getvect( 0x0e ) ;
old_int_08h = getvect( 0x08 ) ;
outportb( 0x21, inportb( 0x21 ) | 0x40 ) ;   /* Запрет прерывания */
setvect( 0x0e, int_0eh ) ;
asm        mov        dx, offset int_08h_routine
asm        push        ds
asm        mov        ax, cs
asm        mov        ds, ax
asm        mov        ax, 02508h
asm        int        21h
asm        pop        ds
bios_disk = (void far *) getvect( 0x1e ) ;
enable() ;
asm        jmp        exit

asm int_08h_routine label near
asm        extrn        DGROUP@:word
asm        push        ax
asm        push        ds
asm        mov        ds, cs:DGROUP@
asm        add        word ptr bios_time, 1
asm        adc        word ptr bios_time + 2, 0
asm        push        ds
asm        mov        ax, 40h
asm        mov        ds, ax
asm        mov        byte ptr ds:[40h], 0ffh
asm        pop        ds
asm        mov        al, 20h
asm        out        20h, al
asm        cmp        abort_on_hangup, 0
asm        je        done
asm        dec        rest_ticks
asm        jg        done
        longjmp( hang_reset, -1 ) ;
done:;
asm        pop        ds
asm        pop        ax
asm        iret

exit:;
}

void

                                - 32 -
specify( SPECIFY parms )
{
START ;
HANG_START( "Определить" ) ;
parms.nd = 1 ;
write_data( 0x03 ) ;
write_data( *(char *)&parms ) ;
write_data( *((char *)&parms + 1 ) ) ;
HANG_END ;
END ;
printf( "HUT = %d ms\n", parms.hut * 16 ) ;
printf( "HLT = %d ms\n", parms.hlt *  2 ) ;
printf( "SRT = %d ms\n", 16 - parms.srt ) ;
printf( "%sDMA mode\n", parms.nd ? "non-" : "" ) ;
}

void
sence_is( void )
{
write_data( 0x08 ) ;
read_data( st0.c ) ;
if( st0.c != 0x80 )                      /* Неверная команда     */
        read_data( r_c ) ;
}

void
wait_interrupt( void )
{
do      outportb( 0x20, 0x0a ) ;         /* Требуется 8259A IRR */
while( ( inportb( 0x20 ) & 0x40 ) != 0x40 ) ;
outportb( 0x20, 0x08 ) ;                /* Стандартное состояние*/
}

int
recalibrate( void )
{
START ;
HANG_START( "Перекалибровка" ) ;
write_data( 0x07 ) ;
write_data( FDD ) ;
wait_interrupt() ;
do        sence_is() ;
        while( st0.x.se != 1 ) ;
HANG_END ;
END ;
if( ! st0.x.se || st0.x.ic != 0 )
        report_st0() ;
if( r_c == 0 )
        return 0 ;
return -1 ;
}

int
seek( unsigned char cyl )
{
START ;
HANG_START( "Установка" ) ;
write_data( 0x0f ) ;
write_data( FDD ) ;
write_data( cyl ) ;
wait_interrupt() ;
do        sence_is() ;
        while( st0.x.se != 1 ) ;

                                - 33 -
HANG_END ;
END ;
if( ! st0.x.se || st0.x.ic != 0 )
        report_st0() ;
if( r_c == cyl )
        return 0 ;
return -1 ;
}

void
start_operations( void )
{
        SPECIFY temp ;

temp = bios_disk->specify ;
temp.nd = 1 ;
MOTOR_COUNT = UCHAR_MAX - 1 ;
outportb( FDC_DIGITAL, FDD ) ;
delay( 1 ) ;
outportb( FDC_DIGITAL, 0x04 | FDD ) ;
delay( 1 ) ;
outportb( FDC_RATE, RATE_300 ) ;
specify( temp ) ;
outportb( FDC_DIGITAL, ( 0x10 << FDD ) | 0x0C | FDD ) ;
delay( bios_disk->motor_startup * 120 ) ;
}

void
reset_old_fdc( void )
{
setvect( 0x0e, old_int_0eh ) ;
setvect( 0x08, old_int_08h ) ;
outportb( 0x21, inportb( 0x21 ) & ~0x40 ) ;
biostime( 1, bios_time ) ;
MOTOR_COUNT = 1 ;
_AH = 0 ;
_DL = 0 ;
geninterrupt( 0x13 ) ;
}

void
read_ST3( void )
{
START ;
HANG_START( "read_ST3" ) ;
write_data( 0x04 ) ;
write_data( FDD ) ;
read_data( st3.c ) ;
HANG_END ;
END ;
}

/*
 *      Работа контроллера в не-DMA режиме обслуживается ЦП, поэтому
 *      read_operation,write_operation и format_track запрещают пре-
 *      рывания на длительное время. Прошу прощения.
 */
void
read_operation( int code, int head, RW_INPUT *param, char far *buf,
unsigned max_len )
{
        char        *p = (void *)param ;
        int        i = sizeof( RW_INPUT ) ;

                                - 34 -

buffer_bytes = 0 ;
START ;
HANG_START( "операция чтения/записи" ) ;
/*
 *      Послать параметр команды w/ в 8272A
 */
write_data( code ) ;
write_data( ( ( head & 1 ) << 2 ) | FDD ) ;
for( ; i > 0 ; i--, p++ ) write_data( *p ) ;
/*
 *      Ждать до начала фазы исполнения
 */
while( !( inportb( FDC_MSR ) & 0x20 ) ) ;
disable() ;
/*
 *      Разрешение на передачу данных
 */
asm        mov        cx, max_len
asm        mov        dx, FDC_MSR
asm        les        di, buf
asm        xor        si, si
asm        mov        bx, 1+1
asm        cld
input_loop:
asm        in        al, dx
asm        test        al, 20h
asm        jz        exit
asm        test        al, 80h
asm        jz        input_loop
asm        inc        dx
asm        in        al, dx
asm        stosb
asm        inc        si
asm        dec        dx
asm        loop        input_loop
exit:;
enable() ;
asm        mov        buffer_bytes, si
/*
 *      Ждать конца фазы выполнения
 */
wait_interrupt() ;
/*
 *      Читать результат
 */
read_data( st0.c ) ;
read_data( st1.c ) ;
read_data( st2.c ) ;
read_data( r_c ) ;
read_data( r_h ) ;
read_data( r_r ) ;
read_data( r_n ) ;
HANG_END ;
END ;
}

void
write_operation( int code, int head, RW_INPUT *param, char far *buf,
unsigned max_len )
{
        char        *p = (void *)param ;
        int        i = sizeof( RW_INPUT ) ;

                                - 35 -

buffer_bytes = 0 ;
START ;
HANG_START( "операция чтения/записи" ) ;
/*
 *      Послать параметр команды w/ в 8272A
 */
write_data( code ) ;
write_data( ( ( head & 1 ) << 2 ) | FDD ) ;
for( ; i > 0 ; i--, p++ ) write_data( *p ) ;
/*
 *      Ждать до начала фазы выполнения
 */
while( !( inportb( FDC_MSR ) & 0x20 ) ) ;
disable() ;
/*
 *      Быть готовым передать данные
 */
asm        push        ds
asm        mov        cx, max_len
asm        mov        dx, FDC_MSR
asm        lds        si, buf
asm        xor        di, di
asm        cld
wait_start:
asm        in        al, dx
asm        test        al, 20h
asm        jz        wait_start

output_loop:
asm        in        al, dx
asm        test        al, 20h
asm        jz        exit
asm        test        al, 80h
asm        jz        output_loop
asm        inc        dx
asm        outsb
asm        inc        di
asm        dec        dx
asm        loop        output_loop
/*
 * FDC требует больше данных, но данных слева нет, так что они
 * разрушаются.
*/
        outportb( FDC_DIGITAL, 0 ) ;
        delay( 5 ) ;
        outportb( FDC_DIGITAL, ( 0x10 << FDD ) | 0x0C | FDD ) ;
        asm        pop        ds
        asm        mov        buffer_bytes, di
        HANG_END ;
        END ;
        enable() ;
        specify( bios_disk->specify ) ;
        return ;
exit:;
enable() ;
asm        pop        ds
asm        mov        buffer_bytes, di
/*
 *      Ждать конец фазы исполнения
 */
wait_interrupt() ;
/*

                                - 36 -
 *      Read result
 */
read_data( st0.c ) ;
read_data( st1.c ) ;
read_data( st2.c ) ;
read_data( r_c ) ;
read_data( r_h ) ;
read_data( r_r ) ;
read_data( r_n ) ;
HANG_END ;
END ;
}

void
format_track(int head, int n, int sc, int gpl, int d, char far *buf,
int lim )
{
START ;
HANG_START( "Формат дорожки" ) ;
/*
 *      Передать параметры команды записи в 8272A
 */
write_data( 0x0D | mfm ) ;
write_data( ( ( head & 1 ) << 2 ) | FDD ) ;
write_data( n        ) ;
write_data( sc        ) ;
write_data( gpl ) ;
write_data( d        ) ;
/*
 *      Ждать до начала фазы исполнения
 */
while( !( inportb( FDC_MSR ) & 0x20 ) ) ;
disable() ;
/*
 *     Быть готовым к передаче данных
 */
asm        push        ds
asm        mov        cx, lim
asm        mov        dx, FDC_MSR
asm        lds        si, buf
asm        xor        di, di
asm        cld
output_loop:
asm        in        al, dx
asm        test        al, 20h
asm        jz        exit
asm        test        al, 80h
asm        jz        output_loop
asm        inc        dx
asm        lodsb
asm        out        dx, al
asm        inc        di
asm        dec        dx
asm        loop        output_loop
        /*
         * FDC нужно больше данных, но больше данных нет, так
         * что они разрушаются
         */
        delay( 1 ) ;
        outportb( FDC_DIGITAL, 0 ) ;
        delay( 5 ) ;
        outportb( FDC_DIGITAL, ( 0x10 << FDD ) | 0x0C | FDD ) ;
        asm        pop        ds

                                - 37 -
        asm        mov        buffer_bytes, di
        HANG_END ;
        END ;
        enable() ;
        specify( bios_disk->specify ) ;
        return ;
exit:;
enable() ;
asm        pop        ds
asm        mov        buffer_bytes, di
/*
 *      Wait execution phase end
 */
wait_interrupt() ;
/*
 *      Читать результат
 */
read_data( st0.c ) ;
read_data( st1.c ) ;
read_data( st2.c ) ;
read_data( r_c ) ;
read_data( r_h ) ;
read_data( r_r ) ;
read_data( r_n ) ;
HANG_END ;
END ;
}


void
read_sector( int head, RW_INPUT *param )
{
read_operation( 0x06 | mfm, head, param, buffer, BUFFER_SIZE ) ;
}

void
read_deleted( int head, RW_INPUT *param )
{
read_operation( 0x0C | mfm, head, param, buffer, BUFFER_SIZE ) ;
}

void
read_track( int head, RW_INPUT *param )
{
read_operation( 0x02 | mfm, head, param, buffer, BUFFER_SIZE ) ;
}

void
write_sector( int head, unsigned count, RW_INPUT *param )
{
write_operation( 0x05 | mfm, head, param, buffer, count ) ;
}

void
write_deleted( int head, unsigned count, RW_INPUT *param )
{
write_operation( 0x09 | mfm, head, param, buffer, count ) ;
}

void
read_address( int head )
{
START ;

                                - 38 -
HANG_START( "Read_address" ) ;
write_data( 0x0a | mfm ) ;
write_data( ( ( head & 1 ) << 2 ) | FDD ) ;
wait_interrupt() ;
read_data( st0.c ) ;
read_data( st1.c ) ;
read_data( st2.c ) ;
read_data( r_c ) ;
read_data( r_h ) ;
read_data( r_r ) ;
read_data( r_n ) ;
HANG_END ;
END ;
}

void
explain_ST3( void )
{
printf( "ST3 is %02X\n", st3.c ) ;
printf( "Drive %c is %s\n", 'A' + st3.x.ds, st3.x.rdy ? "ready" :
"not ready" ) ;
printf( "Selected head %d\n", st3.x.h ) ;
if( st3.x.ft )
        printf( "!!!!! Drive fault !!!!!\n" ) ;
if( st3.x.wp )
        printf( "Write protected\n" ) ;
if( st3.x.t0 )
        printf( "Currently at track 0\n" ) ;
}

void
print_rw_return( void )
{
printf( "STn : %02x %02x %02x\n", st0.c, st1.c, st2.c ) ;
printf( "Drive %c, head %d :\n", 'A' + st0.x.ds, st0.x.h ) ;
switch( st0.x.ic ){
        case 0: break ;
        case 1:
                printf( "Abnormal operation termination\n" ) ;
                break ;
        case 2:
                printf( "Illegal command\n" ) ;
                break ;
        case 3:
                printf( "Disk drive ready condition changed\n" ) ;
                break ;
        }
if( st0.x.se )
        printf( "Seek ended\n" ) ;
if( st0.x.ec )
        printf( "Drive fault\n" ) ;
if( st0.x.nr )
        printf( "Drive not ready or head 1 selected on single-sided
                drive\n" );
if( st1.x.en )
        printf( "End of track error\n" ) ;
if( st1.x.de )
        if( st2.x.dd )
                printf( "User data CRC error\n" ) ;
        else        printf( "Sector ID CRC error\n" ) ;
if( st1.x.or )
        printf( "Overrun error\n" ) ;
if( st1.x.nd )

                                - 39 -
        printf( "Sector not found\n" ) ;
if( st1.x.nw )
        printf( "Write protect error\n" ) ;
if( st1.x.ma )
        printf( "Missing address mark\n" ) ;
if( st2.x.cm )
      printf("Control mark-Deleted data on Read Data or reverse\n");
if( st2.x.wc )
        printf( "Sector ID cylinder number do not match\n" ) ;
if( st2.x.sh )
        printf( "Scan condition hit\n" ) ;
if( st2.x.sn )
        printf( "Scan not satisfied\n" ) ;
if( st2.x.bc )
        printf( "IBM bad track\n" ) ;
if( st2.x.md )
        printf( "Missing data address mark\n" ) ;
}

void
read_rw_input( RW_INPUT *p )
{
        int        temp ;

printf( "C = " ) ;
scanf( "%x", &temp ) ; p->c = temp ;
printf( "H = " ) ;
scanf( "%x", &temp ) ; p->h = temp ;
printf( "R = " ) ;
scanf( "%x", &temp ) ; p->r = temp ;
printf( "N = " ) ;
scanf( "%x", &temp ) ; p->n = temp ;
printf( "EOT = " ) ;
scanf( "%x", &temp ) ; p->eot = temp ;
printf( "GPL = " ) ;
scanf( "%x", &temp ) ; p->gpl = temp ;
printf( "DTL = " ) ;
scanf( "%x", &temp ) ; p->dtl = temp ;
}

void
print_chrn( void )
{
printf( "Cyl = %2x Head = %2x Sect = %2x Siz = %1x\n", r_c, r_h, r_r, r_n ) ;
}

/*
   analyze_track() очень простой анализатор дорожки, основанный на ко-
манде чтения идентификатора сектора.  Он не способен найти  специально
спрятанные секторы.
 */
void
analyze_track( int cyl, int head )
{
        struct        _x        {
                char        c, h, r, n ;
                long        time_diff ;
                } ;
        struct        _x        sector_table[ MAX_SECTORS ] ;
        struct        _x        *p ;
        int                i ;
        long                start, end ;
        int                track_size ;

                                - 40 -
        RW_INPUT        param ;

seek( cyl ) ;
fprintf( stderr, "Analyzing track %02X ... Please Wait\n", cyl ) ;

param.c = 0xff ;
param.h = 0xff ;
param.r = 0x0 ;
param.n = 0xff ;
param.eot = 0xff ;
param.gpl = 0x20 ;
param.dtl = 0xff ;
disable() ;
/*
 * Следующий  read_sector  заканчивается сразу после чтения индексного
 * отверстия
 */
do {
        param.r-- ;
        read_sector( head, &param ) ;
        } while( ( ! st1.x.nd ) && ( ! st1.x.ma ) ) ;
if( st1.x.ma ){
        print_rw_return() ;
        return ;
        }
start = get_exact_time() ;
for( i = 0, p = sector_table ; i < MAX_SECTORS ; i++, p++ ){
        read_address( head ) ;
        end = get_exact_time() ;
        enable() ;
        p->c = r_c ;
        p->h = r_h ;
        p->r = r_r ;
        p->n = r_n ;
        if( st1.x.ma ){
                print_rw_return() ;
                return ;
                }
        p->time_diff = end - start ;
        if( p->time_diff > REVOLUTION_TIME )
                break ;
        }
track_size = i ;
fprintf(out, "%02X sectors found on track %02X side %1X\n",
        track_size, cyl, head ) ;
fprintf(out, "      Cyl Hd Sec  N   Pos (ms) Off (ms)       Cyl Hd
        Sec  N   Pos (ms) Off (ms)\n" ) ;
for( i = 0, p = sector_table ; i < track_size ; i++, p++ ){
  fprintf( out, "#%2d - %02X   %1X  %02X  %1X   %7.3lf %7.3lf%c", i,
           p->c, p->h, p->r, p->n, p->time_diff / ( 1193.18 * 2 ),
  ( i == ( track_size - 1 ) ? ( sector_table->time_diff +
    REVOLUTION_TIME - p->time_diff ) :
  ( (p+1)->time_diff - p->time_diff ) ) / ( 1193.18 * 2 ),
  i % 2 ? '\n' : '\t' ) ;
        }
if( i % 2 == 1 ) fputc( '\n', out ) ;
}

void
analyze_disk( int from, int count )
{
        int        i ;


                                - 41 -
for( i = 0 ; i < count ; i++, from++ ){
        analyze_track( from, 0 ) ;
        analyze_track( from, 1 ) ;
        }
}

#define LINE_SIZE        16
#define SCREEN_SIZE        10
#define PAGE_SIZE        (SCREEN_SIZE*LINE_SIZE)

void
draw_buffer( char *start, unsigned lines, unsigned num_start )
{
        int        i ;

for( ; lines-- > 0 ; num_start += LINE_SIZE, start += LINE_SIZE ){
        printf( "%04X ", num_start ) ;
        for( i = 0 ; i < LINE_SIZE ; i++ )
                printf( "%02X ", start[ i ] ) ;
        for( i = 0 ; i < LINE_SIZE ; i++ )
                putchar( isprint( start[ i ] ) ? start[ i ] : '.' ) ;
        putchar( '\n' ) ;
        }
}

void
buffer_operations( void )
{
        unsigned        current_offset = 0 ;
        int                need_redraw = 1 ;
        unsigned        offset, len ;
        unsigned        pattern ;
        char                temp[ 80 ] ;
        int                handle ;

START ;
while( 1 ){
 if( need_redraw ){
  draw_buffer(buffer + current_offset, SCREEN_SIZE, current_offset);
  printf( "[H,U,D,G,F,S,E,R,W,C,X]\n" ) ;
                }
 need_redraw = 1 ;
 switch( toupper( (char)bioskey( 0 ) ) ){
 case 'X':
  case 0x1b:
  END ;
  return ;
   case 'U':
   if( current_offset >= PAGE_SIZE )
   current_offset -= PAGE_SIZE ;
   else        need_redraw = 0 ;
                        break ;
   case 'D':
   if( current_offset < BUFFER_SIZE - 2 * PAGE_SIZE )
   current_offset += PAGE_SIZE ;
   else        need_redraw = 0 ;
   break ;
   case 'G':
   printf( "Go to offset : " ) ;
   scanf( "%x", &current_offset ) ;
   if( current_offset >= BUFFER_SIZE - PAGE_SIZE )
   current_offset = 0 ;
   break ;

                                - 42 -
   case 'F':
   printf( "Fill from offset : " ) ;
   scanf( "%x", &offset ) ;
   printf( "Length : " ) ;
   scanf( "%x", &len ) ;
   printf( "Pattern : " ) ;
   scanf( "%x", &pattern ) ;
   setmem( buffer + offset, len, pattern ) ;
   break ;
   case 'S':
   printf( "Set at offset : " ) ;
   scanf( "%x", &offset ) ;
   printf( "Value : " ) ;
   scanf( "%x", &pattern ) ;
   buffer[ offset ] = pattern ;
   break ;
   case 'E':
   printf( "Set at offset : " ) ;
   scanf( "%x", &offset ) ;
   printf( "Byte count : " ) ;
   scanf( "%x", &len ) ;
   for( ; len-- > 0 ; offset++ ){
   printf( "Value : " ) ;
   scanf( "%x", &pattern ) ;
   buffer[ offset ] = pattern ;
                                }
   break ;
   case 'R':
   printf( "Read to offset : " ) ;
   scanf( "%x", &offset ) ;
   printf( "Byte count : " ) ;
   scanf( "%x", &len ) ;
   printf( "File : " ) ;
   scanf( "%s", temp ) ;
   if( ( handle = _open( temp, O_RDONLY | O_BINARY ) ) == -1 ){
   perror( temp ) ;
   break ;
                                }
if( _read( handle, buffer + offset, len ) != len )
perror( temp ) ;
_close( handle ) ;
break ;
case 'W':
printf( "Write from offset : " ) ;
scanf( "%x", &offset ) ;
printf( "Byte count : " ) ;
scanf( "%x", &len ) ;
printf( "File : " ) ;
scanf( "%s", temp ) ;
if( ( handle = _creat( temp, 0 ) ) == -1 ){
perror( temp ) ;
break ;
                                }
if( _write( handle, buffer + offset, len ) != len )
perror( temp ) ;
_сlose( handle ) ;
break ;
case 'C':
printf( "Set at offset : " ) ;
scanf( "%x\n", &offset ) ;
printf( "Type string : " ) ;
gets( temp ) ;
memcpy( buffer + offset, temp, strlen( temp ) ) ;

                                - 43 -
break ;
case 'H':
printf( "U - Up     D - Down  G - Go to  F - Fill  S - Set byte\n"
      "E - Enter  R - Read  W - Write  X - Exit  C - set string\n");
default:
   need_redraw = 0 ;
                }
        }
}

main( int argc, char *argv[] )
{
        int                cyl, hd ;
        char                key ;
        RW_INPUT        rw ;
        char                name[ 20 ] ;
        int                n, sc, gpl, d, lim ;

if( ( buffer = malloc( BUFFER_SIZE ) ) == NULL ){
        printf( "Can't allocate buffer space !\n" ) ;
        return( 3 ) ;
        }
if( argc > 1 ) FDD = atoi( argv[ 1 ] ) ;
delay( 1 ) ;
install_fdc_driver() ;
start_operations() ;
do {
 switch( setjmp( hang_reset ) ){
 case 0: break ;
 default:
  fprintf(stderr,"Unexpected hangup in function %s\n",
          current_function);
 start_operations() ;
                }
fprintf( stderr, "-----[H,S,D,R,A,E,B,T,F,L,P,W,C,O,I,M,Z] : " ) ;
do        key = toupper( bioskey( 0 ) ) ;
        while( key == 0 ) ;
        printf( "%c\n", key ) ;
        switch( key ){
case 'H':
printf("H - Help        S - Seek        D - ST3
        R - Read sector\n" ) ;
printf("A - Read ID     E - Recalibrate B - BufOps
        T - Analyze track\n" ) ;
printf("F - AnalizeDisk L - ReadDeleted P - Data rate
        W - WriteData\n" ) ;
printf("C - read traCk  O - fOrmat      I - wrItedelete
        M - MFM/FM toggle\n" ) ;
printf("Z - Exit\n" ) ;
break ;
case 'S':
printf( "*Seek to cylinder : " ) ;
scanf( "%x", &cyl ) ;
seek( cyl ) ;
break ;
case 'D':
  read_ST3() ;
  explain_ST3() ;
break ;
case 'R':
  printf( "*Read data\n" ) ;
  printf( "Head : " ) ;
  scanf( "%x", &hd ) ;

                                - 44 -
  read_rw_input( &rw ) ;
  read_sector( hd, &rw ) ;
  print_rw_return() ;
  printf( "%d (%#4x) bytes read\n", buffer_bytes, buffer_bytes ) ;
  printf( "Operation time is not reliable !\n" ) ;
break ;
case 'L':
  printf( "*Read deleted data\n" ) ;
  printf( "Head : " ) ;
  scanf( "%x", &hd ) ;
  read_rw_input( &rw ) ;
  read_deleted( hd, &rw ) ;
  print_rw_return() ;
  printf( "%d (%#4x) bytes read\n", buffer_bytes, buffer_bytes ) ;
  printf( "Operation time is not reliable !\n" ) ;
break ;
case 'C':
  printf( "*Read a track\n" ) ;
  printf( "Head : " ) ;
  scanf( "%x", &hd ) ;
  read_rw_input( &rw ) ;
  read_track( hd, &rw ) ;
  print_rw_return() ;
  printf( "%d (%#4x) bytes read\n", buffer_bytes, buffer_bytes ) ;
  printf( "Operation time is not reliable !\n" ) ;
break ;
case 'W':
  printf( "*Write data\n" ) ;
  printf( "Head : " ) ;
  scanf( "%x", &hd ) ;
  printf( "Byte count : " ) ;
  scanf( "%x", &cyl ) ;
  read_rw_input( &rw ) ;
  write_sector( hd, cyl, &rw ) ;
  print_rw_return() ;
  printf( "%d (%#4x) bytes written\n", buffer_bytes, buffer_bytes );
  printf( "Operation time is not reliable !\n" ) ;
break ;
case 'I':
  printf( "*Write deleted\n" ) ;
  printf( "Head : " ) ;
  scanf( "%x", &hd ) ;
  printf( "Byte count : " ) ;
  scanf( "%x", &cyl ) ;
  read_rw_input( &rw ) ;
  write_deleted( hd, cyl, &rw ) ;
  print_rw_return() ;
  printf( "%d (%#4x) bytes written\n", buffer_bytes, buffer_bytes );
  printf( "Operation time is not reliable !\n" ) ;
break ;
case 'A':
  printf( "*Read address\n" ) ;
  printf( "Head = " ) ;
  scanf( "%x", &cyl ) ;
  read_address( cyl ) ;
  print_rw_return() ;
  print_chrn() ;
break ;
case 'E':
  printf( "*Recalibrating\n" ) ;
  recalibrate() ;
break ;
case 'B':

                                - 45 -
  buffer_operations() ;
break ;
case 'T':
  printf( "Analyze cylinder : " ) ;
  scanf( "%x", &cyl ) ;
  printf( "Head : " ) ;
  scanf( "%x", &hd ) ;
  out = stdout ;
  analyze_track( cyl, hd ) ;
break ;
case 'F':
  printf( "Analyze from cylinder : " ) ;
  scanf( "%x", &cyl ) ;
  printf( "Cylinder count : " ) ;
  scanf( "%x", &hd ) ;
  printf( "File for output : " ) ;
  scanf( "%s", name ) ;
  if( ( out = fopen( name, "wt" ) ) == NULL ){
  perror( name ) ;
break ;
                           }
analyze_disk( cyl, hd ) ;
fclose( out ) ;
break ;
case 'P':
  printf( "Select rate : 0 - 500 KBS, 1 - 300 KBS, 2 - 250 KBS\n" ) ;
  scanf( "%x", &cyl ) ;
  outportb( FDC_RATE, cyl ) ;
break ;
case 'O':
  printf( "*Format a track using IDs from buffer\n" ) ;
  printf( "Head : " ) ;
  scanf( "%x", &hd ) ;
  printf( "N   = " ) ;
  scanf( "%x", &n ) ;
  printf( "SC  = " ) ;
  scanf( "%x", &sc ) ;
  printf( "GPL = " ) ;
  scanf( "%x", &gpl ) ;
  printf( "D   = " ) ;
  scanf( "%x", &d ) ;
  printf( "TC  = " ) ;
  scanf( "%x", &lim ) ;
  format_track( hd, n, sc, gpl, d, buffer, lim ) ;
  print_rw_return() ;
  printf( "%x bytes transferred as IDs\n", buffer_bytes ) ;
break ;
case 'M':
  if( mfm ) mfm = 0 ;
   else          mfm = 0x40 ;
  printf( "Current mode is %s\n", mfm ? "MFM" : "FM" ) ;
break ;
case 'Z':
  reset_old_fdc() ;
  return( 0 ) ;
  default:
    printf( "No action for this key : %c\n", key ) ;
break ;
                }
printf( "Elapsed time is %8.3f ms\n", elapsed_time / ( 1193.180 * 2 ) ) ;
        } while( 1 ) ;
}


                                - 46 -
        5. Приложение B. Простая программа тестирования HDC

   Текст программы находится в файле hd_scan.c, который также требу-
ет TC 2.0 и TASM для компиляции.  Программа принимает  идентификатор
дисковода BIOS (80 для 1-го дисковода,  81 для 2-го). Если этот тест
зависает после печати сообщения :
   " Cyls = XXX, Heads = XXX, Sectors = XXX ",
все программы оптимизации диска должны быть удалены из памяти до вы-
полнения этого теста.

static char __rights__[]="Модуль тестирования жесткого диска. (C) 1991
                          Сергей С. Пашкович" ;

#ifndef __TINY__
#error Необходимо компилировать в модели TINY!
#endif
#pragma inline
#include <stdio.h>
#include <stdlib.h>
#include <bios.h>
#include <dos.h>

#define VERSION                 "0.0"
#define MAX_ROMS                100
#define MAX_SECTORS             64
#define SECTOR_SIZE             512
#define TIMER_CLOCK_RATE        (2*1193180ul)

#define INT_13()        geninterrupt(0x13)
#define BIOS_13()       (*BIOS_entry)()

        unsigned        HDD ;
        unsigned        cyls, heads, sectors ;
        FILE            *report ;
        void interrupt  (*BIOS_entry)( void ) ;
        unsigned        ROM_count = 0 ;
        unsigned long   revolution_time ;
        unsigned char   error_code ;
        double          ForcedRPS = 0.0 ;

        struct  _Z_ {
        unsigned char   sector ;
        unsigned long   position ;
        } ;

static  struct  _Y_ {
        unsigned        ROM_start ;     /* para                 */
        unsigned        ROM_end ;       /* para                 */
        } ROM_list[ MAX_ROMS ] ;

static  struct  _X_ {
        unsigned char   code ;
        char            *message ;
        } disk_errors[] = {
                0x00, "Функция выполнена успешно",
                0x01, "Неверное значение или неверная функция",
                0x02, "Не найдена адресная метка",
                0x03, "Защита записи",
                0x04, "Сектор не найден",
                0x05, "Ошибка установки",
                0x07, "Ошибка параметра активации",
                0x08, "Сбой DMA",
                0x09, "Попытка доступа к DMA свыше 64K",

                                - 47 -
                0x0A, "Плохой флаг сектора",
                0x0D, "Плохой номер сектора (формат)",
                0x0E, "Обнаружены контрольные данные адрес. метки",
                0x0F, "Неверное значение уровня арбитража DMA",
                0x10, "Неисправимая ошибка ECC",
                0x11, "Данные скорректированы ECC",
                0x20, "Поломка контроллера диска",
                0x40, "Ошибка операции установки",
                0x80, "Жесткий диск не готов",
                0xBB, "Неопределенная ошибка",
                0xCC, "Ошибка записи",
                0xE0, "Регистр ошибок обнулен",
                } ;

char    *
get_error_name( unsigned char code )
{
        int     i ;
static  char    buf[ 80 ] ;

for( i = 0 ; i < sizeof disk_errors / sizeof( struct _X_ ) ; i++ )
 if(code == disk_errors[ i ].code) return disk_errors[ i ].message;
sprintf( buf, "Unknown error code 0x%02X", code ) ;
return buf ;
}

unsigned long
get_exact_time( void )
{
asm     xor     ax, ax
asm     mov     es, ax
asm     pushf
asm     sti
repeat_request:
asm     mov     al, 0c2h
asm     cli
asm     out     43h, al
asm     jmp     $+2
asm     jmp     $+2
asm     in      al, 40h
asm     jmp     $+2
asm     mov     bl, al          /* Счетчик байта состояния */
asm     in      al, 40h
asm     jmp     $+2
asm     mov     ah, al
asm     in      al, 40h
asm     mov     dx, es:[46Ch]
asm     sti
asm     xchg    ah, al
asm     cmp     dx, es:[46Ch]
asm     jne     repeat_request
asm     popf
asm     neg     ax
asm     shl     bl, 1
asm     cmc
asm     rcl     dx, 1
return ( (unsigned long)_DX << 16 ) + _AX ;
}

void
init_timer_channel( unsigned char channel )
{
asm     pushf

                                - 48 -
asm     cli
asm     mov     al, channel
asm     mov     cl, 6
asm     shl     al, cl
asm     or      al, 36h
asm     out     43h, al
asm     mov     dx, 40h
asm     add     dl, channel
asm     jmp     $+2
asm     jmp     $+2
asm     xor     al, al
asm     out     dx, al
asm     jmp     $+2
asm     jmp     $+2
asm     out     dx, al
asm     popf
}

int
parce_parameters( int argc, char **argv )
{
printf( "HD_scan v. " VERSION " (C) 1991 Serge S. Pachkovsky\n" ) ;
printf("Values reported can be incorrect for a caching disk
        controllers\n" ) ;
if( argc < 2 ){
        printf( "HD_scan BIOS_disk_ID [Drive RPS]\n" );
        return -1 ;
        }
if( sscanf( argv[ 1 ], "%x", &HDD ) != 1 ){
        printf( "Invalid disk ID %s\n", argv[ 1 ] ) ;
        return -1 ;
        }
if( argc > 2 )
if( sscanf( argv[ 2 ], "%lg", &ForcedRPS ) != 1 ){
  printf( "Invalid drive revolutions_per_secons %s\n", argv[ 2 ] );
  return -1 ;
        }
return 0 ;
}

int
init_files( void )
{
if( ( report = fopen( "HD_scan.rep", "wt" ) ) == NULL ){
        perror( "HD_scan.rep" ) ;
        return -1 ;
        }
fprintf(report, "Hard disk scan v " VERSION " report on disk %X\n",
        HDD );
return 0 ;
}

int
read_HDD_params( unsigned char disk )
{
        unsigned dx, cx ;

_DL = disk ;
_AH = 0x08 ;
INT_13() ;
dx = _DX ; cx = _CX ;
heads   = dx >> 8 ;
sectors = cx & 0x3F ;

                                - 49 -
cyls    = ( cx >> 8 ) | ( ( ( cx >> 6 ) & 3 ) << 8 ) ;
return 0 ;
}

int
scan_ROMs( void )
{
        unsigned                seg ;
        unsigned char far       *ptr ;
        struct _Y_              *x = ROM_list ;

for( seg = 0xC800 ; seg < 0xE000 ; seg += 0x80 ){
        ptr = MK_FP( seg, 0 ) ;
        if( ptr[ 0 ] == 0x55 && ptr[ 1 ] == 0xAA ){
                /* Найден признак расширенной памяти */
                x->ROM_start = seg ;
                x->ROM_end   = seg + (unsigned)ptr[ 2 ] * (512/16) - 1 ;
                x++ ; ROM_count++ ;
                }
        }
x->ROM_start = 0xF000 ; x->ROM_end = 0xFFFF ;
ROM_count++ ;
return 0 ;
}

void interrupt
(*trace_to_BIOS( void interrupt (*start)(), unsigned char disk ))()
{
static  void interrupt  (*old_01)( void ) ;
static  void interrupt  (*entry_point)( void ) ;
static  unsigned char   trace_on ;
static  void interrupt  (*trace_dst)( void ) ;

entry_point = start ;
old_01      = getvect( 0x01 ) ;
trace_on    = 1 ;
asm     push    cs
asm     lea     ax, trace
asm     push    ax
asm     mov     ax, 1
asm     push    ax
asm     call    setvect
asm     add     sp, 6
asm     pushf
asm     push    cs
asm     lea     ax, normal_return
asm     push    ax
asm     pushf
asm     pop     ax
asm     or      ah, 1
asm     push    ax
asm     popf
asm     pushf
asm     push    word ptr start + 2
asm     push    word ptr start + 0
asm     mov     ah, 8
asm     mov     dl, disk
asm     iret
asm     normal_return   label   near
setvect( 0x01, old_01 ) ;
asm     jmp     end_routine

asm     trace   label   near

                                - 50 -
asm     push    bp
asm     mov     bp, sp
asm     push    ax
asm     push    bx
asm     push    cx
asm     push    ds
asm     push    es
asm     mov     ax, cs
asm     mov     ds, ax
/*
 *      BP ->   0       old BP
 *              2       IP
 *              4       CS
 *              6       Flags
 *              8       IP or flags
 *             10       CS
 *             12       flags
 */
asm     cmp     trace_on, 1
asm     je      proceed_trace
stop_trace:
asm     and     byte ptr ss:[bp+6+1], NOT 1     /* clear TF     */
exit_trace:
asm     pop     es
asm     pop     ds
asm     pop     cx
asm     pop     bx
asm     pop     ax
asm     pop     bp
asm     iret
proceed_trace:
asm     mov     ax, ss:[bp+2]
asm     shr     ax, 1
asm     shr     ax, 1
asm     shr     ax, 1
asm     shr     ax, 1
asm     add     ax, ss:[bp+4]
asm     lea     bx, ROM_list
asm     mov     cx, ROM_count
look_next_ROM_block:
asm     cmp     ax, ds:[bx].ROM_start
asm     jb      bad_ROM
asm     cmp     ax, ds:[bx].ROM_end
asm     ja      bad_ROM
asm     mov     ax, ss:[bp+2]
asm     mov     word ptr entry_point + 0, ax
asm     mov     ax, ss:[bp+4]
asm     mov     word ptr entry_point + 2, ax
asm     jmp     stop_trace
bad_ROM:
asm     add     bx, 4
asm     loop    look_next_ROM_block
asm     les     bx, dword ptr ss:[bp+2]
asm     mov     ax, word ptr es:[bx]
asm     cmp     al, 0cfh
asm     je      trace_iret_command
asm     cmp     al, 09dh
asm     je      trace_popf_command
asm     cmp     al, 0cdh
asm     je      trace_intn_command
asm     cmp     al, 0cch
asm     je      trace_int3_command
asm     jmp     exit_trace

                                - 51 -

trace_iret_command:
asm     or      byte ptr ss:[bp+12+1], 1
asm     jmp     exit_trace
trace_popf_command:
asm     or      byte ptr ss:[bp+8+1], 1
asm     jmp     exit_trace
trace_int3_command:
asm     inc     word ptr ss:[bp+2]
asm     mov     ah, 3
asm     jmp     trace_interrupt
trace_intn_command:
asm     cmp     ah, 10h
asm     jb      OK_to_trace
asm     cmp     ah, 13h
asm     jb      exit_trace
asm     je      OK_to_trace
asm     cmp     ah, 1dh
asm     jb      exit_trace
OK_to_trace:
asm     add     word ptr ss:[bp+2], 2
asm     jmp     trace_interrupt
trace_interrupt:
asm     xor     bx, bx
asm     mov     es, bx
asm     mov     bl, ah
asm     shl     bx, 1
asm     shl     bx, 1
asm     mov     ax, word ptr es:[bx]
asm     mov     word ptr trace_dst, ax
asm     mov     ax, word ptr es:[bx+2]
asm     mov     word ptr trace_dst+2, ax
asm     pop     es
asm     pop     ds
asm     pop     cx
asm     pop     bx
asm     pop     ax
asm     pop     bp
asm     pushf
asm     push    word ptr cs:trace_dst + 2
asm     push    word ptr cs:trace_dst + 0
asm     iret

end_routine:;
return entry_point ;
}

int
detect_BIOS_entry( unsigned char disk )
{
if( scan_ROMs() == -1 ) return -1 ;
if( ( BIOS_entry = trace_to_BIOS( getvect( 0x13 ), disk ) ) == NULL)
 return -1 ;
return 0 ;
}

int
detect_HDD_params( void )
{
if( read_HDD_params( HDD ) == -1 ) return -1 ;
fprintf( report, "Cyls = %u, Heads = %u, Sectors = %u\n", cyls + 1,
         heads + 1, sectors ) ;
printf( "Cyls = %u, Heads = %u, Sectors = %u\n", cyls + 1, heads +

                                - 52 -
         1, sectors ) ;
if( detect_BIOS_entry( HDD ) == -1 ) return -1 ;
fprintf( report, "Hard disk BIOS entry point %Fp\n", BIOS_entry ) ;
printf( "Hard disk BIOS entry point %Fp\n", BIOS_entry ) ;
return 0 ;
}

int
init_system_timer( void )
{
init_timer_channel( 0 ) ;
return 0 ;
}

void
read_sector(unsigned cyl,unsigned head,unsigned sector,char buf[512])
{
        unsigned        cx, dx ;

cx = ( sector & 0x3F ) | ( cyl << 8 ) | ( ( cyl >> 8 ) << 6 ) ;
dx = ( head << 8 ) | HDD ;
_ES = FP_SEG( buf ) ;
_BX = FP_OFF( buf ) ;
_CX = cx ;
_DX = dx ;
_AX = 0x0201 ;
BIOS_13() ;
switch( error_code = _AH ){
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 9:
        case 0x0D:
        case 0x0F:
        case 0x20:
        case 0x40:
        case 0x80:
        case 0xBB:
        case 0xCC:
        case 0xE0:
                fprintf( stderr, "%s", get_error_name(error_code));
                exit( 255 ) ;
        }
}

int
detect_rotation_speed( void )
{
        int             i ;
        unsigned long   start, end ;
        char            buf[ 512 ] ;

_AX = 0 ; _DL = HDD ; BIOS_13() ;
read_sector( 0, 0, 1, buf ) ;
start = get_exact_time() ;
for( i = 0 ; i < 500 ; i++ ){
        _AX = 0 ; _DL = HDD ; BIOS_13() ;
        read_sector( 0, 0, 1, buf ) ;
        }
end   = get_exact_time() ;
revolution_time = ( end - start ) / i ;

                                - 53 -
fprintf( report, "Measured drive RPM is %lu\n", 60*TIMER_CLOCK_RATE
                  / revolution_time ) ;
printf( "Measured drive RPM is %lu\n", 60 * TIMER_CLOCK_RATE /
         revolution_time ) ;
if( ForcedRPS != 0.0 ){
        revolution_time = TIMER_CLOCK_RATE / ForcedRPS ;
        fprintf( report, "Forced drive RPM is %lu\n", 60 *
                 TIMER_CLOCK_RATE / revolution_time ) ;
        printf( "Forced drive RPM is %lu\n", 60 * TIMER_CLOCK_RATE /
                 revolution_time ) ;
        }
return 0 ;
}

int
check_sectors( unsigned cyl, unsigned head, struct _Z_ *p )
{
        int             secs = 0 ;
        unsigned        i ;
        char            buf[ 512 ] ;

for( i = 1 ; i <= sectors ; i++ ){
  read_sector( cyl, head, i, buf ) ;
  if( error_code != 0 )
   fprintf( report, "\t%s at sector %4u/%2u/%2u\n",get_error_name(
            error_code ), cyl, head, i ) ;
  if( error_code == 0 || error_code == 0x0A || error_code == 0x10
      || error_code == 0x11 ){
    p++->sector = i ;
    secs++ ;
                }
        }
return secs ;
}

int
get_read_times(unsigned cyl,unsigned head,int sectors,struct _Z_
               *table)
{
        int             i ;
        char            buf[ 512 ] ;
        unsigned long   index ;

read_sector( cyl, head, sectors + 1, buf ) ;
index = get_exact_time() ;
for( i = 0 ; i < sectors ; i += 2 ){
        read_sector( cyl, head, table[ i ].sector, buf );
        table[ i ].position = get_exact_time() - index;
        }
for( i = 1 ; i < sectors ; i += 2 ){
        read_sector( cyl, head, table[ i ].sector, buf ) ;
        table[ i ].position = get_exact_time() - index ;
        }
return 0 ;
}

int
cmp_positions( const struct _Z_ *a, const struct _Z_ *b )
{
if( a->position < b->position ) return -1 ;
if( a->position > b->position ) return  1 ;
return 0 ;
}

                                - 54 -

int
process_read_times( int sectors, struct _Z_ *table )
{
        int     i ;

for( i = 0 ; i < sectors ; i++ )
        while( table[ i ].position > revolution_time )
               table[ i ].position -= revolution_time ;
qsort( table, sectors, sizeof( struct _Z_ ), cmp_positions ) ;
return 0 ;
}

int
report_track_ordering( int sectors, struct _Z_ *table )
{
        int     i ;

for( i = 0 ; i < sectors ; i++ ){
        if( i % 17 == 0 ){
                putc( '\t', report ) ;
                if( i != 0 ) putc( '\t', report ) ;
                }
        fprintf( report, "%02d ", table[ i ].sector ) ;
        if( i % 17 == 16 )
                putc( '\n', report ) ;
        }
if( i % 17 != 0 ) putc( '\n', report ) ;
return 0 ;
}

int
scan_track( unsigned cyl, unsigned head )
{
        struct _Z_      sector_table[ MAX_SECTORS ] ;
        int             sectors ;

if( ( sectors = check_sectors( cyl, head, sector_table ) ) == -1 )
      return -1 ;
if( get_read_times( cyl, head, sectors, sector_table ) == -1 )
    return -1 ;
if( process_read_times( sectors, sector_table ) == -1 ) return -1 ;
fprintf( report, "%4u/%2u\t", cyl, head ) ;
if(report_track_ordering(sectors, sector_table ) == -1 ) return -1;
return 0 ;
}

int
scan_surface( void )
{
        unsigned        cyl, head ;

for( cyl = 0 ; cyl <= cyls ; cyl++ ){
  for( head = 0 ; head <= heads ; head++ ){
   fprintf( stderr, "\rTrack %4d, head %2d ", cyl, head ) ;
  if( scan_track( cyl, head ) == -1 ) return -1 ;
  fflush( report ) ;
                }
        }
fputc( '\n', stderr ) ;
return -1 ;
}


                                - 55 -
int
main( int argc, char *argv[] )
{
if( parce_parameters( argc, argv ) == -1 ) return 1 ;
if( init_files() == -1 ) return 2 ;
if( detect_HDD_params() == -1 ) return 3 ;
if( init_system_timer() == -1 ) return 4 ;
if( detect_rotation_speed() == -1 ) return 5 ;
if( scan_surface() == -1 ) return 6 ;
return 0 ;
}

            6. Приложение C. Как выяснить номер кластера

   Текст программы  находится в файле cluster.c,  требуются TC 2.0 и
TASM для компиляции.  Полное имя файла (то есть,  включая дисковод и
каталог,  даже  файл находится в текущем каталоге) должно вводиться,
как параметр.  Хотя этот пример проверялся на ОС MS DOS 3.20,  3.30,
4.00 и 5.00 (beta), он может не работать под другими версиями MS DOC
или совместимыми системами,  потому что он основывается на структуре
неописанного DiskInfoBlock.

static  char    __rights__[] = "(C) 1991 Serge S. Pachkovsky" ;

#pragma  inline
#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <fcntl.h>
#include <dir.h>
#include <dos.h>
#include <mem.h>
#include <string.h>
#include <ctype.h>
#include <alloc.h>
#include <limits.h>
#include <stdarg.h>

typedef struct  _DI {
        unsigned        char    drv ;  /* Номер привода 0 - A: */
        unsigned        char    subunit ; /*От головки устройства*/
        unsigned                sect_siz ; /* Размер сектора     */
        unsigned        char    hi_sector ;/*Скрытый сектор в клас*/
        unsigned        char    cls_shift ;/* Сдвиг класт. к сект.*/
        unsigned                boot_siz ; /* Резервные секторы   */
        unsigned        char    fats ;     /* Номер FAT           */
        unsigned                max_dir ;  /* Номер корневого кат.*/
        unsigned                data_sec ; /*Первый кластер данных*/
        unsigned                hi_clust ; /* Кластеры + 2        */
        union   {
         struct  {
          unsigned      char    fat_size ; /* Секторов в FAT      */
          unsigned              root_sec ; /* Начало крневого кат.*/
          void          far     *device ; /* Адрес заголовка устр.*/
          unsigned      char    media ;   /* Media описатель      */
          unsigned      char    access ;  /* 0 если доступно      */
          struct _DI    far     *next ;   /* Следующий инф. блок  */
                        } dos3 ;
         struct  {
          unsigned              fat_size ;/* Секторов в FAT       */
          unsigned              root_sec ;/* Начало крневого кат. */
          void          far     *device ; /* Адрес заголовка устр.*/
          unsigned      char    media ;   /* Media описатель      */

                                - 56 -
          unsigned      char    access ;  /* 0 если доступно      */
          struct _DI    far     *next ;   /* Следующий инф. блок  */
                        } dos4 ;
                } dos_dependent ;
        } DISK_INFO ;

typedef struct  {
        char            file_name[ 8 ] ;
        char            file_ext[ 3 ] ;
        char            file_attribute ;
        char            __unused[ 10 ] ;
        unsigned        time ;
        unsigned        date ;
        unsigned        cluster ;
        long            file_size ;
        } DIRECTORY_ENTRY ;

static  DISK_INFO       source_disk_info ;
enum {  FAT_16, FAT_12  } ;
static  int             fat_type = FAT_16 ;

static int near
get_disk_info( char *name )
{
        unsigned        char    drive ;
        DISK_INFO       far     *p ;

drive = toupper( *name ) - 'A' ;
asm     push    ds
asm     mov     dl, drive
asm     inc     dl
asm     mov     ah, 32h
asm     int     21h
asm     cmp     al, 0
asm     jne     error
asm     mov     dx, ds
asm     pop     ds
        p = MK_FP( _DX, _BX ) ;
        source_disk_info = *p ;
        return 0 ;
error:
asm     pop     ds
return -1 ;
}

static unsigned near
get_fat( char huge *fat, unsigned clust )
{
        unsigned        temp_clust ;
        unsigned        x ;

switch( fat_type ){
        case FAT_12:
                temp_clust = ( clust * 3 ) / 2 ;
                x = *(unsigned huge *)( fat + temp_clust ) ;
                if( ( clust & 1 ) == 0 )
                        x &= 0xfff ;
                else    x >>= 4 ;
                if( ( x & 0xfff ) > 0xff0 )
                        x |= 0xf000 ;
                break ;
        case FAT_16:
                x = *( (unsigned huge *)fat + clust ) ;

                                - 57 -
                break ;
        }
return x ;
}

static unsigned near
search_start_cluster( char *name )
{
        char            dir[ MAXDIR ] ;
        char            drive[ MAXDRIVE ] ;
        char            file[ MAXFILE ] ;
        char            ext[ MAXEXT ] ;
        struct  xfcb    xfcb ;
        struct  fcb     *fcb = &xfcb.xfcb_fcb ;
        char            dta[ sizeof( DIRECTORY_ENTRY ) + 9 ] ;
        char    far     *old_dta ;


printf( "Looking for first cluster number of %s\n", name ) ;
setdisk( toupper( *name ) - 'A' ) ;
if( getdisk() != toupper( *name ) - 'A' ){
        printf( "setdisk(%d) failure\n", toupper( *name ) - 'A' ) ;
        return 0xffff ;
        }
fnsplit( name, drive, dir, file, ext ) ;
if( strlen( dir ) > 1 )
        dir[ strlen( dir ) - 1 ] = 0 ;
if( chdir( dir ) == -1 ){
        printf( "chdir(%s) failure\n", dir ) ;
        return 0xffff ;
        }
old_dta = getdta() ;
setdta( dta ) ;
xfcb.xfcb_flag = 0xff ;
setmem( xfcb.xfcb_resv, sizeof( xfcb.xfcb_resv ), 0xff ) ;
xfcb.xfcb_attr = FA_RDONLY | FA_HIDDEN | FA_SYSTEM | FA_ARCH ;
fcb->fcb_drive = 0 ;
setmem( fcb->fcb_name, 8 + 3, ' ' ) ;
memcpy( fcb->fcb_name, file, strlen( file ) ) ;
if( strlen( ext ) != 0 )
        memcpy( fcb->fcb_ext, ext + 1, strlen( ext + 1 ) ) ;
asm     push    ds
asm     mov     dx, ss
asm     mov     ds, dx
asm     lea     dx, xfcb
asm     mov     ah, 11h
asm     int     21h
asm     pop     ds
if( _AL != 0 ){
        printf( "DOS fun 11h failure (%u)\n", _AL ) ;
        return 0xffff ;
        }
setdta( old_dta ) ;
return ((DIRECTORY_ENTRY *)( dta + 8 ))->cluster ;
}

/*
 *      abs_read/abs_write section
 */
static  int     abs_error = 0 ;

static int near
abs_read( int drive, int start, int len, void far *buf )

                                - 58 -
{
asm     push    si
asm     push    di
asm     push    ds
        asm     mov     al, drive
        asm     mov     cx, len
        asm     mov     dx, start
        asm     lds     bx, dword ptr buf
        asm     int     25h
        asm     pop     dx
asm     pop     ds
asm     pop     di
asm     pop     si
        asm     jnc     no_error
        asm     mov     abs_error, ax
        return -1 ;
no_error:
return 0 ;
}

static char huge * near
load_fat( void )
{
        char    huge    *fat ;
        char    huge    *p ;
        unsigned        fat_size ;
        unsigned        sectors_per_call ;
        unsigned        fat_start ;

if( _osmajor >= 4 )
        fat_size = source_disk_info.dos_dependent.dos4.fat_size ;
else    fat_size = source_disk_info.dos_dependent.dos3.fat_size ;
if( ( fat = (char huge *)farmalloc( source_disk_info.sect_siz
      * fat_size ) ) == NULL )
        return NULL ;
sectors_per_call = 0x8000u / source_disk_info.sect_siz ;
for( p = fat, fat_start = source_disk_info.boot_siz ; fat_size > 0 ;
                fat_size -= min( sectors_per_call, fat_size ),
                p += sectors_per_call * source_disk_info.sect_siz,
                fat_start += sectors_per_call )
        if( abs_read( source_disk_info.drv, fat_start, min(fat_size,
                      sectors_per_call ), (void far *)p ) == -1 ){
                farfree( (void far *)fat ) ;
                return NULL ;
                }
return fat ;
}

static void near
detect_fat_type( void )
{
if( _osmajor <= 2 )
        fat_type = FAT_12 ;
else    if( source_disk_info.hi_clust > 4086 )
                fat_type = FAT_16 ;
        else    fat_type = FAT_12 ;
}

static unsigned near
collect_chain( char huge *fat, unsigned start_cluster )
{
        unsigned        clusters ;


                                - 59 -
printf( "Clusters chain is :\n" ) ;
for( clusters = 0 ; start_cluster < 0xfff0 ; clusters++ ){
        start_cluster = get_fat( fat, start_cluster ) ;
        printf( "%5u ", start_cluster ) ;
        if( clusters % 10 == 9 ) putchar( '\n' ) ;
        }
if( clusters % 10 != 0 ) putchar( '\n' ) ;
return clusters ;
}

int
main( int argc, char *argv[] )
{
        unsigned        start_cluster ;
        char            huge *fat ; /*Должен быть HUGE для DOS 4.x*/
        unsigned        clusters ;
        char            directory[ MAXPATH + 1 ] ;
        int             disk ;
        char            *name ;


if( argc != 2 ){
        printf( "Type : Cluster full_file_name\n" ) ;
        return -1 ;
        }
name = argv[ 1 ] ;
disk = getdisk() ;
getcurdir( disk + 1, directory + 1 ) ;
directory[ 0 ] = '\\' ;
if( directory[ 1 ] == '\\' ) directory[ 1 ] = 0 ;
if( get_disk_info( name ) == -1 ){
        printf( "Get disk info failure\n" ) ;
        chdir( directory ) ;
        setdisk( disk ) ;
        return -1 ;
        }
if( ( start_cluster = search_start_cluster( name ) ) == 0xffff ){
        printf( "Get start cluster failure\n" ) ;
        chdir( directory ) ;
        setdisk( disk ) ;
        return -1 ;
        }
printf( "Starting cluster number is %u\n", start_cluster ) ;
if( start_cluster == 0 ){
        chdir( directory ) ;
        setdisk( disk ) ;
        return -1 ;
        }
if( ( fat = load_fat() ) == NULL ){
        printf( "Load FAT failure\n" ) ;
        chdir( directory ) ;
        setdisk( disk ) ;
        return -1 ;
        }
detect_fat_type() ;
clusters = collect_chain( fat, start_cluster ) ;
printf( "Total number of clusters is %d\n", clusters ) ;
farfree( (void far *)fat ) ;
chdir( directory ) ;
setdisk( disk ) ;
return 0 ;
}


                                - 60 -
               7. Приложение D. Доступ к хвосту файла

   Текст программы   находится  в  файле  tail.c.  Хотя  любой  ANSI
транслятор должен принимать ее,  этот пример  основывается  на  нео-
писанном поведении функции seek() и может не работать с транслятора-
ми, отличными от TC 2.0.

#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#define GRANULARITY     (512)
#define ID_LENGTH       30

int
main( int argc, char *argv[] )
{
        int     handle ;
        long    length ;
        char    buf[ ID_LENGTH + 1 ] ;
        int     size ;

if( ( handle = open( argv[ 0 ], O_RDWR | O_BINARY ) ) == -1 ){
        perror( argv[ 0 ] ) ;
        return -1 ;
        }
length = filelength( handle ) ;
if( GRANULARITY - length % GRANULARITY < ID_LENGTH ){
        printf( "File %s has no sufficient tail !\n", argv[ 0 ] ) ;
        close( handle ) ;
        return -1 ;
        }
if( argc == 2 ){        /* Write to tail */
        lseek( handle, 0, SEEK_END ) ;
        size = min( strlen( argv[ 1 ] ) + 1, ID_LENGTH ) ;
        if( write( handle, argv[ 1 ], size ) != size ){
                perror( argv[ 0 ] ) ;
                close( handle ) ;
                return -1 ;
                }
        }
else {
        lseek( handle, ID_LENGTH, SEEK_END ) ;
        write( handle, " ", 1 ) ;
        lseek( handle, length, SEEK_SET ) ;
        read( handle, buf, ID_LENGTH ) ;
        printf( "File tail is \"%s\"\n", buf ) ;
        }
chsize( handle, length ) ;
close( handle ) ;
return 0 ;
}

            8. Приложение E. Как отличать основные платы

   Текст программы находится в файле sysboard.c,  требуются TC 2.0 и
TASM для компиляции. Текущее значение параметра INSTRUCTIONS (16384)
будет заставлять счетчик времен переполняться на  частоте  менее  20
МГц процессора 80386.

static  char    __rights__[] = "Модуль идентификации основной платы";

                                - 61 -

#pragma inline
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <alloc.h>

#define INSTRUCTIONS    (16*1024u)

void
init_timer_channel( unsigned char channel )
{
asm     pushf
asm     cli
asm     mov     al, channel
asm     ror     al, 1
asm     ror     al, 1
asm     or      al, 36h
asm     out     43h, al
asm     mov     dx, 40h
asm     add     dl, channel
asm     jmp     $+2
asm     jmp     $+2
asm     xor     al, al
asm     out     dx, al
asm     jmp     $+2
asm     jmp     $+2
asm     out     dx, al
asm     popf
}

unsigned
read_sound_timer( void )
{
/*
 *      ! Эта функция не должна изменять регистры, кроме AX !
 */
asm     mov     al, 80h
asm     out     43h, al
asm     jmp     $+2
asm     jmp     $+2
asm     in      al, 42h
asm     jmp     $+2
asm     mov     ah, al
asm     in      al, 42h
asm     xchg    ah, al
asm     neg     ax
return _AX ;
}

#define disable_count() (outportb(0x61,inportb(0x61)&(unsigned
                         char)~1))
#define enable_count()  (outportb(0x61,inportb(0x61)|(unsigned
                         char)1))

void
instruction_fill( char far *buf, unsigned cnt, char instr[ 2 ] )
{
while( cnt-- > 0 ){
        *buf++ = instr[ 0 ] ;
        *buf++ = instr[ 1 ] ;
        }
*buf++ = 0xCB ; /* retf */

                                - 62 -
}

unsigned
measure( char far *routine )
{
        unsigned        time ;

init_timer_channel( 2 ) ;
disable_count() ;
asm     push    cs
asm     lea     ax, ret_point
asm     push    ax
asm     les     bx, routine
asm     push    es
asm     push    bx
asm     xor     dx, dx
asm     mov     bx, 1
asm     cli
        enable_count() ;
asm     mov     ax, bx
asm     retf
asm     ret_point       label   near
        disable_count() ;
        time = read_sound_timer() ;
asm     sti
return time ;
}

int
main( int argc, char *argv[] )
{
        char    far     *buf ;
        unsigned        idle_time ;
        unsigned        CPU_mark, mem_mark, DMA_mark ;

if( ( buf = farmalloc( INSTRUCTIONS * 2 + 1 ) ) == NULL ){
        perror( "No memory" ) ;
        return -1 ;
        }
            instruction_fill( buf, 0, NULL ) ;
idle_time = measure( buf ) ;
            instruction_fill( buf, INSTRUCTIONS, "\xF7\xF3" ) ;
CPU_mark  = measure( buf ) - idle_time ;                /* div bx */
            instruction_fill( buf, INSTRUCTIONS, "\xC4\x07" ) ;
mem_mark  = measure( buf ) - idle_time ;          /* les ax, [bx] */
            instruction_fill( buf, INSTRUCTIONS, "\xE6\x0C" ) ;
DMA_mark  = measure( buf ) - idle_time ;          /* out 0Ch, al  */
printf( "Idle time   = %u\n", idle_time ) ;
printf( "CPU mark    = %u\n", CPU_mark ) ;
printf( "Memory mark = %u\n", mem_mark ) ;
printf( "DMA mark    = %u\n", DMA_mark ) ;
return 0 ;
}

