Доступ к устройствам модели архитектуры SCSI

Для устройств, поддерживаемых драйверами семьи Model архитектуры SCSI, семья Model архитектуры SCSI обеспечивает два типа интерфейса устройства, один для эксклюзивного доступа к устройству и один для неэксклюзивного доступа к осваивающим носители устройствам только. Эта глава фокусируется о том, как использовать эти интерфейсы устройства для передачи с устройствами, поддерживаемыми драйверами семьи Model архитектуры SCSI, и включает пример кода, иллюстрирующий, как получить доступ к диску CD-R/W.

Если необходимо получить доступ к устройству Параллели SCSI, и приложение должно работать в версиях OS X до v10.2, видеть Доступ Устройства Параллели SCSI для получения информации о том, как использовать API и семьи Model архитектуры SCSI и осуждаемой семьи SCSI для нахождения устройства. (Обратите внимание на то, что APIs осуждаемой семьи SCSI не будет работать в основанном на Intel Macintosh.)

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

Поддержка устройства семьи модели архитектуры SCSI

Семья Model архитектуры SCSI поддерживает периферийные устройства, соответствующие и SCSI Основная спецификация Команд или SPC, и одному из следующих протоколов автобусного транспорта:

Семья Model архитектуры SCSI обеспечивает драйверы логической единицы в ядре, переводящие универсальные запросы I/O в специфичные для устройства команды для устройств на этих шинах, объявляющих один из следующих четырех типов периферийного устройства:

Для устройств, объявляющих другие типы периферийного устройства, такие как сканеры, лентопротяжные устройства и средние преобразователи, семья Model архитектуры SCSI обеспечивает интерфейс устройства, названный SCSITaskDeviceInterface, позволяющим приложению быть драйвером логической единицы для того устройства.

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

Семья Model архитектуры SCSI обеспечивает третий интерфейс, названный SCSITaskInterface, позволяющим приложению, действующему как драйвер логической единицы управлять объектом SCSITask в ядре. Объект SCSITask содержит команду, отправляемую в устройство, вместе с информацией, такой как состояние задачи и указатели функции обратного вызова.

Отправка SCSI или команд ATA к устройству хранения

Проектом OS X не позволяет приложениям отправлять команды SCSI или ATA в устройства хранения, если разработчик приложений также не обеспечивает драйвер устройства в ядре, поддерживающий команды. Семья Model архитектуры SCSI позволяет только одному драйверу логической единицы управлять устройством за один раз и обеспечивает драйверы логической единицы в ядре для устройств хранения (как перечислено в Поддержке Устройства семьи Модели архитектуры SCSI). Точно так же семья ATA не позволяет приложениям отправлять команды ATA непосредственно в ATA или SATA (Serial ATA) устройства. Этот проект сохраняет целостность операционной системы и улучшает безопасность данных пользователя двумя способами:

  • Произвольное приложение не может непосредственно изменить состояние устройства без явного сотрудничества пользовательского драйвера логической единицы разработчика в ядре.

  • Приложение не может обойти полномочия файловой системы, установленные пользователем путем отправки команд I/O непосредственно в устройство хранения.

И семья Model архитектуры SCSI и семья ATA обеспечивают интерфейсы устройства, позволяющие различные типы приложений доступа к устройствам. Остающиеся разделы в этой главе описывают интерфейсы устройства, которые семья Model архитектуры SCSI обеспечивает, чтобы позволить приложению отправлять команды в определенные типы устройства. Семья ATA обеспечивает интерфейс устройства, позволяющий приложениям получать SMART (самоконтроль, анализ и технология создания отчетов) данные от ATA и устройств SATA, реализующих набор функций SMART. Для получения дополнительной информации об этом интерфейсе устройства см. справочную документацию API для ATASMARTLib.h в Ссылке Платформы Набора I/O.

Если для Вашего приложения нужна информация, доступная от SCSI INQUIRY или ATA IDENTIFY DEVICE команда, однако, Вы не должны отправлять команды в устройство или использовать интерфейс устройства. Большая часть информации, предоставленной INQUIRY и IDENTIFY DEVICE команды доступны в Реестре I/O, который можно просмотреть с приложением Проводника Реестра I/O (в /Developer/Applications) или инструмент командной строки ioreg.

Если Ваше приложение должно выполнить операции I/O блочного уровня на устройстве хранения, можно получить доступ к узлу устройства неструктурированного диска BSD в /dev. Для получения дополнительной информации о том, как сделать это, посмотрите Руководство по Доступу Файла устройств для Устройств хранения.

Если Вы решите, что Ваше приложение должно отправить команды SCSI или ATA в устройства, не поддерживаемые интерфейсами устройства, предоставленными семьями Model и ATA архитектуры SCSI, то необходимо будет записать пользовательский драйвер логической единицы в ядре. Если Вы действительно создаете свой собственный драйвер логической единицы в ядре, уверены, что Вы не отправляете READ или WRITE команды от драйвера. При отправке этих команд Вы создаете вредоносный код дыры в системе безопасности, может использовать в своих интересах при помощи Вашего драйвера, чтобы считать или уничтожить данные по устройству, которое пользователь защитил путем установки прав доступа. Для получения дополнительной информации о том, как записать пользовательский драйвер логической единицы в ядре, см. Руководство по программированию Драйвера Устройства массового хранения и пример кода VendorSpecificType00.

Устройства модели архитектуры SCSI на OS X

