ATA для дZенствующих. Часть 1

Дата публикации 18 май 2004

ATA для дZенствующих. Часть 1 — Архив WASM.RU

Введение

В этом мануале речь пойдет о программировании винтов и cd-rom-ов на полностью аппаратном уровне.

В конце сего действа мы напишем на ассемблере пару процедур для работы с винчестером на уровне портов и даже рассмотрим код, который может работать с треем cd-rom.

Предупрежу сразу, DMA обмены рассматриваться в данной части мануала не будут, это тема другого большого разговора, а туман вокруг DMA я проясню в следующих выпусках.

Второй момент - все наши действия будут происходить исключительно в среде DOS. Это обусловлено тем что протокол обмена с дисками от ОС не зависит. DOS был выбран исключительно с целью абстрагироваться от архитектуры драйверов и сосредоточить внимание именно на вопросах программирования устройств.

IDE КОНТРОЛЛЕР

Стандартный IDE контроллер, применяемый в PC, поддерживает 2 канала, на каждом из которых может быть 2 устройства АТА (то есть всего может быть 4 устройства). Каждый канал имеет свою собственную часть пространства ввода-вывода. Для первого канала - 1F0h-1F7h для второго - 170h-177h. На данном этапе надо ввести понятие базового порта: в общем это лучше всего пояснить на примере:

адреса портов формируются следующим образом: базовый_порт+смещение.

Загрузив в базовый_порт значение 1F0h или 170h можно больше не думать, о том с каким каналом ты работаешь, потому что функции портов к примеру 1F3h и 173h совпадают для разных каналов IDE.

То есть для первого канала базовым портом является 1F0h, для второго - 170h, загрузив один раз эти значения в качестве базы остальные порты можно адресовать по смещениям относительно этих, т.е. сделать так, что один и тот же код сможет работать с двумя каналами. В нагрузку к этому контроллером используется еще пара портов 3F6h-3F7h для первого канала и 376h-377h для его коллеги.

Чтобы окончательно все не затуманивать составим следующую таблицу, на первых порах придется к ней часто обращаться.

Номер портаCHSLBAReadWrite
1F0h (170h)  Порт данныхПорт данных
1F1h (171h)  Порт ошибокПорт свойств
1F2h (172h)  Счетчик секторовСчетчик секторов
1F3h (173h)Номер сектораАдрес 0-7 
1F4h (174h)Цилиндр [0:7]Адрес 8-15 
1F5h (175h)Цилиндр [8:15]Адрес 16-23 
1F6h (176h)Номер головки и устройстваАдрес 24-27, устройство 
1F7h (177h)  Регистр состоянияРегистр команды
3F6h (376h)  Регистр состоянияУправление
3F7h (377h)Не используется---------

Колонка 4 и 5 показывают, что находится в регистре в зависимости от того, читаем мы или пишем.

Теперь попробуем разобраться с остальным. 2-я и 3-я колонки означают режим адресации, CHS система доживает последние дни, эта система основана на физической геометрии дисков - адрес состоит из 3 частей - Цилиндр-Головка-Сектор , откуда и название системы - Cylinder-Head-Sector - CHS. Я думаю, последнее понятие надо пояснить - представим себе несколько дисков, насаженных на общий шпиндель. Каждый диск разбит на дорожки,(концентрические, не спиральные), дорожка с одним номером для верхнего диска будет находится как-раз над соответствующей дорожкой нижнего диска. Если мысленно их объединить, они образуют цилиндр. У каждого диска 2 поверхности (одна из них, правда, используется не всегда, она служит для так называемых сервометок, особых областей, по которым контроллер диска определяет текущее положение головок и "нацеливается" на нужный цилиндр во время позиционирования. Сервометки могут присутствовать и на пластинах для записи данных - это называется встроенная сервоповерхность - embedded servo, но чаще под нее выделяют отдельную пластину в целях надежности - это называется выделенная сервоповерхность - dedicated servo), для каждой своя головка. И наконец, цилиндр разбит на секторы.

Несмотря на то, что теоретически винчестер может работать в CHS адресации, во всех новых устройствах используется LBA.

Хотя искушенные в математике могут посчитать максимальное кол-во секторов, перемножив максимальные кол-ва цилиндров, головок и секторов 65536*16*255=267386880 секторов, что приблизительно равно 127Гб, на самом деле BIOS вводит ограничение - цилиндров не больше 1024, секторов не больше 63, зато головок все 255. В результате получаем 1024*255*63=16450560 секторов, что равно жалким 8Гб (размер сектора 512 байт).

