Выбор конфигурации для USB-устройства

Чтобы выбрать конфигурацию для USB-устройства, драйвер клиента для устройства должен выбрать по крайней мере одну из поддерживаемых конфигураций и указать альтернативные параметры каждого интерфейса для использования. Драйвер клиента упаковает эти варианты в запрос на выборку конфигурации и отправляет запрос в предоставленный Корпорацией Майкрософт стек USB-драйверов, в частности драйвер шины USB (PDO-концентратор USB). Драйвер шины USB выбирает каждый интерфейс в указанной конфигурации и настраивает канал связи для каждой конечной точки в интерфейсе. После выполнения запроса драйвер клиента получает дескриптор для выбранной конфигурации и дескриптор конвейера для конечных точек, определенных в активном альтернативном параметре для каждого интерфейса. Затем драйвер клиента может использовать полученные дескрипторы для изменения параметров конфигурации и отправки запросов ввода-вывода на чтение и запись в определенную конечную точку.

Драйвер клиента отправляет запрос на выборку конфигурации в блоке запросов USB (URB) типа URB_FUNCTION_SELECT_CONFIGURATION. Процедура в этом разделе описывает, как использовать подпрограмму USBD_SelectConfigUrbAllocateAndBuild для создания этой urb. Подпрограмма выделяет память для URB, форматирует URB для запроса на выборку конфигурации и возвращает адрес URB драйверу клиента.

Кроме того, можно выделить структуру URB , а затем отформатировать urb вручную или путем вызова макроса UsbBuildSelectConfigurationRequest .

Предварительные требования

Шаг 1. Создание массива структур USBD_INTERFACE_LIST_ENTRY

  1. Получение количества интерфейсов в конфигурации. Эти сведения содержатся в элементе bNumInterfacesструктуры USB_CONFIGURATION_DESCRIPTOR .

  2. Создание массива USBD_INTERFACE_LIST_ENTRY структур. Число элементов в массиве должно быть на один больше числа интерфейсов. Инициализируйте массив, вызвав RtlZeroMemory.

    Драйвер клиента задает альтернативные параметры в каждом интерфейсе для включения в массиве USBD_INTERFACE_LIST_ENTRY структур.

    • Элемент InterfaceDescriptor каждой структуры указывает на дескриптор интерфейса, содержащий альтернативный параметр.
    • Элемент Интерфейса каждой структуры указывает на структуру USBD_INTERFACE_INFORMATION , которая содержит сведения о канале в элементе Pipe . Каналы хранят сведения о каждой конечной точке, определенной в альтернативном параметре.
  3. Получите дескриптор интерфейса для каждого интерфейса (или его альтернативного параметра) в конфигурации. Эти дескрипторы интерфейса можно получить, вызвав USBD_ParseConfigurationDescriptorEx.

    Сведения о драйверах-функциях для составного USB-устройства:

    Если USB-устройство является составным, конфигурация выбирается предоставленным Корпорацией Майкрософт универсальным родительским драйвером USB (Usbccgp.sys). Драйвер клиента, который является одним из драйверов функций составного устройства, не может изменить конфигурацию, но драйвер по-прежнему может отправить запрос на выборку конфигурации через Usbccgp.sys.

    Перед отправкой этого запроса драйвер клиента должен отправить запрос URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE. В ответ Usbccgp.sys получает частичный дескриптор конфигурации , содержащий только дескрипторы интерфейса и другие дескрипторы, относящиеся к конкретной функции, для которой загружается драйвер клиента. Число интерфейсов, указанных в поле bNumInterfaces частичного дескриптора конфигурации, меньше общего числа интерфейсов, определенного для всего составного USB-устройства. Кроме того, в частичном дескрипторе конфигурации bInterfaceNumber дескриптора интерфейса указывает фактический номер интерфейса относительно всего устройства. Например, Usbccgp.sys может сообщать о частичном дескрипторе конфигурации со значением bNumInterfaces 2 и значением bInterfaceNumber 4 для первого интерфейса. Обратите внимание, что номер интерфейса больше, чем количество интерфейсов, сообщаемых.

    При перечислении интерфейсов в частичной конфигурации старайтесь не искать интерфейсы, вычисляя номера интерфейсов на основе количества интерфейсов. В предыдущем примере, если USBD_ParseConfigurationDescriptorEx вызывается в цикле, который начинается с нуля, заканчивается на (bNumInterfaces - 1)и увеличивает индекс интерфейса (указанный в параметре InterfaceNumber ) в каждой итерации, подпрограмме не удается получить правильный интерфейс. Вместо этого убедитесь, что вы ищете все интерфейсы в дескрипторе конфигурации, передав -1 в поле InterfaceNumber. Дополнительные сведения о реализации см. в примере кода в этом разделе.

    Сведения о том, как Usbccgp.sys обрабатывает запросы на выборку конфигурации, отправленные драйвером клиента, см. в разделе Настройка Usbccgp.sys для выбора конфигурации USB по умолчанию.

  4. Для каждого элемента (кроме последнего) в массиве задайте для элемента InterfaceDescriptor адрес дескриптора интерфейса. Для первого элемента массива задайте для члена InterfaceDescriptor адрес дескриптора интерфейса, представляющего первый интерфейс в конфигурации. Аналогичным образом для n-гоэлемента в массиве задайте для члена InterfaceDescriptor адрес дескриптора интерфейса, который представляет n-йинтерфейс в конфигурации.

  5. Элемент InterfaceDescriptor последнего элемента должен иметь значение NULL.