Когда устройство обнаружено на OS X, Набор I/O находит и загружает драйверы, требуемые поддерживать его. В случае периферийного устройства Модели архитектуры SCSI, объявляющего тип периферийного устройства 00$, 05$, 07$ или 0$ E, Набор I/O загружает несколько уровней драйверов. Вместе, эти уровни известны как штабель драйвера массового хранения (для большей всесторонней информации о штабеле драйвера массового хранения, посмотрите Введение в Руководство по программированию Драйвера Устройства массового хранения).

Штабель драйвера массового хранения разделен на три основных уровня.

  • Физический взаимосвязанный уровень включает физические взаимосвязанные драйверы, поддерживающие физическое соединение устройства к шине.

  • Транспортный уровень драйвера включает драйвер логической единицы и драйвер служб протокола, переводящие универсальные запросы I/O в специфичные для устройства команды, подходящие для транспорта через определенную шину.

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

Рисунок 2-1 показывает штабель драйвера массового хранения, поддерживающий устройство массового хранения, объявляющее тип периферийного устройства 00$, 05$, 07$ или 0$ E.

Рисунок 2-1  штабель драйвера массового хранения
The mass storage driver stack

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

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

Когда устройство, объявляющее тип периферийного устройства 00$, 05$, 07$ или 0$ E, обнаружено на одной из поддерживаемых шин, Набор I/O сначала находит и загружает надлежащие физические взаимосвязанные драйверы и драйвер служб протокола. Затем объект куска Набора I/O, названный куском периферийного устройства, запрашивает устройство и публикует его тип периферийного устройства в Реестре I/O. Набор I/O тогда находит и загружает драйвер логической единицы, соответствующий опубликованный тип периферийного устройства.

Другой объект куска Набора I/O, названный объектом связи услуг устройства, затем публикует логическую единицу водительский тип в Реестре I/O. Если устройство способно к авторской разработке, семья Model архитектуры SCSI публикует SCSITaskDeviceCategory ключ в этом объекте куска. Этот ключ, со значением SCSITaskAuthoringDevice, указывает, что MMCDeviceInterface доступен для устройства. Семья Model архитектуры SCSI также добавляет глобально уникальное идентификационное значение (или GUID) к куску, который приложение может использовать для идентификации устройства.

Затем, Набор I/O находит и загружает драйвер блочной системы хранения, и, если устройством является CDROM или DVD-ROM с настоящим носителей, это загружает схема выделения разделов DVD или CD. Устройство тогда готово обработать запросы I/O от хост-системы.

SCSITaskDeviceInterface

Когда устройство, объявляющее тип периферийного устройства кроме 00$, 05$, 07$ или 0$ E, обнаружено на поддерживаемой шине, Набор I/O начинает соответствие и процесс загрузки, описанный в Устройствах модели архитектуры SCSI на OS X.

Как прежде, кусок периферийного устройства публикует тип периферийного устройства, о котором сообщает устройство. Поскольку тип периферийного устройства не составляет 00$, 05$, 07$ или 0$ E, семья Model архитектуры SCSI добавляет GUID и SCSITaskDeviceCategory ключ со значением SCSITaskUserClientDevice к куску периферийного устройства. Эта пара ключ/значение указывает, что приложение может использовать SCSITaskDeviceInterface для управления устройством.

Поскольку нет никаких драйверов логической единицы в ядре, соответствующих на типе периферийного устройства кроме 00$, 05$, 07$ или 0$ E, здании остановов штабеля драйвера после того, как публикуется кусок периферийного устройства. Рисунок 2-2 показывает штабель драйвера массового хранения, когда основанный на приложении драйвер является драйвером логической единицы для устройства, объявляющего тип периферийного устройства 01$.

Рисунок 2-2  основанный на приложении драйвер логической единицы
An application-based logical unit driver

Когда основанный на приложении драйвер для устройства без драйвера логической единицы в ядре запускается, это ищет Реестр I/O тип устройства или тип устройства, которым это может управлять. Когда это находит устройство, это приобретает SCSITaskDeviceInterface, и приложение становится драйвером логической единицы для устройства.

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

MMCDeviceInterface

Когда авторская разработка или осваивающее носители устройство, такое как CD-R/W или DVD-R/W, обнаружены на поддерживаемой шине, Набор I/O выполняет соответствие и загрузку драйверов, требуемых создавать весь штабель драйвера массового хранения (как показано на рисунке 2-1). Поскольку семья Model архитектуры SCSI осуществляет политику эксклюзивного доступа, однако, исходное приложение должно так или иначе заменить драйвер логической единицы в ядре для получения эксклюзивного доступа к устройству. Когда исходное приложение является драйвером логической единицы для устройства CD-R/W, рисунок 2-3 показывает штабель драйвера массового хранения.

Рисунок 2-3  исходное приложение
An authoring application

Исходное приложение заменяет драйвер логической единицы, с помощью семьи Model архитектуры SCSI и Набора I/O, в двух фазах:

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

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

  2. На основе информации, полученной из ее запроса, приложение резервирует носители, получает SCSITaskDeviceInterface и запрашивает эксклюзивный доступ к устройству.

Когда запрос на эксклюзивный доступ предоставляют, управление доходами драйвера логической единицы в ядре к исходному приложению. Драйвер блочной системы хранения в уровне услуг устройства также оставляет управление, и Семейство систем хранения разъединяет любые присутствовавшие схемы выделения разделов. В то время как исходное приложение имеет эксклюзивный доступ к устройству, это может выполнить авторские функции.

Когда исходное приложение завершается, драйвер логической единицы в ядре и драйвер блочной системы хранения восстанавливают управление устройством, и Семейство систем хранения восстанавливает схему выделения разделов в случае необходимости.

