Разделение на подклассы драйверов логической единицы
Модель архитектуры SCSI определяет несколько совместно используемых спецификаций набора команд, каждый связанный с типом периферийного устройства. Этот документ спецификаций, как команды SCSI обрабатываются встроенным микропрограммным обеспечением устройства. Если Ваше устройство не соответствует совместно используемой спецификации набора команд для ее типа периферийного устройства в некотором роде, или потому что это обрабатывает команды по-другому или потому что ИТ-услуги дополнительные команды, необходимо разделить надлежащий драйвер логической единицы Apple на подклассы для оказания поддержки устройство требует.
В этой главе описываются, как разделить предоставленный Apple драйвер логической единицы на подклассы для решения проблем реализации набора команд SCSI. Пример кода в этой главе универсален и подчеркивает форму, которую Ваш драйвер должен принять, а не код, требуемый реализовать определенную команду. Поскольку демонстрационные драйверы универсальны, они не присоединят к определенному устройству. Для тестирования их с устройством замените универсальные значения для параметров, таких как поставщик или идентификация продукта со значениями, идентифицирующими устройство. Для получения дополнительной информации о том, как разработать расширения ядра в целом и драйверы Набора I/O в частности посмотрите, что Расширение ядра Программирует Темы и Руководство по проектированию Драйвера устройства IOKit.
Эта глава также содержит код, показывающий, как Ваш драйвер может использовать a SCSITask
объект отправить команду в устройство и как использовать разработчика команды семьи Model архитектуры SCSI функции для создания пользовательского CDB.
Установка Вашего проекта
В этом разделе описывается создать Ваш проект драйвера и отредактировать Ваш водительский информационный список свойств (Info.plist
файл). Демонстрационный драйвер в этой главе является драйвером логической единицы для универсального устройства CDROM, таким образом, это - подкласс предоставленного Apple IOSCSIPeripheralDeviceType05
драйвер.
Демонстрационное использование проекта MyLogicalUnitDriver
для имени драйвера и универсальных значений такой как MySoftwareCompany
для имени разработчика. Необходимо заменить эти имена и значения с собственной информацией для тестирования этого кода с устройством.
Создайте новый проект
Откройте приложение XCode и создайте новый названный проект драйвера Набора I/O MyLogicalUnitDriver
. Укажите каталог для нового проекта или примите значение по умолчанию.
При создании нового проекта драйвера Набора I/O XCode предоставляет несколько файлов, включая два пустых исходных файла —MyLogicalUnitDriver.h
и MyLogicalUnitDriver.cpp
. Перед добавлением любого кода к этим файлам, однако, необходимо отредактировать водительский информационный список свойств.
Отредактируйте свой водительский список свойств
Каждый драйвер имеет Info.plist
файл, содержащий информацию о драйвере и в чем это нуждается, включая его лица. Как описано в Лицах Драйвера и Соответствии Процесса, водительская индивидуальность содержит соответствующую информацию использование Набора I/O для определения надлежащего драйвера для устройства. Для проверки драйвер загружается для устройства, Вы добавляете несколько свойств к его словарю индивидуальности, идентифицирующих устройство или тип устройства, которое это поддерживает.
В XCode, водительском Info.plist
файл перечислен в представлении Groups & Files в проекте. Можно отредактировать файл списка свойств как простой текст XML в редакторе XCode окно, или можно выбрать различное приложение (такое как Редактор Списка свойств) для использования. Для получения дополнительной информации о том, как выбрать другого редактора, посмотрите Привет Набор I/O: Создание Драйвера С XCode.
IOKitPersonalities
словарь в водительском Info.plist
файл может содержать словари разносторонне развитой личности, один для каждого устройства или типа устройства Ваши поддержки драйвера. Демонстрационный драйвер в этой главе реализует только один словарь индивидуальности, но можно создать дополнительные словари, если драйвер может поддерживать больше чем один тип устройства или тип устройства.
Пример кода использует следующие шесть ключей свойства:
CFBundleIdentifier
IOClass
IOProviderClass
Peripheral Device Type
Vendor Identification
Product Identification
При разработке драйвера для определенной версии устройства можно добавить идентификационный ключ версии продукта к индивидуальности для еще более определенного соответствия.
Используя Вашу выбранную среду редактирования, создайте новый дочерний элемент IOKitPersonalities
словарь. Сделайте имя этого нового дочернего элемента MyLogicalUnitDriver
и набор его класс Словаря.
Создайте шесть новых дочерних элементов MyLogicalUnitDriver
словарь, один для каждого из этих шести свойств Вы будете добавлять. Таблица 5-1 показывает свойства, вместе с их классами и значениями. Для тестирования примера кода с устройством замените значения такой как MyProductIdentification
с фактическими значениями для Вашего устройства.
Свойство | Класс | Значение |
---|---|---|
| Строка |
|
| Строка |
|
| Строка |
|
| Число |
|
| Строка |
|
| Строка |
|
Драйвер объявляет свои зависимости от других загружаемых расширений ядра и компонентов в ядре в OSBundleLibraries
словарь. Каждая зависимость имеет строковое значение, объявляющее самую раннюю версию зависимости, с которой драйвер совместим.
Демонстрационный драйвер зависит от двух загружаемых расширений от IOSCSIArchitectureModel
семья. Добавить эти зависимости к OSBundleLibraries
словарь, Вы создаете новый дочерний элемент для каждой зависимости. Таблица 5-2 показывает зависимости, которые Вы добавляете для демонстрационного драйвера.
Свойство | Класс | Значение |
---|---|---|
| Строка |
|
| Строка |
|
Поскольку драйвер дисковода для компакт-дисков должен быть в состоянии смонтировать корень на локальном томе, Вы добавляете OSBundleRequired
свойство к верхнему уровню Info.plist
файл. Другими словами, новое OSBundleRequired
свойство является одноуровневым элементом IOKitPersonalities
и OSBundleLibraries
словари, не дочерний элемент. Отредактируйте новый элемент для соответствия следующего:
OSBundleRequired String Local-Root |
Создание Вашего драйвера
В этом разделе описываются некоторые элементы, которые должны быть включены в Ваши водительские исходные файлы. Для демонстрации процесса разделения на подклассы демонстрационный драйвер просто переопределяет GetConfiguration
метод и печать сообщение. Необходимо заменить эту тривиальную функцию собственным кодом, поддерживающим определенные реализации команды устройства.
В XCode водительские исходные файлы перечислены в области Groups & Files, показанной discosure треугольником рядом с MyLogicalUnitDriver
проект и треугольник раскрытия рядом с папкой Source.
Отредактируйте заголовочный файл
Заголовочный файл обеспечивает доступ к внешним объявлениям и поддерживающий определения типа, необходимые функциям и объектам в файле C++. Заголовок для демонстрационного драйвера прост, потому что это включает только одно объявление метода. Отредактируйте MyLogicalUnitDriver.h
файл для соответствия кода в Перечислении 5-1.
Перечисление 5-1 заголовочный файл MyLogicalUnitDriver
#ifndef _MyLogicalUnitDriver_H_ |
#define _MyLogicalUnitDriver_H_ |
// Because the sample driver is a subclass of the Apple-provided |
// peripheral device type 05 driver, it must include that driver's |
// header file. |
#include <IOKit/scsi-commands/IOSCSIPeripheralDeviceType05.h> |
// Here, the sample driver declares its inheritance and the method |
// it overrides. |
class com_MySoftwareCompany_driver_MyLogicalUnitDriver : public |
IOSCSIPeripheralDeviceType05 |
{ |
OSDeclareDefaultStructors ( |
com_MySoftwareCompany_driver_MyLogicalUnitDriver ) |
protected: |
virtual IOReturn GetConfiguration ( void ); |
}; |
#endif /* _MyLogicalUnitDriver_H_ */ |
Отредактируйте файл C++
Файл C++ обеспечивает код для переопределения выбранных методов. Демонстрационный водительский файл C++ содержит все элементы, требуемые для разделенного на подклассы драйвера даже при том, что он не выполняет ничто более существенное, чем сообщение, отправленное в системный файл журнала, /var/log/system.log
.
Отредактируйте MyLogicalUnitDriver.cpp
файл для соответствия кода в Перечислении 5-2.
Перечисление 5-2 файл C++ MyLogicalUnitDriver
// Include the header file you created |
#include "MyLogicalUnitDriver.h" |
// This definition allows you to use the more convenient "super" in |
// place of "IOSCSIPeripheralDeviceType05", where appropriate. |
#define super IOSCSIPeripheralDeviceType05 |
// This macro must appear before you define any of your class's methods. |
// Note that you must use the literal name of the superclass here, not |
// "super" as defined above. |
OSDefineMetaClassAndStructors ( |
com_MySoftwareCompany_driver_MyLogicalUnitDriver, |
IOSCSIPeripheralDeviceType05 ); |
// Define the method to override. |
IOReturn |
com_MySoftwareCompany_driver_MyLogicalUnitDriver::GetConfiguration ( void ) |
{ |
IOLog( "MyLogicalUnitDriver overriding GetConfiguration\n" ); |
// You can add code that accesses your device here. |
// Call super's GetConfiguration method before returning. |
return super::GetConfiguration(); |
} |
Тестирование Вашего драйвера
Этот раздел представляет некоторое уведомление относительно тестирования Вашего драйвера. Вы не можете использовать kextload
загрузить и протестировать Ваш драйвер «вручную», потому что существуют универсальные драйверы, которые будут всегда загружаться в его месте во время начальной загрузки. Поэтому необходимо удостовериться, что у Вас есть многократные загрузочные диски или разделы, таким образом, можно демонтировать драйвер, если он ведет себя плохо, и перезагрузите диск или раздел.
Поскольку OSBundleRequired
свойство в водительской выборке Info.plist
файл установлен в Local-Root
, футболист BootX автоматически загрузит его, когда система будет перезапущена (для получения дополнительной информации об этом процессе, посмотрите Загружающиеся Расширения ядра во Время начальной загрузки).
Для справки с отладкой можно открыть окно в Терминальном приложении (расположенный в /Applications/Utilities/Terminal
) и введите следующую строку для просмотра системного журнала:
tail -f /var/log/system.log |
Создание и отправка команд SCSI
Как описано в Транспортном Уровне Драйвера, подкласс драйвера логической единицы создает a SCSITask
объект содержать блок дескриптора команды (или CDB) и различные индикаторы состояния имел отношение к выполнению команды SCSI. Создать команду для помещения в a SCSITask
объект, можно использовать встроенные функции создания команды, или можно создать пользовательский CDB. Когда необходимо отправить стандартные команды SCSI, такой как, встроенные функции создания команды являются надлежащими INQUIRY
, TEST_UNIT_READY
, и REPORT_SENSE
. Когда необходимо отправить специфичную для поставщика команду SCSI, Вы используете SetCommandDescriptorBlock
функционируйте для создания соответственно размерного CDB. Обратите внимание на то, что SetCommandDescriptorBlock
и встроенные функции создания команды определяются в IOSCSIPrimaryCommandsDevice.h
. Пример кода в этом разделе показывает оба способа создать и отправить команды SCSI.
Код в этом разделе показывает простой драйвер логической единицы для блочного устройства команд, в частности, подкласса IOSCSIPeripheralDeviceType00
. Это показывает, как использовать одну из встроенных функций создания команды, и это демонстрирует, как установить Вашу собственную функцию создания команды для создания пользовательской команды. Это также показывает, как проверить ответ команды и состояние SCSITask
объект. Перечисление 5-3 показывает заголовочный файл для демонстрационного драйвера.
Заголовочный файл перечисления 5-3 для драйвера, отправляющего стандартные и пользовательские команды SCSI
#ifndef _SampleINQUIRYDriver_H_ |
#define _SampleINQUIRYDriver_H_ |
#include <IOKit/scsi/IOSCSIPeripheralDeviceType00.h> |
class com_MySoftwareCompany_driver_SampleINQUIRYDriver : public IOSCSIPeripheralDeviceType00 |
{ |
OSDeclareDefaultStructors ( com_MySoftwareCompany_driver_SampleINQUIRYDriver ) |
protected: |
bool InitializeDeviceSupport ( void ); |
void SendBuiltInINQUIRY ( void ); |
void SendCreatedINQUIRY ( void ); |
bool BuildINQUIRY ( SCSITaskIdentifier request, |
IOBufferMemoryDescriptor * buffer, |
SCSICmdField1Bit CMDDT, |
SCSICmdField1Bit EVPD, |
SCSICmdField1Byte PAGE_OR_OPERATION_CODE, |
SCSICmdField1Byte ALLOCATION_LENGTH, |
SCSICmdField1Byte CONTROL ); |
}; |
#endif /* _SampleINQUIRYDriver_H_ */ |
Перечисление 5-4 показывает реализацию демонстрационного драйвера. Несмотря на то, что существует некоторая показанная обработка ошибок, необходимо добавить более обширный код обработки ошибок, когда Вы используете эту выборку в качестве основания для фактического драйвера.
Реализация перечисления 5-4 драйвера, отправляющего стандартные и пользовательские команды SCSI
#include <IOKit/IOBufferMemoryDescriptor.h> |
#include <IOKit/scsi/SCSICmds_INQUIRY_Definitions.h> |
#include <IOKit/scsi/SCSICommandOperationCodes.h> |
#include <IOKit/scsi/SCSITask.h> |
#include "SampleINQUIRYDriver.h" |
#define super IOSCSIPeripheralDeviceType00 |
OSDefineMetaClassAndStructors ( com_MySoftwareCompany_driver_SampleINQUIRYDriver, IOSCSIPeripheralDeviceType00 ); |
bool |
com_MySoftwareCompany_driver_SampleINQUIRYDriver::InitializeDeviceSupport ( void ) |
{ |
bool result = false; |
result = super::InitializeDeviceSupport ( ); |
if ( result == true ) { |
SendBuiltInINQUIRY ( ); |
SendCreatedINQUIRY ( ); |
} |
return result; |
} |
void |
com_MySoftwareCompany_driver_SampleINQUIRYDriver::SendBuiltInINQUIRY ( void ) |
{ |
// The Service Response represents the execution status of a service request. |
SCSIServiceResponse serviceResponse = kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE; |
IOBufferMemoryDescriptor * buffer = NULL; |
SCSITaskIdentifier request = NULL; |
UInt8 * ptr = NULL; |
// Get a new IOBufferMemoryDescriptor object with a buffer large enough |
// to hold the SCSICmd_INQUIRY_StandardData structure (defined |
// in SCSICmds_INQUIRY_Definitions.h). |
buffer = IOBufferMemoryDescriptor::withCapacity ( sizeof ( SCSICmd_INQUIRY_StandardData ), kIODirectionIn, false ); |
require ( ( buffer != NULL ), ErrorExit ); |
// Get the address of the beginning of the buffer and zero-fill the buffer. |
ptr = ( UInt8 * ) buffer->getBytesNoCopy ( ); |
bzero ( ptr, buffer->getLength ( ) ); |
// Create a new SCSITask object; if unsuccessful, release |
request = GetSCSITask ( ); |
require ( ( request != NULL ), ReleaseBuffer ); |
// Prepare the buffer for an I/O transaction. This call must be |
// balanced by a call to the complete method (shown just before |
// ReleaseTask). |
require ( ( buffer->prepare ( ) == kIOReturnSuccess ), ReleaseTask ); |
// Use the INQUIRY method to assemble the command. Then use the |
// SendCommand method to synchronously issue the request. |
if ( INQUIRY ( request, |
buffer, |
0, |
0, |
0x00, |
buffer->getLength ( ), |
0 ) == true ) |
{ |
serviceResponse = SendCommand ( request, kTenSecondTimeoutInMS ); |
} |
// Check the SendCommand method's return value and the status of the SCSITask object. |
if ( ( serviceResponse == kSCSIServiceResponse_TASK_COMPLETE ) && |
GetTaskStatus ( request ) == kSCSITaskStatus_GOOD ) |
{ |
IOLog ( "INQUIRY succeeded\n" ); |
} |
else |
{ |
IOLog ( "INQUIRY failed\n" ); |
} |
// Complete the processing of this buffer after the I/O transaction |
// (this call balances the earlier call to prepare). |
buffer->complete ( ); |
// Clean up before exiting. |
ReleaseTask: |
require_quiet ( ( request != NULL ), ReleaseBuffer ); |
ReleaseSCSITask ( request ); |
request = NULL; |
ReleaseBuffer: |
require_quiet ( ( buffer != NULL ), ErrorExit ); |
buffer->release ( ); |
buffer = NULL; |
ErrorExit: |
return; |
} |
void |
com_MySoftwareCompany_driver_SampleINQUIRYDriver::SendCreatedINQUIRY ( void ) |
{ |
SCSIServiceResponse serviceResponse = kSCSIServiceResponse_SERVICE_DELIVERY_OR_TARGET_FAILURE; |
IOBufferMemoryDescriptor * buffer = NULL; |
SCSITaskIdentifier request = NULL; |
UInt8 * ptr = NULL; |
// Get a new IOBufferMemoryDescriptor object with a buffer large enough |
// to hold the SCSICmd_INQUIRY_StandardData structure (defined in |
// SCSICmds_INQUIRY_Definitions.h). |
buffer = IOBufferMemoryDescriptor::withCapacity ( sizeof ( SCSICmd_INQUIRY_StandardData ), kIODirectionIn, false ); |
// Return immediately if the buffer wasn't created. |
require ( ( buffer != NULL ), ErrorExit ); |
// Get the address of the beginning of the buffer and zero-fill the buffer. |
ptr = ( UInt8 * ) buffer->getBytesNoCopy ( ); |
bzero ( ptr, buffer->getLength ( ) ); |
// Create a new SCSITask object; if unsuccessful, release the buffer and return. |
request = GetSCSITask ( ); |
require ( ( request != NULL ), ReleaseBuffer ); |
// Prepare the buffer for an I/O transaction. This call must be |
// balanced by a call to the complete method (shown just before |
// ReleaseTask). |
require ( ( buffer->prepare ( ) == kIOReturnSuccess ), ReleaseTask ); |
// The BuildINQUIRY function shows how you can design and use a |
// command-building function to create a custom command to send |
// to your device. Although the BuildINQUIRY function builds a standard INQUIRY |
// command from the passed-in values, you do not create a custom function to |
// build a standard command in a real driver. Instead, you use the SCSI |
// Architecture Model family's built-in command-building functions. The |
// BuildINQUIRY function uses INQUIRY as an example merely because |
// it is a well-understood command. |
if ( BuildINQUIRY ( request, |
buffer, |
0x00, // CMDDT (Command support data) |
0x00, // EVPD (Vital product data) |
0x00, // PAGE_OR_OPERATION_CODE |
buffer->getLength ( ), // ALLOCATION_LENGTH |
0x00 ) // CONTROL |
== true) |
{ |
serviceResponse = SendCommand ( request, kTenSecondTimeoutInMS ); |
} |
if ( ( serviceResponse == kSCSIServiceResponse_TASK_COMPLETE ) && |
GetTaskStatus ( request ) == kSCSITaskStatus_GOOD ) |
{ |
IOLog ( "Vendor-created INQUIRY command succeeded\n" ); |
} |
else |
{ |
IOLog ( "Vendor-created INQUIRY command failed\n" ); |
} |
buffer->complete ( ); |
ReleaseTask: |
require_quiet ( ( request != NULL ), ReleaseBuffer ); |
ReleaseSCSITask ( request ); |
request = NULL; |
ReleaseBuffer: |
require_quiet ( ( buffer != NULL ), ErrorExit ); |
buffer->release ( ); |
buffer = NULL; |
ErrorExit: |
return; |
} |
bool |
com_MySoftwareCompany_driver_SampleINQUIRYDriver::BuildINQUIRY ( |
SCSITaskIdentifier request, |
IOBufferMemoryDescriptor * dataBuffer, |
SCSICmdField1Bit CMDDT, |
SCSICmdField1Bit EVPD, |
SCSICmdField1Byte PAGE_OR_OPERATION_CODE, |
SCSICmdField1Byte ALLOCATION_LENGTH, |
SCSICmdField1Byte CONTROL ) |
{ |
bool result = false; |
// Validate the parameters here. |
require ( ( request != NULL ), ErrorExit ); |
require ( ResetForNewTask ( request ), ErrorExit ); |
// The helper functions ensure that the parameters fit within the |
// CDB fields and that the buffer passed in is large enough for |
// the transfer length. |
require ( IsParameterValid ( CMDDT, kSCSICmdFieldMask1Bit ), ErrorExit ); |
require ( IsParameterValid ( EVPD, kSCSICmdFieldMask1Bit ), ErrorExit ); |
require ( IsParameterValid ( PAGE_OR_OPERATION_CODE, kSCSICmdFieldMask1Byte ), ErrorExit ); |
require ( IsParameterValid ( ALLOCATION_LENGTH, kSCSICmdFieldMask1Byte ), ErrorExit ); |
require ( IsParameterValid ( CONTROL, kSCSICmdFieldMask1Byte ), ErrorExit ); |
require ( IsMemoryDescriptorValid ( dataBuffer, ALLOCATION_LENGTH ), ErrorExit ); |
// Check the validity of the PAGE_OR_OPERATION_CODE parameter, when using both the CMDDT and EVPD parameters. |
if ( PAGE_OR_OPERATION_CODE != 0 ) |
{ |
if ( ( ( CMDDT == 1 ) && ( EVPD == 1 ) ) || ( ( CMDDT == 0 ) && ( EVPD == 0 ) ) ) |
{ |
goto ErrorExit; |
} |
} |
// This is a 6-byte command: fill out the CDB appropriately |
SetCommandDescriptorBlock ( request, |
kSCSICmd_INQUIRY, |
( CMDDT << 1 ) | EVPD, |
PAGE_OR_OPERATION_CODE, |
0x00, |
ALLOCATION_LENGTH, |
CONTROL ); |
SetDataTransferDirection ( request, kSCSIDataTransfer_FromTargetToInitiator ); |
SetTimeoutDuration ( request, 0 ); |
SetDataBuffer ( request, dataBuffer ); |
SetRequestedDataTransferCount ( request, ALLOCATION_LENGTH ); |
result = true; |
ErrorExit: |
return result; |
} |