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

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

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

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

Предпосылки

  • Начиная с Windows 8 USBD_SelectConfigUrbAllocateAndBuild заменяет USBD_CreateConfigurationRequestEx.

  • Перед отправкой запроса на выборку конфигурации необходимо иметь USBD-дескриптор для регистрации драйвера клиента в стеке USB-драйверов. Создание вызова usbD-дескриптора USBD_CreateHandle.

  • Убедитесь, что вы получили дескриптор конфигурации (USB_CONFIGURATION_DESCRIPTOR структуру) выбранной конфигурации. Как правило, вы отправляете URB типа URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (см. _URB_CONTROL_DESCRIPTOR_REQUEST), чтобы получить сведения о конфигурации устройства. Дополнительные сведения см. в разделе "Дескрипторы конфигурации USB".

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

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

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

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

    • Элемент InterfaceDescriptor каждой структуры указывает на дескриптор интерфейса, содержащий альтернативный параметр.
    • Элемент интерфейса каждой структуры указывает на структуру USBD_INTERFACE_INFORMATION , содержащую сведения о канале в элементе "Каналы ". Каналы хранят сведения о каждой конечной точке, определенной в альтернативном параметре.
  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 адрес дескриптора интерфейса, представляющего первый интерфейс в конфигурации. Аналогичным образом для элемента nth в массиве задайте элементу InterfaceDescriptor адрес дескриптора интерфейса, представляющего интерфейс nth в конфигурации.

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

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

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

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

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

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

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

    Примечание.

    В Windows 7 и более ранних версиях драйвер клиента создал URB для запроса на выборку конфигурации, вызвав USBD_CreateConfigurationRequestEx. В Windows 2000 USBD_CreateConfigurationRequestEx инициализирует Пайпы[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. В частности, член массива каналов заполняется сведениями о каналах, связанных с конечными точками интерфейса.

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

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

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

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

    Запрос на выбор конфигурации и запрос на выбор интерфейса могут завершиться ошибкой, если недостаточно пропускной способности для поддержки изохронных, управляемых и прерываемых конечных точек в активированных интерфейсах. В этом случае драйвер USB-шины устанавливает для элемента состояния заголовка 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
);