SCSITaskInterface

Драйверы логической единицы, или в ядре или основанный на приложении, используют объекты SCSITask связаться с устройствами. Объект SCSITask инкапсулирует всю требуемую информацию во время продолжительности жизни единственной транзакции I/O. Эта информация включает блок дескриптора команды (или CDB) надлежащий спецификации набора команд SCSI, которой устройство соответствует, состояние повторной попытки задачи и указатели функции обратного вызова.

Основанный на приложении драйвер логической единицы должен иметь эксклюзивный доступ к устройству, прежде чем это сможет создать и использовать объекты SCSITask. Поскольку основанный на приложении драйвер не может управлять объектом SCSITask в ядре непосредственно, это должно получить SCSITaskInterface. SCSITaskInterface обеспечивает доступ к объекту SCSITask в ядре — каждый объект SCSITaskInterface соответствует точно одному объекту SCSITask.

Доступ к устройствам SCSI в основанном на Intel Macintosh

Этот раздел обеспечивает обзор некоторых проблем, связанных с разработкой универсальной версии двоичных файлов приложения, получающего доступ к Модели архитектуры SCSI (или Параллель SCSI) устройство. Перед чтением этого раздела, убедиться считать Универсальные Двоичные Инструкции по Программированию. Тот документ касается архитектурных различий, и порядок байтов форматирует и обеспечивает всесторонние инструкции для модификации кода и создания универсальных двоичных файлов. Инструкции в том документе применяются ко всем типам приложений, включая тех который аппаратные средства доступа.

Прежде чем Вы создадите свое приложение как универсальный двоичный файл, удостоверьтесь что:

Устройства, соответствующие стандартам архитектуры SCSI и набора команд, возвращают данные в формате с обратным порядком байтов, независимо от собственного формата порядка байтов компьютера, в котором работает Ваше приложение. Поскольку основанный на PowerPC Macintosh является также обратным порядком байтов, необходимо исследовать приложение PowerPC на места, где Вы могли бы предположить, что никогда не должны подкачиваться многобайтовые данные.

Необходимо также искать трудно кодированный байт, загружает приложение (такое как код, всегда подкачивающий многобайтовое значение от одного формата порядка байтов до другого). Если Ваш код будет содержать такие подкачки (или он предполагает, что подкачки байта никогда не будут необходимы), то он не будет работать правильно в основанном на Intel Macintosh. Замените все трудно кодированные подкачки надлежащими условными подкачивающими байт макросами, определенными в платформе Ядра в libkern/OSByteOrder.h. Они макросы доступны для использования в любом проекте, включая высокоуровневые приложения. Например, Авторский проект Испытательного образца Модуля (в /Developer/Examples/IOKit/scsi/SCSITaskLib/Authoring/Cocoa) использование OSReadBigInt16 функция для обеспечения данных, возвращенных из диска, находится в порядке байтов порядка байтов узла, как показано в Перечислении 2-1:

Перечисление 2-1  , Гарантирующее корректный формат порядка байтов данных, читало из диска

// Here, "interface" is an instance of an MMCDeviceInterface.
- (void) testReadDiscInfo {
    UInt8            discInfoBuffer[4096];
    IOReturn         err;
    SCSITaskStatus   taskStatus;
    SCSI_Sense_Data  senseData;
    UInt16           discInfoSize;
    ...
    // Issue a READ_DISC_INFORMATION command to the device. Ask for
    // 4 bytes to get the length field.
    err = (*interface)->ReadDiscInformation (interface, discInfoBuffer, 4, &taskStatus, &senseData);
    // ...If the command completed successfully:
    // The first word contains the size. Add 2 bytes for the first word since it isn't counted
    discInfoSize = OSReadBigInt16 ( discInfoBuffer, 0 ) + sizeof ( UInt16 );
    ...
}

К счастью, спецификация модели команды SCSI определяет CDB (блок дескриптора команды) как массив байтов. Это означает, что эти байты сохранены в определенном порядке независимо от собственного формата порядка байтов компьютера, в котором работает Ваше приложение. Поскольку Вы создаете команды CDB с помощью объекта SCSITaskInterface, Вы не должны волноваться о формате порядка байтов значений, которые Вы используете.

Вы, возможно, должны выполнить свопинг байта на значениях Ваши чтения приложения от SCSI Устройство модели Параллельной или архитектуры SCSI. Необходимо ли выполнить свопинг байта на значениях, которые Вы отправляете и получаете в параметрах к функциям интерфейса устройства, зависит от типа значения:

Используя интерфейсы устройства семьи модели архитектуры SCSI

Устройство взаимодействует через интерфейс, семья Model архитектуры SCSI обеспечивает, плагины, указывающие функции, которые Ваше приложение может вызвать для передачи с устройством. Эти интерфейсы определяются в /System/Library/Frameworks/IOKit.framework/Headers/scsi/SCSITaskLib.h

Прежде чем можно будет использовать эти интерфейсы, однако, необходимо найти устройство, которым Вы интересуетесь. Устройство, соответствующее, является процессом использования функций Набора I/O для поиска Реестра I/O определенные устройства или типы устройства. Этот процесс варьируется в зависимости от того, разрабатываете ли Вы исходное приложение или основанный на приложении драйвер для устройства, не имеющего никакого драйвера логической единицы в ядре. Следующие два раздела описывают, как выполнить устройство, соответствующее для обоих случаев. Затем Доступ к Устройству обрисовывает в общих чертах остающиеся шаги в процессе получения и использования интерфейсов устройства семьи Model архитектуры SCSI.

