Разделение на подклассы драйверов логической единицы

Модель архитектуры 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 с фактическими значениями для Вашего устройства.

  Свойства Table 5-1 Personality для MyLogicalUnitDriver

Свойство

Класс

Значение

CFBundleIdentifier

Строка

com.MySoftwareCompany.driver.MyLogicalUnitDriver

IOClass

Строка

com_MySoftwareCompany_driver_MyLogicalUnitDriver

IOProviderClass

Строка

IOSCSIPeripheralDeviceNub

Peripheral Device Type

Число

5

Vendor Identification

Строка

MyProductIdentification

Product Identification

Строка

MyVendorIdentification

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

Демонстрационный драйвер зависит от двух загружаемых расширений от IOSCSIArchitectureModel семья. Добавить эти зависимости к OSBundleLibraries словарь, Вы создаете новый дочерний элемент для каждой зависимости. Таблица 5-2 показывает зависимости, которые Вы добавляете для демонстрационного драйвера.

Табличные 5-2  зависимости для MyLogicalUnitDriver

Свойство

Класс

Значение

com.apple.iokit.IOSCSIArchitectureModelFamily

Строка

1.0.0

com.apple.iokit.IOSCSIMultimediaCommandsDevice

Строка

1.0.0

Поскольку драйвер дисковода для компакт-дисков должен быть в состоянии смонтировать корень на локальном томе, Вы добавляете 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;
}