Биосовский сервис INT 13h использует именно эту систему. Использование CHS системы на полностью аппаратном уровне (с возможностью адресации всего объема диска) без сервисов оправдано только в том случае, если данные нужно записать в какое-то конкретное место, cвязанное с геометрией диска, например в первый сектор N-го цилиндра (иногда это требуется при размещении различных структур файловой системы, к примеру в системе FAT16(32) ), загрузочные секторы разделов могут находиться только в первых секторах цилиндров). С использованием же сервисов, например int 13h, встает другая проблема:

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

Совсем другое дело LBA (Logical Block Addressing) адресация, здесь есть единственный параметр - абсолютный номер сектора начиная с 0 (в CHS секторы нумеруются с 1). Контроллер диска его транслирует самостоятельно. Следовательно не надо думать о том как диск физически организован. Эта система позволяет адресовать до 128Гб данных. Все современные винчестеры и их драйверы используют именно эту систему, и мы будем также работать с ней ввиду ее исключительной простоты.

Кстати, в качестве лирического отступления - SCSI винчестеры работают только в системе LBA и позволяют адресовать до 2Тбайт информации (там для адреса 32 бита, а не 28 как в ATA).

С адресацией более-менее разобрались, займемся регистрами контроллера.

Порт 1F0h(170h) является единственным 16-битным портом контроллера, из него принимаются и в него записываются данные при работе с винтом.

Порт ошибок, он же порт 1F1h содержит коды ошибок после выполнения команды, чтобы сильно не углубляться в этот вопрос, скажу только, что если там все нули значит "все хорошо".

Регистр 1F2h используется в групповых операциях (читается или записывается группа секторов за один раз), он выступает в роли счетчика секторов, его содержимое уменьшается на единицу после обработки каждого сектора из группы.

Следующие регистры 1F3h-1F6h хранят адрес. Тут все понятно надеюсь.

Т.к. мы будем работать в LBA системе, столбцы хранящие назначение регистров адреса в CHS нам не понадобятся.

Особо стоит отметить регистр 1F6h т.к. не все его раздряды хранят адрес:

76543210
1AM1DEVLBA27LBA26LBA25LBA24

С разрядами [0:3] все понятно - туда пишем соответствующую часть LBA адреса.

Биты 5 и 7 зарезервированы, там всегда должно быть 1.

Бит AM (Addressing Mode) определяет режим адресации - 0-CHS, 1-LBA. В нашем случае там всегда будет 1.

Наконец, добрались до главного - бит DEV определяет устройство на канале (Master/Slave) к которому относится все то что пишешь в остальные порты.

0-Master, 1- Slave.

1F7h, он же регистр состояния имеет следующий формат:

76543210
BSYDRDYDFDSCDRQCORRIDXERR

BSY- устройство занято выполнением команды.
DRDY - устройство готово к приему команды.
DF - устройство неисправно.
DSC - поиск завершен.
DRQ - устройство готово к обмену данными.
CORR - была ошибка, но данные были скорректированы.
IDX - в разных источниках написано по разному, этот бит зависит от производителя, и самое верное решение - просто его игнорировать.
ERR - в процессе выполнения команды, были обнаружены ошибки, какие именно - указывает 1F1h.
Сразу оговорюсь, что мы будем реально использовать только биты BSY, DRDY и DRQ. Если с винчестером все в порядке, то остальные биты нам не пригодятся.
Наконец, порт 3F6h - в нем используются только биты 1 и 2.

1 - прерывания от устройства запрещены, 0 - разрешены.
2 - программный сброс всех подключенных к каналу устройств (при установке его в 1). Протокол PIO

Теперь разберемся с алгоритмом обмена (протоколом).

Протокол PIO (Programmable Input/Output) заключается в следующих основных положениях (при работе без прерываний):

  1. Дождаться готовности устройства (BSY=0)
  2. Записать в DEV номер устройства на канале.
  3. Дождаться BSY=0, DRDY=1 считывая 1F7h или 3F6h (для первого канала).
  4. Записать в регистры остальные параметры.
  5. Записать в регистр команды код операции.
  6. Читать регистр статуса пока устройство не установит BSY=0.
  7. Дождаться готовности обмена данными (DRQ=1)
  8. Принять данные (или передать).

Схема довольно общая, но суть, думаю, ясна...

Пока BSY установлен в 1, ничего делать нельзя, надо подождать, пока винчестер разберется со своими проблемами и будет готов заняться твоими, причем BSY=1 указывает на занятость канала, а не устройства. В этом кроется причина невозможности параллельной работы двух устройств на одном канале - прежде чем обращаться к какому-либо устройству, нужно, чтобы оба подключенных к каналу устройства освободились, так как они разделяют бит BSY между собой.