Устройство, соответствующее для способных к авторской разработке устройств

При разработке исходного приложения Вы сначала создаете соответствующий словарь, соответствующий на способных к авторской разработке Устройствах модели архитектуры SCSI. Для создания словаря Вы используете Базовые функции Основы в качестве в этом примере:

CFMutableDictionaryRef          matchingDictionary;
 
matchingDictionary = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL );

Теперь необходимо поместить пару ключ/значение в соответствующий словарь. Ключ IOPropertyMatch ключ и значение являются подсловарем, который Вы создаете. Подсловарь содержит SCSITaskDeviceCategory ключ со значением SCSITaskAuthoringDevice. Перечисление 2-2 показывает, как создать подсловарь.

Перечисление 2-2  , Создающее подсловарь для SCSITaskDeviceCategory

CFMutableDictionaryRef          subDictionary;
 
subDictionary = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL );
CFDictionarySetValue ( subDictionary, CFSTR ( kIOPropertySCSITaskDeviceCategory ), CFSTR ( kIOPropertySCSITaskAuthoringDevice ) );

Если Вы не должны выполнять более определенный поиск, Вы добавляете IOPropertyMatch ключ и этот подсловарь к соответствующему словарю Вы создали ранее, как в этом примере:

CFDictionarySetValue ( matchingDictionary, CFSTR ( kIOPropertyMatchKey ), subDictionary );

Можно совершенствовать этот поиск путем добавления других свойств для соответствия на. Например, можно определить соответствие на GUID устройства. Вы добавляете пару ключ/значение для GUID к подсловарю, который Вы создали выше, как в этом примере:

CFDictionarySetValue ( subDictionary, CFSTR ( kIOPropertySCSITaskUserClientInstanceGUID ), myGUID );

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

Подсловарь Характеристик Протокола может содержать или или оба из следующих ключей:

  • Physical Interconnect

  • Physical Interconnect Location

Подсловарь Характеристик устройства может содержать любой из следующих ключей:

  • Vendor Name

  • Product Name

  • Product Revision Level

Если, например, необходимо соответствовать на определенном поставщике и названии продукта, можно создать подсловарь, содержащий те пары ключ/значение, и добавьте его к SCSITaskDeviceCategory подсловарь Вы создали ранее. Перечисление 2-3 показывает, как сделать это.

Перечисление 2-3  , Создающее подсловарь характеристик устройства

#define kMyVendorNameString     “MyVendorName”
#define kMyProductNameString    “MyProductName”
 
CFMutableDictionaryRef          deviceCharacteristicsDict;
 
deviceCharacteristicsDict = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL );
CFDictionarySetValue ( deviceCharacteristicsDict, CFSTR ( kIOPropertyVendorNameKey ), CFSTR ( kMyVendorNameString ) );
CFDictionarySetValue ( deviceCharacteristicsDict, CFSTR ( kIOPropertyProductNameKey ), CFSTR ( kMyProductNameString ) );
 
CFDictionarySetValue ( subDictionary, CFSTR ( kIOPropertyDeviceCharacteristicsKey ), deviceCharacteristicsDict );

Устройство, соответствующее для способных к неавторской разработке устройств

Для приложения, управляющего устройством без драйвера логической единицы в ядре, Вы создаете соответствующий словарь, содержащий IOPropertyMatch ключ. Значение этого ключа является подсловарем, который Вы создаете, который содержит SCSITaskDeviceCategory ключ и значение SCSITaskUserClientDevice. Перечисление 2-4 показывает, как сделать это.

Перечисление 2-4  , Создающее соответствующий словарь для неавторского устройства

CFMutableDictionaryRef          matchingDictionary;
CFMutableDictionaryRef          subDictionary;
 
matchingDictionary = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL );
subDictionary = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL );
 
CFDictionarySetValue ( subDictionary, CFSTR ( kIOPropertySCSITaskDeviceCategory ), CFSTR ( kIOPropertySCSITaskUserClientDevice ) );
 
CFDictionarySetValue ( matchingDictionary, CFSTR ( kIOPropertyMatchKey ), subDictionary );

Можно совершенствовать этот поиск путем соответствия на GUID устройства. Перед добавлением подсловаря к соответствующему словарю Вы помещаете пару ключ/значение, представляющую GUID в подсловаре, как в этом примере:

CFDictionarySetValue ( subDictionary, CFSTR ( kIOPropertySCSITaskUserClientInstanceGUID ), myGUID );

Доступ к устройству

Шаги, которые Вы предпринимаете для доступа к устройству, разделены на два набора. Первый набор применяется только к исходным приложениям. Второй набор применяется к и основанным на приложении драйверам устройств без драйверов логической единицы в ядре и исходным приложениям, уже завершившим первый набор шагов. Пример кода в Доступе к Устройству модели архитектуры SCSI получает доступ к устройству CD-R/W и следует за обоими наборами шагов.

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

  1. Получите интерфейс MMCDeviceInterface устройства. Этот интерфейс устройства обеспечивает функции, дающие Вам информацию о носителях в устройстве, таких как его размер и является ли это пробелом.

  2. Используйте функции MMCDeviceInterface для запросов устройства, при необходимости.

  3. Если диск в настоящее время находится в диске, зарезервируйте носители.

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

  1. Получите интерфейс SCSITaskDeviceInterface устройства. Этот интерфейс устройства обеспечивает функции, чтобы помочь Вам подготовить отправлять объекты SCSITask в устройство. Это также осуществляет политику эксклюзивного доступа и обеспечивает функции для добавления асинхронных механизмов обратного вызова к циклу выполнения.

  2. Запросите эксклюзивный доступ к устройству. Чтобы сделать это, Вы используете функцию SCSITaskDeviceInterface ObtainExclusiveAccess.

  3. Создайте объект SCSITask связаться с устройством. Чтобы сделать это, Вы используете функцию SCSITaskDeviceInterface CreateSCSITask.

