Доступ посредствомчерез SPTI
Одно из интереснейших архитектурных особенностей операционной системы Windows NT заключается в ее умении взаимодействовать с IDE-?устройствами посредствомчерез SCSI-интерфейса! К сожалению, данная технология чрезвычайно скудно документирована —– Platform SDK, MSDN, DDK содержат лишь обрывки информации, а имеющиеся примеры крайне ненаглядны и к тому же выполнены с большим количеством фактических ошибок, так что разобраться с ними под силу лишь профессионалу ну или очень настырному новичку.
Замечание
В общем-то это ситуация вполне логичнао —– ведь Microsoft не имеет к ATAPI/SCSI-интерфейсам ни малейшего отношения, и их стандартизацией занимаются совершенно иные комитеты. Однако в "приличных домах" так все-таки не поступают. Вместо того, чтобы оставить программиста со своими проблемами наедине, составители документации могли бы по крайней мере нарисовать общую картину взаимодействия. Попробуйте выкачать из Сети Интернет тысячи страниц технической документации (большей частью ненужной, но кто ж это знает заранее!) и, проштудировав ее всю, попытаться свести эту разрозненную картину воедино.
И, судя по сообщениям в телеконференциях, многим программистам осилить технику управления устройствами посредствомчерез SCSI-интерфейса так и не удалось, поэтому имеет смысл рассмотреть эту проблему поподробнее.
Для решения поставленной задачи нам понадобяиться:
5. а) Описание SCSI--интерфейса (см. документ "SCSI Architecture Model –— 3", описывающий общие концепции SCSI-архитектуры и "SCSI Primary Commands –— 3", определяющий базовый набор команд для всех SCSI-устройств; черновые версии обоих документах доступны в электронном виде и лежат по адресамздесь http://www.t10.org/ftp/t10/drafts/sam3/sam3r08.pdf и здесь http://www.t10.org/ftp/t10/drafts/spc3/spc3r14.pdf
соответственно; в качестве пособия "быстрого старта" рекомендую "The Linux SCSI programming HOWTO", который можно найти по адресуздесь: http://www.ibiblio.org/pub/Linux/docs/HOWTO/other-formats/pdf/SCSI-Programming-HOWTO.pdf).;
6. б) Описание SCSI-команд, специфичных для оптических накопителей (см. документ "Multimedia Commands –— 4", описывающий принципы программирования CD-ROM/R/ RW накопителей, электронную версию которого можно найти, в частности, по адресуздесь: http://www.t10.org/ftp/t10/drafts/mmc4/mmc4r02b.pdf).;
7. в) Описание ATAPI-интерфейса для CD-ROM/DVD накопителей
(см. например, "ATA Packet Interface for CD-ROMs" и "Specification for ATAPI DVD Devices", причем, спецификации на DVD гораздо лучше и полнее описывают архитектуру CD-ROM, чем его родная документация; не самые свежие, но вполне подходящие ревизии можно найти по адресамздесь: www.stanford.edu/~csapuntz/specs/INF-8020.PDF
и ftp.seagate.com/sff/INF-8090.PDF; описания SCSI- и ATAPI- команд во многом дублируют друг друга, однако некоторые особо тонкие моменты лучше описываются то в одном, то в другом руководстве, поэтому профессиональные программисты должны иметь оба).;
8. в) Описание форматов хранения данных на лазерных дисках (см. стандарт ECMA-130 "Data interchange on read-only 120 mm optical data disks", известный также под именем "Желтой Книги", которую можно найти по адресуздесь: http://www.ecma-international.org/publications/files/ecma-st/Ecma-130.pdf; это —– базовый стандарт для накопителей CD-?ROM накопителей;).;
9. г) Помимо этого годиться любая литература, так или иначе затрагивающая вопросы программирования CD-ROM; нелишним будет почитать "ATAPI(IDE) CD. Информация к размышлению" от Константина Норватова и "Особенности программирования CD--ROM'а на Спектруме" от Влада Сотникова.
Итак, что же такое SCSI? Это —– стандартизованный, платформенно-независимый интерфейс, обеспечивающий согласованное взаимодействие различных устройств и высокоуровневых приложений. Собственно, аббревиатура SCSI именно так и расшифровывается —– Small Computer System Interface (системный интерфейс малых компьютеров).
Благодаря интерфейсу SCSI для низкоуровневого управления устройствами совершенно необязательно прибегать к написанию собственных драйверов (писать драйвер только для того, чтобы прорваться сквозь ограничения API –— чистейший маразм), ведьи эту задачу можно решить и на прикладном уровне, посылая устройству специальные CDB-блоки, содержащие стандартные или специфичные для данного устройства команды управления вместе со всеми необходимыми им параметрами. Собственно, "CDB" так и расшифровывается —– Command Descriptor Block. Пример одного из таких блоков приведен в таблице 1.4.2ниже.:
Таблица 2.1.4.22. Пример CDB блока, который будучи переданным SCSI-устройству, заставляет его прочитать 0x69-сектор
Смещение, байт |
Содержимое |
|
0x0 |
0x28 |
Код команды "read sector" |
0x1 |
0x00 |
Зарезервировано |
0x2 |
0x00 |
Номер сектора –— 0х69 |
0x3 |
0x00 |
|
0x4 |
0х00 |
|
0x5 |
0x69 |
|
0x6 |
0x00 |
Количество секторов |
0x7 |
0x01 |
|
0x8 |
0x00 |
Зарезервировано |
0x9 |
0x00 |
Зарезервировано |
0xA |
0x00 |
Зарезервировано |
Краткое описание стандартных SCSI-команд можно найти в том же "The Linux SCSI programming HOWTO", однако для наших целей их вряд ли окажется достаточно, и команды, специфичные для дисков CD-ROM дисков, мы рассмотрим отдельно. Однако это произойдет не раньше, чем мы разберемся как CDB-блоки упаковываются в SRB-конверт (SCSI Request Block), без которого операционная система просто не поймет, что же мы хотим сделать (как известно, компьютернаямашинная программа выполняет то, что ей приказали сделать, иногда это совпадает с тем, что от нее хотели, иногда нет).
Структура SRB-блока подробно описана в NT DDK, поэтому не будем подробно на ней останавливаться и пробежимся по основным полям лишь вкратце (листинг 1.4.6).
Листинг 2.1.4.6. КратноеКраткое описание структуры SCSI_REQUEST_BLOCK
typedef struct _SCSI_REQUEST_BLOCK {
USHORT Length; // длина структуры SCSI_REQUEST_BLOCK
UCHAR Function; // функция (обычно SRB_FUNCTION_EXECUTE_SCSI == 0, т.е.
// отправить устройству команду на выполнение)
UCHAR SrbStatus; // здесь устройство отображает прогресс выполнения
// команды, наиболее часто встречаются значения:
// SRB_STATUS_SUCCESS == 0x1 – команда завершена успешно
// SRB_STATUS_PENDING == 0x0 – команда еще выполняется
// SRB_STATUS_ERROR == 0x4 – произошла ошибка
// также возможны и другие значения, перечисленные в DDK
UCHAR ScsiStatus; // здесь устройство возвращает статус завершения команды
// и, если не SUCCESS, значит, произошел ERROR
UCHAR PathId // SCSI-порт, на котором сидит контроллер устройства
// для "виртуальных" SCSI устройств всегда 0
UCHAR TargetId; // контроллер устройства на шине
// для IDE устройств обычно 0 – primary, 1 – secondary
UCHAR Lun; // логический номер устройства внутри контроллера
// для IDE устройств обычно 0 – master, 1 – slayer
CHAR QueueTag; // обычно не используется и должно быть равно нулю
CHAR QueueAction; // обычно не используется и должно быть равно нулю
CHAR CdbLength; // длина CDB-блока, для ATAPI-устройств всегда 12 (0Ch)
CHAR SenseInfoBufferLength; // длина SENSE-буфера (о нем ниже)
LONG SrbFlags; // флаги.
обычно принимают два значения
// SRB_FLAGS_DATA_IN == 0x40 – перемещение данных от
// устройства к компьютеру (чтение)
// SRB_FLAGS_DATA_OUT == 0x80 – перемещение данных от
// компьютера к устройству (запись)
ULONG DataTransferLength; // длина блока читаемых/записываемых данных
LONG TimeOutValue; // время вылета по тайм-ауту в секундах
PVOID DataBuffer; // указатель на буфер c читаемыми/записываемыми данными
PVOID SenseInfoBuffer; // указатель на SENSE буфер (о нем – ниже)
struct _SCSI_REQUEST_BLOCK *NextSrb; // указатель на след. SRB. Обычно не исп.
PVOID OriginalRequest; // указатель на IRP. Практически не используется
PVOID SrbExtension; // обычно не используется и должно быть равно нулю
UCHAR Cdb[16]; // собственно, сам CDB-блок
} SCSI_REQUEST_BLOCK, *PSCSI_REQUEST_BLOCK;
Заполнив поля структуры SCSI_REQUEST_BLOCK подобающим образом, мы можем передать SRB-блок выбранному нами устройству посредством функции DeviceIoControl, просто задав соответствующий код IOCTL. Вот, собственно, и все! Заглотнув наживку, операционная система передаст CDB-блок соответствующему устройству, и оно выполнит (или не выполнит) содержащуюся в нем (СDB-блоке) команду. Обратите внимание: CDB-блок обрабатывается не драйвером устройства, ано самим устройством, иа потому мы имеем практически неограниченные возможности по управлению последним. И все это —– с прикладного уровня!
Теперь о грустном. Процедура управлениями устройствами довольно капризна и одно-единственное неправильно заполненное поле может обернуться категорическим нежеланием устройства выполнять передаваемые ему команды. Вместо этого будет возвращаться код ошибки или вовсе не возвратится ничего. К тому же малейшая неаккуратность может запросто испортить данные на всех жестких дисках, а потому с выбором значений TargetID и lun вы должны быть особенно внимательными! (Для автоматического определения физического адреса CD-ROM'а можно использовать SCSI-команду SCSI_INQUIRY —– см.
демонстрационный пример \NTDDK\src\win_me\block\wnaspi32 из DDK). Однако довольно говорить об опасностях (без них жизнь была бы слишком скучной), переходим к самому интересному —– поиску того самого IOCTL-кодаа, который этот SRB-блок собственно и передает.
Оказывается, напрямую это сделать не так-то просто, точнее —– легальными средствами невозможно вообще! Создатели Windows по ряду соображений решили предоставить полный доступ к полям структуры SCSI_REQUEST_BLOCK только писателям драйверов, а прикладных программистов оставили наедине со структурами SCSI_PASS_THROUGH и SCSI_PASS_THROUGH_DIRECT, —– схожими по назначению с SRB, но несколько ограниченными в своей функциональности. К счастью, на содержимое CDB-блоков не было наложено никаких ограничений, а потому возможность низкоуровневого управления железом у нас все-таки осталаись. Подробнее обо всем этом можно прочитать в разделе "9.2 SCSI Port I/O Control Codes" из NT DDK, а также из исходного текста демонстрационного примера "\NTDDK\src\storage\class\spti" из того же DDK (обратите внимание на файл spti.htm, лежащий в этом же каталоге, который достаточно подробно описывает суть управления устройством через SСSI-интерфейс).
Согласно наименованию каталога с демонстрационным примером, данный способ взаимодействия с устройством носит название SPTI и расшифровывается как SCSI Pass Through IOCTLs —– т. е. SCSI, проходящий через IOCTL. Кратко перечислим основные особенности и ограничения интерфейса SPTI-интерфейса.
q Во-первых, для передачи CDB-блоков устройству вы должны обладать привилегиями администратора, что не всегда удобно (зато безопасно!).
q Во-вторых, использование многоцелевых команд запрещено (т. е. мы не можем отдать команду копирования данных с устройства А на устройство Б в обход процессора, хотя такие команды у современных приводов есть, и было бы очень здорово копировать лазерные диски совершенно не загружая процессор).
q В-третьих, реверсивное ( то бишь двунаправленное) перемещенниеперемещение данных не поддерживается, и в каждый момент времени данные могут перемещаться либо от устройства к компьютеру, либо от компьютера к устройству, но не то и другое одновременно!).
q В-четвертых, при установленном class-драйвере для целевого устройства, мы должны направлять CDB-блоки именно class-драйверу, ано не самому SCSI-устройству. То есть, для управления CD-ROM'ом вы должны взаимодействовать с ним через устройство \\.\X:, где X —– буква привода, попытка же обращения к "\\.\Scsi0:" возвратит ошибку (и это, как показывает практика, основной камень преткновения неопытных программистов, начинающих программировать раньше, чем читать документацию).
Замечание
Как вариант –— можно обращаться к устройству "\\.\CdRom0" или "\\.\CdRom1" без знака двоеточия на конце, где 0 и 1 –— порядковый номер привода CD-ROM привода в системе. Вопреки распространенному заблуждению, гласящему, что устройство "\\.\CdRom0" расположено на более низком уровне, чем "\\.\X:", с точки зрения операционной системы это синонимы и, чтобы убедиться в этом, достаточно заглянуть в содержимое таблицы объектов (objdir "\DosDevice"), доказывающее, что "\\.\X:" представляет собой ни что иное, как символическую ссылку на \\.\CdRomN.
q В-пятых, на максимальный размер пересылаемых данных (MaximumTransferLength) наложены жесткие ограничения, диктуемые спецификой используемого оборудования и обслуживающего его драйвера мини- порта. Ограничения касаются как предельно допустимого размера блока данных, так и количества занятых им физических страниц. Для определения конкретных характеристик следует послать устройству команду IOCTL_SCSI_GET_CAPABILITIES, которая возвратит структуру IO_SCSI_CAPABILITIES (ищите ее определение в NTDDSCSI.h), содержащую среди всего прочего значения[Y101] MaximumTransferLength и MaximumPhysicalPages_in_bytes.
Максимальный размер пересылаемых данных вычисляется по следующей формуле: largest transfer = min (MaximumTransferLength, MaximumPhysicalPages_in_bytes).
Как вариант можно ограничиться блоками по 64 Ккилобайтовыми блоками, гарантированно поддерживаемых всеми устройствами. Буфер так же должен быть выровнен на величину кратную величине AlignmentMask[Y102] , возвращаемую в структуре IO_SCSI_CAPABILITIES. Степень выравнивания, обеспечиваемая функцией malloc, для этих целей оказывается вполне достаточнойа и при ее использовании никаких проблем не возникает. Другое дело, если выделение памяти осуществляется конструкцией "char buf[BUF_SIZE]", –— в этом случае работоспособность вашей программы уже не гарантируется.
q В-шестых, сама структура SCSI_PASS_THROUGH_DIRECT (листинг 1.4.7) содержит значительно меньше полей, причем значения полей PathId, TargetId и Lun просто игнорируются! Физический адрес устройства на шине определяется непосредственно самой операционной системой по символьному имени дескриптора устройства, которому, собственно, и посылается SCSI_PASS_THROUGH_DIRECT- запрос. Структура SCSI_PASS_THROUGH во всем похожа на структуру SCSI_PASS_THROUGH_DIRECT, но не обеспечивает передачу данных посредством DMA (Direct Memory Access).
Листинг 2.1.4.7. Формат структуры SCSI_PASS_THROUGH_DIRECT (структура SCSI_PASS_THROUGH во всем похожа на нее, но не обеспечивает передачу данных через DMA).
typedef struct _SCSI_PASS_THROUGH_DIRECT {
USHORT Length; // размер структуры SCSI_PASS_THROUGH_DIRECT
UCHAR ScsiStatus; // статус выполнения SCSI-команды устройством
UCHAR PathId; // игнорируется
UCHAR TargetId; // игнорируется
UCHAR Lun; // игнорируется
UCHAR CdbLength; // длина CDB-пакета, посылаемая устройству, байты
UCHAR SenseInfoLength; // длина SENSE-буфера для возращения ошибки
UCHAR *DataIn; // направление передачи данных
ULONG DataTransferLength; // размер буфера для обмена данными в байтах
ULONG TimeOutValue; // время вылета по тайм-ауту
PVOID DataBuffer; // указатель на буфер для обмена данными
ULONG SenseInfoOffset; // указатель на SENSE-буфер с информацией о error
UCHAR Cdb[16]; // буфер с CDB-пакетом (16 байт максимум)
}SCSI_PASS_THROUGH_DIRECT, *PSCSI_PASS_THROUGH_DIRECT;
К счастью, "цензура", в основном, коснулась тех полей, которые в реальной жизни все равно практически не используются, так что мы ровным счетом ничего не потеряли. Заполняем оставшиеся поля, и наша структура готова!
Естественно, прежде чем передать ее устройству, нам необходимо получить дескриптор, использующийся для управления этого самого устройства. Это можно сделать так как показано в листинге 1.4.8.:
Листинг 2.1.4.8. Открытие привода для получения дескриптора, использующегося для его уприавления
HANDLE hCD = CreateFile ("\\\\.\\X:", GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
Убедившись, что hCD не равно INVALID_HANDLE_VALUE, передаем полученный дескриптор вместе с самой структурой IOCTL_SCSI_PASS_THROUGHT_DIRECT функции DeviceIoControl, вызывая ее как это показано в листинге 1.4.9.следующим образом:
Листинг 2.1.4.9. Передача структуры IOCTL_SCSI_PASS_THROUGH
DeviceIoControl(hCD, 0x4D014h /* IOCTL_SCSI_PASS_THROUGH_DIRECT
*/, &srb,
sizeof(SCSI_PASS_THROUGH_DIRECT), sense_buf, SENSE_SIZE, &returned, 0);
Где srb и есть заполненный экземпляр структуры IOCTRL_SCSI_PASS_THROUGHT_DIRECT, а returned –— переменная, в которую будет записано количество байт, возращенных устройством. В свою очередь, sense_buf –— это тот самый буфер, в котором заполненный нами экземпляр структуры IOCTL_SCSI_PASS_THROUGHT_DIRECT возвращается назад, да не один, а вместе с Sense Infoинфой, –— кодом ошибки завершения операции.
Если же операция завершилась без ошибок, то Sense Info не возвращается и буфер sense_buf содержит только структуру IOCTL_SCSI_PASS_THROUGHT. Позиция размещения Sense Info в буфере определяется содержимым поля SenseInfoOffset, значение которого должно быть подобрано так, чтобы не "наступать на пятки" структуре IOCTRL_SCSI_PASS_THROUGHT, т. е., попросту говоря, минимально возможное смещение Sense Info равно: srb.SenseInfoOffset = sizeof(SCSI_PASS_THROUGH_DIRECT). Обратите внимание, SenseInfoOffset это не указатель на Sense Info, но индекс первого байта Sense Info в возвращаемом буфере!
Для определения факта наличия ошибки, необходимо проанализировать количество байт, возращенных функцией DeviceIoControl в переменной returned. Если оно превышает размер структуры IOCTL_SCSI_PASS_THROUGHT, то в буфере находится Sense Info, а раз есть Sense Info, то есть и ошибка! Формат кода ошибки Sense Info приведен на рисунке 1.4.1 .ниже:
Рис. 2.1.4.11. 0x063 [Y103] Формат кода Sense Info, возвращаемогой устройством в случае возникновения ошибки
Первый байт указывает на тип ошибки и обычно принимает значение 70h (текущая ошибка –— current error) или 71h (отсроченная ошибка –— deferred error). Коды ошибок с 72h по 7Eh зарезервированы, причем ошибки с кодом 7Eh указывают на нестандартный (vendor-specific) формат sense-info формат. Коды ошибок с 00h по 6Fh в спецификации CD-ROM ATAPI неопределенны, и потому их использование нежелательно (данное предостережение, разумеется, адресовано не программистам, а разработчикам аппаратуры).
Описание ошибки кодируется тройкой чисел: Sense Key, Additional Sense Code (дополнительный смысловой код, сокращенно ASC) и Additional Sense Code Qualifier (ASCQ). Вершину этой иерархической пирамиды возглавляет Sense Key, содержащий общую категорию ошибки (genetic categories), затем идет дополнительный смысловой код, более детально описывающий ошибку и, наконец, в самом низу иерархии находится квалификатор дополнительного смыслового кода, уточняющий непосредственно сам дополнительный смысловой код.
Если ошибка исчерпывающе описывается одним лишь кодом Sense Key и ASC, то ASCQ в таком случае отсутствует (точнее –— находится в неопределенном состоянии).
Расшифровка основных кодов ошибок описывается в двух таблицах (табл. 1.4.3 и 1.4.4)., приведенных ниже. Стоит сказать, что для анализа ошибки значение Sense Key в общем-то некритично, т. к. гарантируется, что каждый код ASC принадлежит только одному Sense Key; напротив, один и тот же код ASCQ может принадлежать нескольким различным кодам ASC, и потому в отрыве от последнего он бессмыслен.
Таблица 2.1.4.33. Основные Sense Key (категории ошибок) и их описания
Sense Key |
Описание |
00h |
NO SENSE. Нет дополнительного кода ошибкий Sense Info. Операция выполнена успешно. |
01h |
RECOVERED ERROR (восстановленная ошибка). Операция выполнена успешно, но в процессе ее выполнения возникли некоторые проблемы, устраненные непосредственно самим приводом. За дополнительной информацией обращайтесь к ключам ASC и ASCQ. |
02h |
NOT READY (не готов). Устройство не готово. |
03h |
MEDIUM ERROR (ошибка носителя). В процессе выполнения операции произошла неустранимая ошибка, вызванная, по всей видимости, дефектами носителя или ошибкой записи данных. Данный Sense Key может возвращаться и в тех случаях, когда привод оказывается не в состоянии отличить дефект носителя от аппаратного сбоя самого привода. |
04h |
HARDWARE ERROR (аппаратная ошибка). Неустранимая аппаратная ошибка (например, отказ контроллера). |
05h |
ILLEGAL REQEST (неверный запрос). Неверные параметры, переданные приводу в CDB-пакете (например, начальный адрес больше конечного). |
06h |
UNIT ATTENTION (модуль требуетмого внимания) Носитель заменен или выполнен сброс контроллера привода. |
07h |
DATA PROTECT (защищенные данные) Попытка чтения защищенных данных. |
8h –—0Ah |
Зарезервировано. |
0Bh |
ABORTED COMMAND (команда прервана). По тем или иным причинам выполнение команды было прервано. |
0Eh |
MISCOMPARE (ошибка сравнения) Исходные данные не соответствуют данным, прочитанным с носителя. |
0Fh |
Зарезервировано. |
Таблица 2.1.4.44. Основные ASC- и ASCQ- коды.
ASC |
ASCQ |
DROM |
Описание |
00 |
00 |
DROM |
NO ADDITIONAL SENSE INFORMATION |
00 |
11 |
R |
PLAY OPERATION IN PROGRESS |
00 |
12 |
R |
PLAY OPERATION PAUSED |
00 |
13 |
R |
PLAY OPERATION SUCCESSFULLY COMPLETED |
00 |
14 |
R |
PLAY OPERATION STOPPED DUE TO ERROR |
00 |
15 |
R |
NO CURRENT AUDIO STATUS TO RETURN |
01 |
00 |
R |
MECHANICAL POSITIONING OR CHANGER ERROR |
02 |
00 |
DROM |
NO SEEK COMPLETE |
04 |
00 |
DROM |
LOGICAL DRIVE NOT READY - CAUSE NOT REPORTABLE |
04 |
01 |
DROM |
LOGICAL DRIVE NOT READY - IN PROGRESS OF BECOMING READY |
04 |
02 |
DROM |
LOGICAL DRIVE NOT READY - INITIALIZING COMMAND REQUIRED |
04 |
03 |
DROM |
LOGICAL DRIVE NOT READY - MANUAL INTERVENTION REQUIRED |
05 |
01 |
DROM |
MEDIA LOAD - EJECT FAILED |
06 |
00 |
DROM |
NO REFERENCE POSITION FOUND |
09 |
00 |
DRO |
TRACK FOLLOWING ERROR |
09 |
01 |
RO |
TRACKING SERVO FAILURE |
09 |
02 |
RO |
FOCUS SERVO FAILURE |
09 |
03 |
RO |
SPINDLE SERVO FAILURE |
11 |
00 |
DRO |
UNRECOVERED READ ERROR |
11 |
06 |
RO |
CIRC UNRECOVERED ERROR |
15 |
00 |
DROM |
RANDOM POSITIONING ERROR |
15 |
01 |
DROM |
MECHANICAL POSITIONING OR CHANGER ERROR |
15 |
02 |
DRO |
POSITIONING ERROR DETECTED BY READ OF MEDIUM |
17 |
00 |
DRO |
RECOVERED DATA WITH NO ERROR CORRECTION APPLIED |
17 |
01 |
DRO |
RECOVERED DATA WITH RETRIES |
17 |
02 |
DRO |
RECOVERED DATA WITH POSITIVE HEAD OFFSET |
17 |
03 |
DRO |
RECOVERED DATA WITH NEGATIVE HEAD OFFSET |
17 |
04 |
RO |
RECOVERED DATA WITH RETRIES AND/OR CIRC APPLIED |
17 |
05 |
DRO |
RECOVERED DATA USING PREVIOUS SECTOR ID |
18 |
00 |
DRO |
RECOVERED DATA WITH ERROR CORRECTION APPLIED |
18 |
01 |
DRO |
RECOVERED DATA WITH ERROR CORRECTION & RETRIES APPLIED |
18 |
02 |
DRO |
RECOVERED DATA - THE DATA WAS AUTO-REALLOCATED |
18 |
03 |
R |
RECOVERED DATA WITH CIRC |
18 |
04 |
R |
RECOVERED DATA WITH L-EC |
1A |
00 |
DROM |
PARAMETER LIST LENGTH ERROR |
20 |
00 |
DROM |
INVALID COMMAND OPERATION CODE |
21 |
00 |
DROM |
LOGICAL BLOCK ADDRESS OUT OF RANGE |
24 |
00 |
DROM |
INVALID FIELD IN COMMAND PACKET |
26 |
00 |
DROM |
INVALID FIELD IN PARAMETER LIST |
26 |
01 |
DROM |
PARAMETER NOT SUPPORTED |
26 |
02 |
DROM |
PARAMETER VALUE INVALID |
28 |
00 |
ROM |
NOT READY TO READY TRANSITION, MEDIUM MAY HAVE CHANGED |
29 |
00 |
ROM |
POWER ON, RESET OR BUS DEVICE RESET OCCURRED |
2A |
00 |
ROM |
PARAMETERS CHANGED |
2A |
01 |
ROM |
MODE PARAMETERS CHANGED |
30 |
00 |
ROM |
INCOMPATIBLE MEDIUM INSTALLED |
30 |
01 |
RO |
CANNOT READ MEDIUM - UNKNOWN FORMAT |
30 |
02 |
RO |
CANNOT READ MEDIUM - INCOMPATIBLE FORMAT |
39 |
00 |
ROM |
SAVING PARAMETERS NOT SUPPORTED |
3A |
00 |
ROM |
MEDIUM NOT PRESENT |
3F |
00 |
ROM |
ATAPI CD-ROM DRIVE OPERATING CONDITIONS HAVE CHANGED |
3F |
01 |
ROM |
MICROCODE HAS BEEN CHANGED |
40 |
NN |
ROM |
DIAGNOSTIC FAILURE ON COMPONENT NN (80H-FFH) |
44 |
00 |
ROM |
INTERNAL ATAPI CD-ROM DRIVE FAILURE |
4E |
00 |
ROM |
OVERLAPPED COMMANDS ATTEMPTED |
53 |
00 |
ROM |
MEDIA LOAD OR EJECT FAILED |
53 |
02 |
ROM |
MEDIUM REMOVAL PREVENTED |
57 |
00 |
R |
UNABLE TO RECOVER TABLE OF CONTENTS |
5A |
00 |
DROM |
OPERATOR REQUEST OR STATE CHANGE INPUT (UNSPECIFIED) |
5A |
01 |
DROM |
OPERATOR MEDIUM REMOVAL REQUEST |
63 |
00 |
R |
END OF USER AREA ENCOUNTERED ON THIS TRACK |
64 |
00 |
R |
ILLEGAL MODE FOR THIS TRACK |
B9 |
00 |
R |
PLAY OPERATION OBORTED |
BF |
00 |
R |
LOSS OF STREAMING |
Как видите, —– все просто! Единственное, с чем мы еще не разобрались, –— это ATAPI. Поскольку мы не собираемся взаимодействовать с ATAPI-интерфейсом напрямую (этой возможности "благодаря" архитекторам Windows мы, увы, лишены) "промчимся галопом" лишь по ключевым аспектам и особенностям. Как пишет Михаил Гук в своей книге "Интерфейсы персональных компьютеров": "Для устройств, логически отличающихся от жестких дисков —– оптических, магнитооптических, ленточных и любых других —– в 1996 г. была принята спецификация ATAPI. Это пакетное расширение интерфейса, которое позволяет передавать по шине ATA устройству блоки командной информации, структура которых была позаимствована из SCSI". Теперь, по крайней мере, становится понятно, почему Windows так лихо "превращает" ATAPI-устройства в SCSI. Если отбросить аппаратные различия интерфейсов, которые с программного уровня все равно не видны, то ATAPI-интерфейс будет очень напоминать SCSI. Во всяком случае, управление ATAPI-устройствами осуществляется посредством тех самых CDB-блоков, которые мы уже рассматривалирассмотрели ранеевыше.
Естественно, чтобы управлять устройством, необходимо знать, какими именно командами оно управляется. Для получения этой информации нам понадобится документ "ATAPI Packet Commands for CD-ROM devices". Откройте его на описании команды READ CD command
(код BEh) и вы обнаружите таблицу формата этой команды (рис. 1.4.2).следующего содержания:
Рис. 2.1.4.22. ФорматОписание команды READ CD
Попробуем в ней разобраться. Первый байт (точнее байт 0), представляющий собой код выполняемой команды, никаких вопросов не вызывает, но вот дальше мы сталкиваемся с полем Expected Sector Type, задающим тип требуемого сектора. Перевернув несколько страниц вперед, мы найдем коды, соответствующие всем существующим типам секторов: CDDA, Mode 1, Mode 2, Mode 2 Form 1 и Mode 2 Form 2.
Если же тип сектора заранее неизвестен, передавайте с этим полем 0x0, что обозначает "нас устроит любой тип сектора".
Следующие четыре байта занимает адрес первого читаемого сектора (Starting Logical Block Address), заданный в формате LBA (т. е. Logical Block Address). За этой "страшной" аббревиатурой скрывается элегантный способ сквозной нумерации секторов. Если вы когда-то программировали "древние" жесткие диски, то наверняка помните, какие громоздкие расчеты приходилось выполнять, чтобы определить к какой головке, цилиндру, сектору каждый байт прилежит. Теперь же можно обойтись безо всех этих заморочек. Первый сектор имеет номер 0, затем идет 1, 2, 3… и так до последнего сектора диска. Только помните, что порядок байт в этом двойном слове обратный, –— т. е. старший байт старшего слова идет первым.
Байты с шестого по восьмой "оккупировал" параметр, задающий количество читаемых секторов (Transfer Length in Blocks). Вот какая несправедливость —– для адреса сектора выделяется четыре байта, а для количества читаемых секторов только три. Шутка! Вы же ведь не собираетесь читать весь диск за раз?! Порядок байт здесь также обратный, так что не ошибитесь, иначе при попытке считать один-единственный сектор вы запросите добрую половину диска целиком!
Девятый байт наиболее интересен, ибо он хранит флаги, определяющие, какие части сектора мы хотим прочитать (Flag Bits). Помимо пользовательских данных, мы можем запросить синхробайты (Synch Field), заголовок (Header(s) code), EDC/ECC коды (EDC & ECC) и даже флаги ошибок чтения (Error Flag(s)) (для взлома некоторых защит это самое то! —– правда, эту возможность поддерживают не все приводы).
Десятый байитт (Sub-Channel Data Selection Bits) отвечает за извлечение данных изх подканалов, однако поскольку эти же самые данные уже содержатся в заголовке, то без них можно, в принципе, и обойтись.
Наконец, последний, одиннадцатый, считая от нуля, байт, никак не используется и зарезервирован на будущее, а потому для гарантии совместимости с новыми моделями приводов, он должен быть равен нулю.
Естественно, в зависимости от рода и количества запрашиваемых данных, длина возращенного сектора может варьироваться в очень широких пределах (табл. 1.4.5). Вот, смотрите:
Таблица 2.1.4.5 . 0х035 Взаимосвязь [Y104] рода запрошенных данных и длины возвращаемого сектора, старндаррые режимы выдлены серым
Data to be transferred |
Flag Bits |
CD-DA |
Mode 1 |
Mode 2 non XA |
Mode 2 Form 1 |
Mode 2 Form 2 |
User Data |
10h |
2352 |
2048 |
2336 |
2048 |
2338 |
User Data + EDC/ECC |
18h |
(10h) |
2336 |
(10h) |
2336 |
(10h) |
Header Only |
20h |
(10h) |
4 |
4 |
4 |
4 |
Header Only + EDC/ECC |
28h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
Header & User Data |
30h |
(10h) |
2052 |
2340 |
Illegal |
Illegal |
Header & User Data + EDC/ECC |
38h |
(10h) |
2344 |
(30h) |
Illegal |
Illegal |
Sub Header Only |
40h |
(10h) |
8 |
8 |
8 |
8 |
Sub Header Only + EDC/ECC |
48h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
Sub Header & User Data |
50h |
(10h) |
(10h) |
(10h) |
2056 |
2336 |
Sub Header & User Data + EDC/ECC |
58h |
(10h) |
(10h) |
(10h) |
2344 |
(50h) |
All Header Only |
60h |
(10h) |
12 |
12 |
12 |
12 |
All Header Only + EDC/ECC |
68h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
All Header & User Data |
70h |
(10h) |
(30h) |
(30h) |
2060 |
2340 |
All Header & User Data + EDC/ECC |
78h |
(10h) |
(30h) |
(30h) |
2340 |
2340 |
Sync & User Data |
90h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
Sync & User Data + EDC/ECC |
98h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
Sync & Header Only |
A0h |
(10h) |
16 |
16 |
16 |
16 |
Sync & Header Only + EDC/ECC |
A8h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
Sync & Header & User Data |
B0h |
(10h) |
2064 |
2352 |
Illegal |
Illegal |
Sync & Header & User Data + EDC/ECC |
B8h |
(10h) |
2344 |
(30h) |
Illegal |
Illegal |
Sync & Sub Header Only |
C0h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
Sync & Sub Header Only + EDC/ECC |
C8h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
Sync & Sub Header & User Data |
D0h |
(10h) |
(10h) |
(10h) |
Illegal |
Illegal |
Sync & Sub Header & User Data + EDC/ECC |
D8h |
(10h) |
(10h) |
(10h) |
Illegal |
Illegal |
Sync & All Headers Only |
E0h |
(10h) |
24 |
24 |
24 |
24 |
Sync & All Headers Only + EDC/ECC |
E8h |
(10h) |
Illegal |
Illegal |
Illegal |
Illegal |
Sync & All Headers & User Data |
F0h |
(10h) |
2064 |
2352 |
2072 |
2352 |
Sync & All Headers & User Data + EDC/ECC |
F8h |
(10h) |
2352 |
(F0h) |
2352 |
(F0h) |
Repeat All Above and Add Error Flags |
02h |
294 |
294 |
294 |
294 |
294 |
Repeat All Above and Add Block & Error Flags |
04h |
296 |
296 |
296 |
296 |
296 |
Рис. 2.3. 0х035 взаимосвязь рода запрошенных данных и длины возвращаемого сектора
Рис. 2.43. 0х033 Внутренний мир Windows NT.
IDE-устройства с прикладного уровня видятся как SCSI. Разумеется, на физическом уровне с приводом не происходит никаких изменений, и привод CD-ROM привод с IDE-–интерфейсом так IDE-приводом и остается, со всеми присущими ему достоинствами и недостатками. Однако IRP-запросы (I/O Request Packet) к этому драйверу, проходя через Storage class driver, транслируются в блок SRB (SCSI Request Block). Затем SRB-запросы попадают в Storage port driver (т. е. непосредственно в сам драйвер привода), где они заново транслируются в конкретные физические команды данного устройства (см. рис. 1.4.30х033). Подробности этого увлекательного процессора можно почерпнуть из набора NT DDK (см. "1.4.1 Storage Driver Architecture"), здесь же достаточно указать на тот немаловажный факт, что, кроме команд семейства IRP_MJ_ххх, мы также можем посылать устройству и SRB-запросы, которые обладают значительно большей свободной и гибкостью. Однако, такое взаимодействие невозможно осуществить непосредственного с прикладного уровня, поскольку, IRP-команды относятся к числу приватных
команд, в то время как API-функция DeviceIoControl передает лишь публичные команды, явно обрабатываемые драйвером в диспетчере IRP_MJ_DEVICE_CONTROL.
Рис. 1.4.3. 0х033 Внутренний мир Windows NT
Давайте теперь, в порядке закрепления всего вышесказанного, попытаемся создать программу, которая бы читала сектора с лазерных дисков в "сыром" виде. Ее ключевой фрагмент (вместе со всеми необходимыми комментариями) приведен в листинге 1.4.10.ниже:
Листинг 2.1.4.10. [/SPTI.raw.sector.read.c] Функция, читающая сектора в "сыром" виде посредствомчерез SPTI
#define RAW_READ_CMD 0xBE // ATAPI RAW READ
#define WHATS_READ 0xF8 // Sync & All Headers & User Data + EDC/ECC
#define PACKET_LEN 2352 // длина одного сектора
//#define WHATS_READ 0x10 // User Data
//#define PACKET_LEN 2048 // длина одного сектора
//-[SPTI_RAW_SECTOR_READ]------------------------------------------------------
// функция читает один или несколько секторов с CDROM в сыром (RAW) виде,
// согласно переданным флагам
//
// ARG:
// CD - что открывать (типа "\\\\.\\X:" или "\\\\.\\CdRom0")
// buf - буфер куда читать
// buf_len - размер буфера в байтах
// StartSec - с какого сектора читать, считая от нуля
// N_SECTOR - сколько секторов читать
// flags - что читать (см. спецификацию на SCSI/ATAPI)
//
// RET:
// !=0 - функция завершилась успешно
// ==0 - функция завершилась с ошибкой
//
// NOTE:
// - работает только под NT/W2K/XP и требует прав администратора
//
// - 64 Кб данных за раз максимум
//-----------------------------------------------------------------------------
SPTI_RAW_SECTOR_READ(char *CD,char *buf,int buf_len,int StartSec,int N_SEC,char flags)
{
HANDLE hCD;
SCSI_PASS_THROUGH_DIRECT srb;
DWORD returned, length, status;
// ОТКРЫВАЕМ УСТРОЙСТВО
hCD = CreateFile ( driver, GENERIC_WRITE|GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,0,0);
if (hCD == INVALID_HANDLE_VALUE) { printf("-ERR: open CD\n"); return 0;}
// ФОРМИРУЕМ SRB
memset(&srb,0,sizeof(SCSI_PASS_THROUGH_DIRECT)); // инициализация
srb.Length = sizeof(SCSI_PASS_THROUGH_DIRECT);
srb.PathId = 0; // SCSI controller ID
srb.TargetId = 6; // игнорируется
srb.Lun = 9; // игнорируется
srb.CdbLength = 12; // длина CDB пакета
srb.SenseInfoLength = 0; // нам не нужна SenseInfo
srb.DataIn = SCSI_IOCTL_DATA_IN; // мы будем читать
srb.DataTransferLength = PACKET_LEN*N_SECTOR; // сколько мы будем читать
srb.TimeOutValue = 200; // время выхода по TimeOut
srb.DataBufferOffset = buf; // указатель на буфер
srb.SenseInfoOffset = 0; // SenseInfo на не нужна
// CDB-пакет, содержащий команды ATAPI
srb.Cdb[0] = RAW_READ_CMD; // читать сырой сектор
srb.Cdb[1] = 0x0; // формат диска - любой
// номер первого сектора для чтения, причем сначала передается старший
// байт старшего слова, а потом младший байт младшего слова
srb.Cdb[2] = HIBYTE(HIWORD(StartSector));
srb.Cdb[3] = LOBYTE(HIWORD(StartSector));
srb.Cdb[4] = HIBYTE(LOWORD(StartSector));
srb.Cdb[5] = LOBYTE(LOWORD(StartSector));
// количество секторов для чтения
srb.Cdb[6] = LOBYTE(HIWORD(N_SECTOR));
srb.Cdb[7] = HIBYTE(LOWORD(N_SECTOR));
srb.Cdb[8] = LOBYTE(LOWORD(N_SECTOR));
srb.Cdb[9] = flags; // что читать
srb.Cdb[10] = 0; // Sub-Channel Data Bits
srb.Cdb[11] = 0; // reserverd
// ОТПРАВЛЯЕМ SRB-блок ATAPI-устройству
status = DeviceIoControl(hCD, IOCTL_SCSI_PASS_THROUGH_DIRECT,
&srb, sizeof(SCSI_PASS_THROUGH_DIRECT), &srb, 0, &returned, 0);
return 1;
}
Остается отметить, что защитные механизмы, взаимодействующие с диском посредствомчерез интерфейса SPTI, элементарно ломаются установкой точки останова на функциии CreateFile/DeviceIoControl.Для предотвращения "лишних" всплытий отладчика фильтр точки останова должен реагировать только на те вызовы функции CreateFile, чей первый слева аргумент равен "\\.\X:" или "\\.\CdRomN". Автоматически, второй слева аргумент функции DeviceIoControl должен представлять собой либо структуру IOCTL_SCSI_PASS_THROUGHT, либо IOCTL_SCSI_PASS_THROUGHT_DIRECT, шестнадцатеричные значения кодов которых равны 0x4D004 и 0x4D014 соответственно.