DRDY=1 показывает, что диск готов к приёму команд, поскольку согласно протоколу анализ этого бита должен происходить после выбора устройства на канале: он показывает занятость именно конкретного выбранного устройства. DRQ=1 говорит, что диск готов к обмену данными. Важно понимать разницу между DRDY и DRQ. После посылки команды в порт, устройство установит DRDY=0, что указывает на занятость устройства выполнением команды, и только после того, как данные будут считаны во внутренний буфер, устройство установит DRQ=1, при этом в протоколе не оговаривается, что устройство сразу же должно быть готово к приему следующей команды.

Для команд, не требующих обмена данными, пункты 7 и 8 теряют актуальность. При работе с прерываниями, по окончании выполнения команды устройство устанавливает запрос прерывания и его обработчик должен сделать всю остальную работу (принять данные и поместить в буфер).

В режиме DMA обмен происходит по каналу DMA, который должен быть своевременно проинициализирован хостом. После обработки команды устройство устанавливает запрос DMA и передает данные в память по каналу.

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

Теперь рассмотрим собственно команды ATA. Вообще ATA команд очень много, даже стандаритизированных около 20, но реально из них используются только штук 10.

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

Чтение секторов с винчестера выглядит следующим образом:

  1. Запретить прерывания записью в 3F6h
  2. Дождаться готовности канала читая бит BSY в порту 1F7h
  3. Выбрать устройство на канале записью в 1F6h
  4. Дождаться DRDY=1 и BSY=0
  5. Загрузить LBA адрес.
  6. Послать команду чтения (20h)
  7. Дождаться BSY=0
  8. Дождаться готовности обмена данными (DRQ=1)
  9. Принять данные от устройства через 1F0h строковой операцией ввода из порта.
  10. Разрешить прерывания от устройства.

Вот код который это делает. (Предполагается - адрес в ESI, ES=DS, в сегменте данных буфер емкостью 512 байт, 28 бит ESI адресует устройство).

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

Есть один тонкий момент, вообще команда 20h является командой чтения именно одного сектора, в абсолютном (я бы даже сказал подавляющем) большинстве винчестеров содержимое регистра 1F2h (счетчик для групповых операций с секторами) ни на что не влияет (в листинге он не фигурирует), однако могут встретится экземпляры, в которых обязательно нужно его устанавливать в 1, если на вашем винчестере данные листинги не будут работать по этой причине, просьба связаться со мной (почтовик в конце мануала).

Код (Text):
  1.  
  2. mov dx,3f6h        ;Запретить прерывания, установить в 1 бит 1 регистра 3F6h
  3. mov al,2h
  4. out dx,al
  5.  
  6. mov dx,01f7h       ;Ждем пока BSY=1, на случай если девайс занят
  7. m1:
  8. in   al,dx
  9. test al,80h
  10. jnz  m1
  11.  
  12. mov ecx,esi           ;выбор режима LBA и установка зарезервированных битов
  13. shr ecx,24
  14. or  cl,0E0h
  15.  
  16. mov dx,01f6h       ;Выбор устройства на канале.
  17. mov al,cl
  18. out dx,al
  19.  
  20. mov  dx,01f7h
  21. m2:                       ;Ждем, DRDY=1 и BSY=0
  22. in   al,dx
  23. test al,80h
  24. jnz  m2
  25. test al,40h
  26. jz   m2
  27.  
  28. mov eax,esi
  29. mov dx,1f3h    ;Загружаем адрес
  30. out dx,al          ;LBA[0:7]
  31. mov dx,1f4h
  32. shr eax,8
  33. out dx,al          ; LBA [8:15]
  34. mov dx,1f5h
  35. shr eax,8
  36. out dx,al          ; LBA [16:23]
  37.  
  38. mov dx,01f7h       ;Шлем команду чтения сектора
  39. mov al,020h
  40. out dx,al
  41.  
  42. mov  dx,03f6h
  43. m3:                       ;И снова ждем пока она будет выполнена
  44.                              ;Читаем альтернативный регистр состояния
  45. in   al,dx
  46. test al,80h
  47. jnz  m3
  48.  
  49. mov  dx,01f7h
  50. m4:                        ;Смотрим как дела с DRQ, если 1 - данные готовы.
  51. in   al,dx
  52. test al,08h
  53. jz   m4
  54.  
  55. cld                         ;Сброс флага DF в EFLAGS
  56. lea di,buffer
  57. mov dx,01f0h      
  58. mov cx,256          ;512 байт (1 сектор)
  59. rep insw               ;Прием данных.
  60.  
  61. mov dx,3f6h        ;Разрешить прерывания от устройства
  62. mov al,0
  63. out dx,al