Доступ к устройству модели архитектуры SCSI

Этот раздел обеспечивает поэтапные инструкции, включая пример кода, для доступа к диску CD-R/W. Пример кода находит устройство в Реестре I/O путем установки соответствующего словаря как тот в Устройстве, Соответствующем для Способных к авторской разработке Устройств, и затем следует за первым набором шагов в Доступе к Устройству для получения MMCDeviceInterface. Затем это следует за вторым набором шагов, чтобы получить SCSITaskDeviceInterface и запросить эксклюзивный доступ.

Когда пример кода имеет эксклюзивный доступ к диску CD-R/W, это получает SCSITaskInterface и отправляет объекты SCSITask протестировать устройство. Пример кода также показывает, как установить уведомления для динамического дополнения и демонтажа устройства.

Установка Вашего проекта

Пример кода в этой главе из проекта XCode, создающего инструмент Core Foundation. При необходимости в подробной документации относительно использования XCode, можно найти его в Руководствах> Инструменты> XCode.

Для установки XCode для создания примера кода сделайте следующее:

  1. Выберите «Core Foundation Tool» при создании проекта. Это создает скелетное main.c файл и включает CoreFoundation.h в проекте.

  2. Использование Добавляет Платформы... из меню Projects для добавления IOKit.framework и System.framework к Вашему проекту.

  3. Замените main.c файл с Перечислением 2-5 через Перечисление 2-14, включительно.

  4. Создайте свой код в Мужественном формате исполняемых файлов (формат XCode по умолчанию).

  5. Рекомендуется запустить путем создания с отладки включенного. Чтобы сделать это в XCode, откройте целевой список путем щелчка по треугольнику раскрытия. Дважды щелкните по единственной перечисленной цели. В инспекторе, появляющемся, щелкните по вкладке сборки, затем щелкните по флажку рядом с, 'Генерируют Отладочные символы'.

Перечисление 2-5 показывает заголовочные файлы, которые необходимо будет включать для примера кода в эту главу.

  Заголовочные файлы перечисления 2-5

#include <IOKit/IOKitLib.h>
#include <IOKit/scsi-commands/SCSITaskLib.h>

Пример кода в этой главе использует глобальные переменные в Перечислении 2-6 для установки асинхронных уведомлений для дополнения устройства и удаления.

  Глобальные переменные перечисления 2-6

static IONotificationPortRef    gNotifyPort;
static io_iterator_t            gAppearedIter;
static io_iterator_t            fDisappearedIter;

Создание основной функции

Пример кода для работы с диском CD-R/W, поддерживаемым драйвером семьи Model архитектуры SCSI, начинается main функция, показанная в Перечислении 2-7. Эта функция выполняет следующие задачи.

  • Это устанавливает сигнальный обработчик (показанный в Перечислении 2-8), чтобы заботиться о прерывании командной строки, используемом для выхода из цикла выполнения.

  • Это устанавливает связь с Набором I/O и устанавливает соответствующий словарь для нахождения авторских устройств.

  • Это устанавливает асинхронное уведомление, которое вызывают, когда устройство сначала присоединено к Реестру I/O и другому, которого вызывают, когда демонтировано устройство.

  • Это запускает цикл выполнения, таким образом, будут получены уведомления.

main функционируйте использует функции Набора I/O, чтобы установить и изменить соответствующий словарь и установить уведомления и Базовые функции Основы для установки цикла выполнения для получения уведомлений. Это вызывает следующие функции для получения доступа к устройству.

  • DeviceAppeared (показанный в Перечислении 2-9), выполняет итерации по набору согласующих устройств и вызовов TestDevice (показанный в Перечислении 2-11) для каждого.

  • DeviceDisappeared (показанный в Перечислении 2-10), выполняет итерации по набору согласующих устройств и выпускает каждого поочередно.

Перечисление 2-7  , Настраивающее основную функцию для доступа к устройству CD-R/W