Шаг 2. Получение указателя на URB, выделенную стеком драйверов USB

Затем вызовите USBD_SelectConfigUrbAllocateAndBuild , указав конфигурацию для выбора и заполненный массив USBD_INTERFACE_LIST_ENTRY структур. Подпрограмма выполняет следующие задачи:

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

  • В этой urb выделяет структуру USBD_INTERFACE_INFORMATION для каждого дескриптора интерфейса, указанного драйвером клиента.

  • Задает элемент Interfacen-гоэлемента массива USBD_INTERFACE_LIST_ENTRY , предоставленного вызывающим объектом, адрес соответствующей структуры USBD_INTERFACE_INFORMATION в URB.

  • Инициализирует InterfaceNumber, AlternateSetting, NumberOfPipes, Pipes[i]. MaximumTransferSize и Pipes[i]. Элементы PipeFlags .

    Примечание

    В Windows 7 и ealier драйвер клиента создал URB для запроса на выборку конфигурации, вызвав USBD_CreateConfigurationRequestEx. В Windows 2000 USBD_CreateConfigurationRequestEx инициализирует Pipes[i]. MaximumTransferSize до максимального размера передачи по умолчанию для одного запроса на чтение и запись URB. Драйвер клиента может указать другой максимальный размер передачи в каналах[i]. MaximumTransferSize. Стек USB игнорирует это значение в Windows XP, Windows Server 2003 и более поздних версиях операционной системы. Дополнительные сведения о MaximumTransferSize см. в разделе Настройка передачи USB и размеров пакетов статьи Выделение пропускной способности USB.

Шаг 3. Отправка URB в стек драйверов USB

Чтобы отправить URB в стек драйверов USB, драйвер клиента должен отправить IOCTL_INTERNAL_USB_SUBMIT_URB запрос на управление вводом-выводом . Сведения об отправке URB см. в разделе Отправка URB.

После получения URB стек USB-драйверов заполняет остальные элементы каждой структуры USBD_INTERFACE_INFORMATION . В частности, элемент массива Pipes заполняется сведениями о каналами, связанными с конечными точками интерфейса.

Шаг 4. После завершения запроса проверьте структуры USBD_INTERFACE_INFORMATION и URB.

После того как стек usb-драйвера завершит IRP для запроса, стек возвращает список альтернативных параметров и связанных интерфейсов в массиве USBD_INTERFACE_LIST_ENTRY .

  1. Элемент "Каналы " каждой структуры USBD_INTERFACE_INFORMATION указывает на массив USBD_PIPE_INFORMATION структур, содержащих сведения о канале, связанном с каждой конечной точкой этого интерфейса. Драйвер клиента может получать дескрипторы канала из pipe[i]. PipeHandle и используйте их для отправки запросов ввода-вывода в определенные каналы. Каналы[i]. Элемент PipeType указывает тип конечной точки и передачи, поддерживаемых этим каналом.

  2. В элементе UrbSelectConfiguration urb стек драйверов USB возвращает дескриптор, который можно использовать для выбора альтернативного параметра интерфейса, отправляя другой URB типа URB_FUNCTION_SELECT_INTERFACE (запрос на выбор интерфейса). Чтобы выделить и создать структуру URB для этого запроса, вызовите USBD_SelectInterfaceUrbAllocateAndBuild.

    Запросы select-configuration и select-interface могут завершиться ошибкой, если не хватает пропускной способности для поддержки изохронных конечных точек, конечных точек управления и прерываний в включенных интерфейсах. В этом случае драйвер шины USB устанавливает для элемента Status заголовка URB значение USBD_STATUS_NO_BANDWIDTH.

В следующем примере кода показано, как создать массив структур USBD_INTERFACE_LIST_ENTRY и вызвать USBD_SelectConfigUrbAllocateAndBuild. В примере запрос отправляется синхронно путем вызова SubmitUrbSync. Пример кода для SubmitUrbSync см. в статье Отправка URB.

/*++

Routine Description:
This helper routine selects the specified configuration.

Arguments:
USBDHandle - USBD handle that is retrieved by the 
client driver in a previous call to the USBD_CreateHandle routine.

ConfigurationDescriptor - Pointer to the configuration
descriptor for the device. The caller receives this pointer
from the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE request.

Return Value: NT status value
--*/