Исходник конечно из серии "за 5 минут", так, если девайса нет, мы будем ждать до бесконечности пока BSY будет 0. Это во-первых. Во-вторых - если какие-то проблемы с чтением (мы используем команду "чтение с повторами", есть еще чтение без повторов ее код 21h, в этом случае, если сектор не прочитался с 1 раза, значит не судьба), таймаутов у нас нет, опять есть перспектива бесконечно ждать. В-третьих, нет проверки на ошибки чтения и т.д. Имея определенные навыки, исходник можно легко модифицировать с учетом этих требований, оставим это в качестве упражнения.

В случае записи на диск, все происходит аналогично, только данные естественно передаются а не принимаются. И еще, чуть не забыл, прежде чем экспериментировать с записью на диск, сначала потренируйся на каком-нибудь девайсе старом, даже если вы уверены, что все правильно... Все, предупредил.

Отдельно рассмотрим команды идентификации и остановки.

Команда IDENTIFY DEVICE возвращает целый сектор различной инфы о накопителе, ее код - EСh, параметров нет. Мы сейчас ограничимся тем, что определим объем винчестера, формат всего сектора можно найти на любом сайте, посвященном системному программированию.

(сегментная часть адреса буфера в ES)

Код (Text):
  1.  
  2. mov dx,01f7h  ;Ждем пока BSY=0
  3. m1:
  4. in   al,dx
  5. test al,80h
  6. jnz  m1
  7.  
  8. mov dx,01f6h ;Выбираем девайс
  9. mov al,0E0h  ;Адресация LBA (это не принципиально в данном случае)
  10. out dx,al
  11.  
  12. mov  dx,01f7h
  13. m2:                    ;Ждем DRDY=1 и BSY=0
  14. in   al,dx
  15. test al,80h
  16. jnz  m2
  17. test al,40h
  18. jz   m2
  19.  
  20. mov dx,01f7h
  21. mov al,0ECh  ;Код команды идентификации - ECh
  22. out dx,al
  23.  
  24. mov  dx,03f6h ;
  25. m3:                  ;Ждем пока все будет готово
  26. in   al,dx
  27. test al,80h
  28. jnz  m3
  29.  
  30. mov  dx,01f7h
  31. m4:                  ;Ждем готовности к обмену данными (DRQ=1)
  32. in   al,dx
  33. test al,08h
  34. jz   m4
  35.  
  36. cld                   ;флаг DF=0
  37. mov di,offset buffer
  38. mov dx,01f0h
  39. mov cx,256     ;512 байт
  40. rep insw
  41.  
  42. mov  ax,word ptr [buffer+60*2] ;60-61 слова в буфере - общее число секторов на          
  43.                                                     ;диске в режиме LBA
  44. mov  dx,word ptr [buffer+61*2]
  45.                         ;В DX:AX - объем диска в секторах.

Ну и наконец - команда остановки винчестера. Все абсолютно аналогично предыдущему пункту, только код - E6h, и никаких данных принимать не надо, сразу после посылки в 1F7h байта E6h веник отправляется смотреть сладкие сны ( процесс осановки будет слышно). Будить его лучше всего сбросом (установкой бита 2 в 3F6h).

Ссылки на сайты производителей HDD:

  • Fujitsu - www.fujitsu.com или www.fujitsu.com.tw
  • IBM - www.storage.ibm.com
  • Maxtor - www.maxtor.com
  • Quantum - www.quantum.com
  • Seagate - www.seagate.com
  • Samsung - www.samsung.com
  • Western Digital - www.wdc.com

    К мануалу прилагается 2 исходника:

    ATA_ID.asm считывает размер винчестера в секторах (х512 если надо в байтах)

    ATA_READ.asm содержит процедуру чтения данных, в качестве демонстрации выводится 511-ый байт MBR - в десятичном виде 170 (AAh).

    Ну вот мы и подошли к концу первой части, если есть вопросы\пожелания\предложения\позитивная критика милости просим мылить сюда Dark_Master@tut.by , в следующей части мануала рассмотрим DMA и программирование устройств ATAPI, а также напишем обещанную прогу которая умеет открывать\закрывать\блокировать\разблокировать лоток CD-ROM-а. Ну все, до скорой (надеюсь) встречи.

    P.S. Все листинги тестились как на реальных компах, так и на эмуляторах типа Vmware. Если вы столкнулись с проблемой неработоспособности программ из этого мануала, пожалуйста, сообщите об этом мне. © Dark_Master


  • 0 3.889
    archive

    archive
    New Member

    Регистрация:
    27 фев 2017
    Публикаций:
    532