int main (int argc, const char *argv[])
{
    mach_port_t         masterPort;
    CFMutableDictionaryRef  matchingDict;
    CFMutableDictionaryRef  subDict;
    CFRunLoopSourceRef      runLoopSource;
    kern_return_t       kr;
    sig_t           oldHandler;
 
    // Set up a signal handler so we can clean up when we're interrupted from the command line
    // Otherwise we stay in our run loop forever.
    oldHandler = signal(SIGINT, SignalHandler);
    if (oldHandler == SIG_ERR)
        printf("Could not establish new signal handler");
 
    // first create a master_port for my task
    kr = IOMasterPort(MACH_PORT_NULL, &masterPort);
    if (kr || !masterPort)
    {
        printf("ERR: Couldn't create a master IOKit Port(%08x)\n", kr);
        return -1;
    }
 
    // Create the dictionaries
    matchingDict = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL );
    subDict      = CFDictionaryCreateMutable ( kCFAllocatorDefault, 0, NULL, NULL );
 
    // Create a dictionary with the "SCSITaskDeviceCategory" key = "SCSITaskAuthoringDevice"
    CFDictionarySetValue (  subDict,
                            CFSTR ( kIOPropertySCSITaskDeviceCategory ),
                            CFSTR ( kIOPropertySCSITaskAuthoringDevice ) );
 
    // Add the dictionary to the main dictionary with the key "IOPropertyMatch" to
    // narrow the search to the above dictionary.
    CFDictionarySetValue (  matchingDict,
                            CFSTR ( kIOPropertyMatchKey ),
                            subDict );
 
    // Create a notification port and add its run loop event source to our run loop
    // This is how async notifications get set up.
    gNotifyPort = IONotificationPortCreate(masterPort);
    runLoopSource = IONotificationPortGetRunLoopSource(gNotifyPort);
 
    CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);
 
    // Retain a reference since we arm both the appearance and disappearance notifications
    // and the call to IOServiceAddMatchingNotification() consumes a reference each time.
    matchingDict = ( CFMutableDictionaryRef ) CFRetain ( matchingDict );
 
    // Now set up two notifications, one to be called when a raw device is first matched by I/O Kit, and the other to be
    // called when the device is terminated.
    kr = IOServiceAddMatchingNotification(  gNotifyPort,
                                            kIOFirstMatchNotification,
                                            matchingDict,
                                            DeviceAppeared,
                                            NULL,
                                            &gAppearedIter );
 
    DeviceAppeared(NULL, gAppearedIter);    // Iterate once to get already-present devices and
                                                // arm the notification
 
    kr = IOServiceAddMatchingNotification(  gNotifyPort,
                                            kIOTerminatedNotification,
                                            matchingDict,
                                            DeviceDisappeared,
                                            NULL,
                                            &gDisappearedIter );
 
    DeviceDisappeared(NULL, gDisappearedIter);  // Iterate once to arm the notification
 
    // Now done with the master_port
    mach_port_deallocate(mach_task_self(), masterPort);
    masterPort = 0;
 
    // Start the run loop. Now we'll receive notifications.
    CFRunLoopRun();
 
    // We should never get here
    return 0;
}

При нажатии Control-C, чтобы остановить цикл выполнения и выйти из программы, некоторые объекты могут все еще использоваться. SignalHandler функция, показанная в Перечислении 2-8, выпускает эти объекты и выходы.

Перечисление 2-8  , Обрабатывающее прерывания командной строки

void SignalHandler(int sigraised)
{
    printf("\nInterrupted\n");
 
    // Clean up here
    IONotificationPortDestroy(gNotifyPort);
 
    if (gAppearedIter)
    {
        IOObjectRelease(gAppearedIter);
        gAppearedIter = 0;
    }
 
    if (gDisappearedIter)
    {
        IOObjectRelease(gDisappearedIter);
        gDisappearedIter = 0;
    }
 
    exit(0);
}

Теперь, когда Вы получили итератор для ряда согласующих устройств, можно использовать его для получения доступа к каждому устройству. Функция DeviceAppeared (показанный в Перечислении 2-9), использует функцию Набора I/O IOIteratorNext для доступа к каждому устройству возражают, и затем вызывает функцию TestDevice (показанный в Перечислении 2-11), чтобы создать интерфейсы устройства для устройства и протестировать его.

Перечисление 2-9  , Получающее доступ к набору согласующих устройств

void DeviceAppeared(void *refCon, io_iterator_t iterator)
{
    kern_return_t   kr;
    io_service_t    obj;
 
    while (obj = IOIteratorNext(iterator))
    {
        printf("Device appeared.\n");
 
        TestDevice(obj);
 
        kr = IOObjectRelease(obj);
    }
}

Функция DeviceDisappeared (показанный в Перечислении 2-10), просто использует итератор, полученный из main функция (показанный в Перечислении 2-7) для выпуска каждого объекта устройства. Это также имеет эффект вооружения уведомления завершения устройства, таким образом, это уведомит программу будущих удалений устройства.

Перечисление 2-10  , Выпускающее объекты устройства

 
void DeviceDisappeared(void *refCon, io_iterator_t iterator)
{
    kern_return_t   kr;
    io_service_t    obj;
 
    while (obj = IOIteratorNext(iterator))
    {
        printf("Device disappeared.\n");
        kr = IOObjectRelease(obj);
    }
}

Тестирование устройства

Функция TestDevice (показанный в Перечислении 2-11), создает промежуточный сменный интерфейс для устройства и затем запрашивает этот интерфейс для получения MMCDeviceInterface. Пример кода не использует MMCDeviceInterface для запросов устройства несмотря на то, что функции такой как GetPerformance и GetTrayState доступны.

Функция TestDevice использование функции Набора I/O для создания MMCDeviceInterface и затем вызывает функцию MMCDeviceInterface GetSCSITaskDeviceInterface создать SCSITaskDeviceInterface. Как только это получает SCSITaskDeviceInterface, это вызывает свою функцию ObtainExclusiveAccess запросить эксклюзивный доступ к устройству. После получения эксклюзивного доступа, TestDevice вызывает следующие функции, чтобы создать объекты SCSITask и отправить их в устройство.

  • Inquiry, (показанный в Перечислении 2-12), создает объект SCSITask отправить команду INQUIRY в устройство и распечатывает данные запроса, которые это получает.

  • TestUnitReady, (показанный в Перечислении 2-13), создает объект SCSITask отправить команду TEST_UNIT_READY в устройство и вызовы PrintSenseString (показанный в Перечислении 2-14) для печати смысла представляют возвраты устройства в виде строки.

TestDevice тогда оставляет эксклюзивный доступ к устройству и выпускает SCSITaskDeviceInterface и MMCDeviceInterface.

Перечисление 2-11  , Получающее MMCDeviceInterface и получающее эксклюзивный доступ