NTSTATUS SelectConfiguration (PDEVICE_OBJECT DeviceObject,
                              PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor)
{
    PDEVICE_EXTENSION deviceExtension;
    PIO_STACK_LOCATION nextStack;
    PIRP irp;
    PURB urb = NULL;

    KEVENT    kEvent;
    NTSTATUS ntStatus;    

    PUSBD_INTERFACE_LIST_ENTRY   interfaceList = NULL; 
    PUSB_INTERFACE_DESCRIPTOR    interfaceDescriptor = NULL;
    PUSBD_INTERFACE_INFORMATION  Interface = NULL;
    USBD_PIPE_HANDLE             pipeHandle;

    ULONG                        interfaceIndex;

    PUCHAR StartPosition = (PUCHAR)ConfigurationDescriptor;

    deviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    // Allocate an array for the list of interfaces
    // The number of elements must be one more than number of interfaces.
    interfaceList = (PUSBD_INTERFACE_LIST_ENTRY)ExAllocatePool (
        NonPagedPool, 
        sizeof(USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    if(!interfaceList)
    {
        //Failed to allocate memory
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    // Initialize the array by setting all members to NULL.
    RtlZeroMemory (interfaceList, sizeof (
        USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    // Enumerate interfaces in the configuration.
    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        interfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
            ConfigurationDescriptor, 
            StartPosition, // StartPosition 
            -1,            // InterfaceNumber
            0,             // AlternateSetting
            -1,            // InterfaceClass
            -1,            // InterfaceSubClass
            -1);           // InterfaceProtocol

        if (!interfaceDescriptor) 
        {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            goto Exit;
        }

        // Set the interface entry
        interfaceList[interfaceIndex].InterfaceDescriptor = interfaceDescriptor;
        interfaceList[interfaceIndex].Interface = NULL;

        // Move the position to the next interface descriptor
        StartPosition = (PUCHAR)interfaceDescriptor + interfaceDescriptor->bLength;

    }

    // Make sure that the InterfaceDescriptor member of the last element to NULL.
    interfaceList[deviceExtension->NumInterfaces].InterfaceDescriptor = NULL;

    // Allocate and build an URB for the select-configuration request.
    ntStatus = USBD_SelectConfigUrbAllocateAndBuild(
        deviceExtension->UsbdHandle, 
        ConfigurationDescriptor, 
        interfaceList,
        &urb);

    if(!NT_SUCCESS(ntStatus)) 
    {
        goto Exit;
    }

    // Allocate the IRP to send the buffer down the USB stack.
    // The IRP will be freed by IO manager.
    irp = IoAllocateIrp((deviceExtension->NextDeviceObject->StackSize)+1, TRUE);  

    if (!irp)
    {
        //Irp could not be allocated.
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    ntStatus = SubmitUrbSync( 
        deviceExtension->NextDeviceObject, 
        irp, 
        urb, 
        CompletionRoutine);

    // Enumerate the pipes in the interface information array, which is now filled with pipe
    // information.

    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        ULONG i;

        Interface = interfaceList[interfaceIndex].Interface;

        for(i=0; i < Interface->NumberOfPipes; i++) 
        {
            pipeHandle = Interface->Pipes[i].PipeHandle;

            if (Interface->Pipes[i].PipeType == UsbdPipeTypeInterrupt)
            {
                deviceExtension->InterruptPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkInPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkOutPipe = pipeHandle;
            }
        }
    }

Exit:

    if(interfaceList) 
    {
        ExFreePool(interfaceList);
        interfaceList = NULL;
    }

    if (urb)
    {
        USBD_UrbFree( deviceExtension->UsbdHandle, urb); 
    }

    return ntStatus;
}

NTSTATUS CompletionRoutine ( PDEVICE_OBJECT DeviceObject,
                            PIRP           Irp,
                            PVOID          Context)
{
    PKEVENT kevent;

    kevent = (PKEVENT) Context;

    if (Irp->PendingReturned == TRUE)
    {
        KeSetEvent(kevent, IO_NO_INCREMENT, FALSE);
    }

    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Select-configuration request completed. \n" ));

    return STATUS_MORE_PROCESSING_REQUIRED;
}

Отключение конфигурации для USB-устройства

Чтобы отключить USB-устройство, создайте и отправьте запрос на выборку конфигурации с дескрипторовом конфигурации NULL. Для этого типа запроса можно повторно использовать urb, созданную для запроса, который выбрал конфигурацию на устройстве. Кроме того, можно выделить новую urb, вызвав USBD_UrbAllocate. Перед отправкой запроса необходимо отформатировать URB с помощью макроса UsbBuildSelectConfigurationRequest , как показано в следующем примере кода.

URB Urb;
UsbBuildSelectConfigurationRequest(
  &Urb,
  sizeof(_URB_SELECT_CONFIGURATION),
  NULL
);