void TestDevice(io_service_t service)
{
    SInt32                          score;
    HRESULT                         herr;
    kern_return_t                   err;
    IOCFPlugInInterface             **plugInInterface = NULL;
    MMCDeviceInterface              **mmcInterface = NULL;
    SCSITaskDeviceInterface         **interface = NULL;
 
    // Create the IOCFPlugIn interface so we can query it.
    err = IOCreatePlugInInterfaceForService (   service,
                                                kIOMMCDeviceUserClientTypeID,
                                                kIOCFPlugInInterfaceID,
                                                &plugInInterface,
                                                &score );
 
    if ( err != noErr )
    {
        printf("IOCreatePlugInInterfaceForService returned %d\n", err);
        return;
    }
 
    // Query the interface for the MMCDeviceInterface.
    herr = ( *plugInInterface )->QueryInterface ( plugInInterface,
                                        CFUUIDGetUUIDBytes ( kIOMMCDeviceInterfaceID ),
                                        ( LPVOID *) &mmcInterface );
    if ( herr != S_OK )
    {
        printf("QueryInterface returned %ld\n", herr);
        return;
    }
 
    interface = ( *mmcInterface )->GetSCSITaskDeviceInterface ( mmcInterface );
    if ( interface == NULL )
    {
        printf("GetSCSITaskDeviceInterface returned NULL\n");
        return;
    }
 
    err = ( *interface )->ObtainExclusiveAccess ( interface );
    if ( err != noErr )
    {
        printf("ObtainExclusiveAccess returned %d\n", err);
        return;
    }
 
    Inquiry(interface);
    TestUnitReady(interface);
 
    ( *interface )->ReleaseExclusiveAccess ( interface );
 
    // Release the SCSITaskDeviceInterface.
    ( *interface )->Release ( interface );
    ( *mmcInterface )->Release ( mmcInterface );
 
    IODestroyPlugInInterface ( plugInInterface );
}
 

Теперь, когда Вы получили эксклюзивный доступ, можно создать объекты SCSITask и отправить их в устройство. Функция Inquiry (показанный в Перечислении 2-12), использует функцию SCSITaskDeviceInterface CreateSCSITask создать объект SCSITaskInterface. Это тогда использует функции SCSITaskInterface для подготовки объекта отправить команду INQUIRY. Если команда выполняется успешно, Inquiry распечатывает результаты. Наконец, Inquiry выпускает объект SCSITaskInterface и возвраты.

Перечисление 2-12  , Отправляющее команду INQUIRY в устройство

void Inquiry(SCSITaskDeviceInterface **interface)
{
 
    SCSICmd_INQUIRY_StandardData    inqBuffer;
    SCSITaskStatus                  taskStatus;
    SCSI_Sense_Data                 senseData;
    SCSICommandDescriptorBlock      cdb;
    SCSITaskInterface **            task  = NULL;
    UInt8 *                         bufPtr = ( UInt8 * ) &inqBuffer;
    char                            vendorID[9];
    char                            productID[17];
    char                            firmwareRevLevel[5];
    IOReturn                        err   = 0;
    UInt32                          index = 0;
    IOVirtualRange *                range = NULL;
    UInt64                          transferCount = 0;
    UInt32                          transferCountHi = 0;
    UInt32                          transferCountLo = 0;
 
    // Create a task now that we have exclusive access
    task = ( *interface )->CreateSCSITask ( interface );
    if ( task != NULL )
    {
 
        // Zero the buffer.
        memset ( bufPtr, 0, sizeof ( SCSICmd_INQUIRY_StandardData ) );
 
        // Allocate a virtual range for the buffer. If we had more than 1 scatter-gather entry,
        // we would allocate more than 1 IOVirtualRange.
        range = ( IOVirtualRange * ) malloc ( sizeof ( IOVirtualRange ) );
        if ( range == NULL )
        {
            printf("*********** ERROR Malloc'ing IOVirtualRange ***********\n\n");
        }
 
        // zero the senseData and CDB
        memset ( &senseData, 0, sizeof ( senseData ) );
        memset ( cdb, 0, sizeof ( cdb ) );
 
        // Set up the range. The address is just the buffer's address. The length is our request size.
        range->address  = ( IOVirtualAddress ) bufPtr;
        range->length   = sizeof ( SCSICmd_INQUIRY_StandardData );
 
        // We're going to execute an INQUIRY to the device as a
        // test of exclusive commands.
        cdb[0] = 0x12 /* inquiry */;
        cdb[4] = sizeof ( SCSICmd_INQUIRY_StandardData );
 
        // Set the actual CDB in the task
        err = ( *task )->SetCommandDescriptorBlock ( task, cdb, kSCSICDBSize_6Byte );
        if ( err != kIOReturnSuccess )
        {
            printf("*********** ERROR Setting CDB ***********\n\n");
        }
 
        // Set the scatter-gather entry in the task
        err = ( *task )->SetScatterGatherEntries ( task, range, 1, sizeof ( SCSICmd_INQUIRY_StandardData ),
                                                    kSCSIDataTransfer_FromTargetToInitiator );
        if ( err != kIOReturnSuccess )
        {
            printf("*********** ERROR Setting SG Entries ***********\n\n");
        }
 
        // Set the timeout in the task
        err = ( *task )->SetTimeoutDuration ( task, 10000 );
        if ( err != kIOReturnSuccess )
        {
            printf("*********** ERROR Setting Timeout ***********\n\n");
        }
 
        printf("*********** Requesting Inquiry Data ***********\n\n");
 
        // Send it!
        err = ( *task )->ExecuteTaskSync ( task, &senseData, &taskStatus, &transferCount );
        if ( err != kIOReturnSuccess )
        {
            printf("*********** ERROR Executing Task ***********\n\n");
        }
 
        // Get the transfer counts
        transferCountHi = ( ( transferCount >> 32 ) & 0xFFFFFFFF );
        transferCountLo = ( transferCount & 0xFFFFFFFF );
 
        printf("taskStatus = %d, transferCountHi = 0x%08lx, transferCountLo = 0x%08lx\n", taskStatus, transferCountHi, transferCountLo);
 
        // Task status is not GOOD, print any sense string if they apply.
        if ( taskStatus == kSCSITaskStatus_GOOD )
        {
 
            printf("*********** INQUIRY DATA ***********\n\n");
 
            printf("Peripheral Device Type = %d\n", bufPtr[0] & 0x1F);
            printf("Removable Media Bit = %d\n", ( bufPtr[1] & 0x80 ) == 0x80);
 
            for ( index = 8; index < 16; index++ )
            {
                if ( bufPtr[index] == 0 )
                    break;
                vendorID[index-8] = bufPtr[index];
            }
            vendorID[index-8] = 0;
 
            for ( index = 16; index < 32; index++ )
            {
                if ( bufPtr[index] == 0 )
                    break;
                productID[index-16] = bufPtr[index];
            }
            productID[index-16] = 0;
 
            for ( index = 32; index < 36; index++ )
            {
                if ( bufPtr[index] == 0 )
                    break;
                firmwareRevLevel[index-32] = bufPtr[index];
            }
            firmwareRevLevel[index-32] = 0;
 
            printf("Vendor Identification = %s\n", ( char * ) vendorID);
            printf("Product Identification = %s\n", ( char * ) productID);
            printf("Product Revision Level = %s\n", ( char * ) firmwareRevLevel);
 
            printf("\n");
 
        }
 
        // Be a good citizen and cleanup
        free ( range );
 
        // Release the task interface
        ( *task )->Release ( task );
 
    }
 
}

Функция TestUnitReady (показанный в Перечислении 2-13), отправляет команду TEST_UNIT_READY в устройство. Во-первых, это создает объект SCSITaskInterface, и затем это создает CDB, содержащий команду. После отправки команды, TestUnitReady проверяет состояние задачи и, в зависимости от значения состояния, вызывает функцию PrintSenseString (показанный в Перечислении 2-14) для печати данных чувственного опыта. Перед возвратом, TestUnitReady выпускает объект SCSITaskInterface.

Перечисление 2-13  , Отправляющее команду TEST_UNIT_READY в устройство

void TestUnitReady(SCSITaskDeviceInterface **interface)
{
 
    SCSITaskStatus                  taskStatus;
    SCSI_Sense_Data                 senseData;
    SCSICommandDescriptorBlock      cdb;
    SCSITaskInterface **            task  = NULL;
    IOReturn                        err   = 0;
    UInt64                          transferCount = 0;
 
    // Create a task now that we have exclusive access
    task = ( *interface )->CreateSCSITask ( interface );
    if ( task != NULL )
    {
 
        // zero the senseData and CDB
        memset ( &senseData, 0, sizeof ( senseData ) );
        memset ( cdb, 0, sizeof ( cdb ) );
 
        // The TEST_UNIT_READY code consists of all zeroes so it is
        // not necessary to set any additional values in the CDB
        // Set the actual CDB in the task
        err = ( *task )->SetCommandDescriptorBlock ( task, cdb, kSCSICDBSize_6Byte );
        if ( err != kIOReturnSuccess )
        {
            printf("*********** ERROR Setting CDB ***********\n\n");
        }
 
        // Set the timeout in the task
        err = ( *task )->SetTimeoutDuration ( task, 5000 );
        if ( err != kIOReturnSuccess )
        {
            printf("*********** ERROR Setting Timeout ***********\n\n");
        }
 
        // Send it!
        err = ( *task )->ExecuteTaskSync ( task, &senseData, &taskStatus, &transferCount );
        if ( err != kIOReturnSuccess )
        {
            printf("*********** ERROR Executing Task ***********\n\n");
        }
 
        printf("taskStatus = %d\n", taskStatus);
 
        // Task status is not GOOD, print any sense string if they apply.
        if ( taskStatus == kSCSITaskStatus_GOOD )
        {
            printf("Good Status\n");
        }
        else if ( taskStatus == kSCSITaskStatus_CHECK_CONDITION )
        {
            // Something happened. Print the sense string
            PrintSenseString(&senseData);
        }
        else
        {
            printf("taskStatus = 0x%08x\n", taskStatus);
        }
 
        printf("\n");
 
        // Release the task interface
        ( *task )->Release ( task );
    }
}

Функция PrintSenseString (показанный в Перечислении 2-14), распечатывает данные чувственного опыта возвраты устройства после выполнения представленной команды TEST_UNIT_READY TestUnitReady (показанный в Перечислении 2-13).

Перечисление 2-14  , Распечатывающее данные чувственного опыта

void
PrintSenseString ( SCSI_Sense_Data * sense )
{
 
    char    str[256];
    UInt8   key, ASC, ASCQ;
 
    key     = sense->SENSE_KEY & 0x0F;
    ASC     = sense->ADDITIONAL_SENSE_CODE;
    ASCQ    = sense->ADDITIONAL_SENSE_CODE_QUALIFIER;
 
    // Print the sense string information.
    sprintf ( str, "Key: $%02lx, ASC: $%02lx, ASCQ: $%02lx  ", key, ASC, ASCQ );
 
}