Udostępnij przez


Deskryptory konfiguracji USB

Urządzenie USB uwidacznia swoje możliwości w postaci serii interfejsów nazywanych konfiguracją USB. Każdy interfejs składa się z co najmniej jednego alternatywnego ustawienia, a każde alternatywne ustawienie składa się z zestawu punktów końcowych. W tym temacie opisano różne deskryptory skojarzone z konfiguracją USB.

Konfiguracja USB jest opisana w deskryptorze konfiguracji (zobacz USB_CONFIGURATION_DESCRIPTOR strukturę). Deskryptor konfiguracji zawiera informacje o konfiguracji i jego interfejsach, ustawieniach alternatywnych i ich punktach końcowych. Każdy deskryptor interfejsu lub ustawienie alternatywne jest opisane w strukturze USB_INTERFACE_DESCRIPTOR . W konfiguracji każdy deskryptor interfejsu jest obserwowany w pamięci przez wszystkie deskryptory punktów końcowych dla interfejsu i ustawienia alternatywnego. Każdy deskryptor punktu końcowego jest przechowywany w strukturze USB_ENDPOINT_DESCRIPTOR .

Rozważmy na przykład urządzenie kamery internetowej USB opisane w temacie Układ urządzenia USB. Urządzenie obsługuje konfigurację z dwoma interfejsami, a pierwszy interfejs (indeks 0) obsługuje dwa alternatywne ustawienia.

W poniższym przykładzie pokazano deskryptor konfiguracji urządzenia kamery internetowej USB:

Configuration Descriptor:
wTotalLength:         0x02CA
bNumInterfaces:       0x02
bConfigurationValue:  0x01
iConfiguration:       0x00
bmAttributes:         0x80 (Bus Powered )
MaxPower:             0xFA (500 mA)

Pole bConfigurationValue wskazuje liczbę konfiguracji zdefiniowanej w oprogramowaniu układowym urządzenia. Sterownik klienta używa tej wartości liczbowej do wybrania aktywnej konfiguracji. Aby uzyskać więcej informacji na temat konfiguracji urządzenia USB, zobacz How to Select a Configuration for a USB Device (Jak wybrać konfigurację urządzenia USB). Konfiguracja USB wskazuje również pewne cechy zasilania. Atrybuty bmAttributes zawierają maskę bitów, która wskazuje, czy konfiguracja obsługuje funkcję zdalnego wznawiania, oraz czy urządzenie jest zasilane z magistrali, czy z napędem własnym. Pole MaxPower określa maksymalną moc (w miliampowych jednostkach), którą urządzenie może pobierać z hosta, gdy urządzenie jest zasilane magistralą. Deskryptor konfiguracji wskazuje również łączną liczbę interfejsów (bNumInterfaces), które obsługuje urządzenie.

W poniższym przykładzie pokazano deskryptor interfejsu dla alternatywnego ustawienia 0 interfejsu 0 dla urządzenia kamery internetowej:

Interface Descriptor:
bInterfaceNumber:     0x00
bAlternateSetting:    0x00
bNumEndpoints:        0x01
bInterfaceClass:      0x0E
bInterfaceSubClass:   0x02
bInterfaceProtocol:   0x00
iInterface:           0x02
0x0409: "Microsoft LifeCam VX-5000"
0x0409: "Microsoft LifeCam VX-5000"

W poprzednim przykładzie zwróć uwagę na wartości pól bInterfaceNumber i bAlternateSetting . Te pola zawierają wartości indeksu używane przez sterownik klienta do aktywowania interfejsu i jednego z jego ustawień alternatywnych. W przypadku aktywacji sterownik wysyła żądanie select-interface do stosu sterowników USB. Następnie stos sterowników tworzy standardowe żądanie sterowania (SET INTERFACE) i wysyła je do urządzenia. Zanotuj pole bInterfaceClass . Deskryptor interfejsu lub deskryptor dla dowolnego z jego ustawień alternatywnych określa kod klasy, podklasę i protokół. Wartość 0x0E wskazuje, że interfejs jest przeznaczony dla klasy urządzenia wideo. Zwróć również uwagę na pole iInterface . Ta wartość wskazuje, że istnieją dwa deskryptory ciągów dołączone do deskryptora interfejsu. Deskryptory ciągów zawierają opisy Unicode używane podczas wyliczania urządzenia w celu zidentyfikowania funkcji. Aby uzyskać więcej informacji na temat deskryptorów ciągów, zobacz Deskryptory ciągów USB.

Każdy punkt końcowy w interfejsie opisuje pojedynczy strumień danych wejściowych lub wyjściowych dla urządzenia. Urządzenie obsługujące strumienie dla różnych rodzajów funkcji ma wiele interfejsów. Urządzenie obsługujące kilka strumieni odnoszących się do funkcji może obsługiwać wiele punktów końcowych w jednym interfejsie.

Wszystkie typy punktów końcowych (z wyjątkiem domyślnego punktu końcowego) muszą udostępniać deskryptory punktów końcowych, aby host mógł uzyskać informacje o punkcie końcowym. Deskryptor punktu końcowego zawiera informacje, takie jak jego adres, typ, kierunek i ilość danych, które może obsłużyć punkt końcowy. Transfery danych do punktu końcowego są oparte na tych informacjach.

W poniższym przykładzie pokazano deskryptor punktu końcowego dla urządzenia kamery internetowej:

Endpoint Descriptor:
bEndpointAddress:   0x82  IN
bmAttributes:       0x01
wMaxPacketSize:     0x0080 (128)
bInterval:          0x01

Pole bEndpointAddress określa unikatowy adres punktu końcowego, który zawiera numer punktu końcowego (bity 3.0) i kierunek punktu końcowego (Bit 7). Odczytując te wartości w poprzednim przykładzie, możemy określić, że deskryptor opisuje punkt końcowy IN, którego numer punktu końcowego to 2. Atrybut bmAttributes wskazuje, że typ punktu końcowego jest izochroniczny. Pole wMaxPacketSizefield wskazuje maksymalną liczbę bajtów, które punkt końcowy może wysyłać lub odbierać w jednej transakcji. Bity 12...11 wskazują całkowitą liczbę transakcji, które można wysłać na mikroframe. BInterval wskazuje, jak często punkt końcowy może wysyłać lub odbierać dane.

Jak uzyskać deskryptor konfiguracji

Deskryptor konfiguracji jest uzyskiwany z urządzenia za pośrednictwem standardowego żądania urządzenia (GET_DESCRIPTOR), które jest wysyłane jako transfer sterowania przez stos sterowników USB. Sterownik klienta USB może zainicjować żądanie w jeden z następujących sposobów:

  • Jeśli urządzenie obsługuje tylko jedną konfigurację, najprostszym sposobem jest wywołanie metody WdfUsbTargetDeviceRetrieveConfigDescriptor dostarczonej przez platformę.

  • W przypadku urządzenia obsługującego wiele konfiguracji, jeśli sterownik klienta chce uzyskać deskryptor konfiguracji innej niż pierwsza, sterownik musi przesłać identyfikator URB. Aby przesłać blok URB, sterownik musi przydzielić, sformatować, a następnie przesłać URB do stosu sterowników USB.

    Aby przydzielić identyfikator URB, sterownik klienta musi wywołać metodę WdfUsbTargetDeviceCreateUrb . Metoda odbiera wskaźnik do URB przydzielonego przez stos sterowników USB.

    Aby przygotować URB, sterownik klienta może użyć makra UsbBuildGetDescriptorRequest. Makro ustawia wszystkie niezbędne informacje w URB, takie jak numer konfiguracji zdefiniowanej przez urządzenie, dla której należy pobrać deskryptor. Funkcja URB jest ustawiona na URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (zobacz _URB_CONTROL_DESCRIPTOR_REQUEST), a typ deskryptora jest ustawiony na USB_CONFIGURATION_DESCRIPTOR_TYPE. Korzystając z informacji zawartych w urB, stos sterowników USB tworzy standardowe żądanie sterowania i wysyła je do urządzenia.

    Aby wysłać URB, sterownik klienta musi używać obiektu żądania WDF. Aby wysłać obiekt żądania do asynchronicznego stosu sterowników USB, sterownik musi wywołać metodę **WdfRequestSend**. Aby wysłać ją synchronicznie, wywołaj metodę WdfUsbTargetDeviceSendUrbSynchronously .

    Sterowniki WDM: Sterownik klienta modelu sterowników Windows (WDM) może uzyskać deskryptor konfiguracji tylko przez przesłanie URB. Aby przydzielić identyfikator URB, sterownik musi wywołać procedurę USBD_UrbAllocate . Aby sformatować URB, sterownik musi wywołać makro UsbBuildGetDescriptorRequest. Aby przesłać URB, sterownik musi skojarzyć URB z IRP i przesłać IRP do stosu sterowników USB. Aby uzyskać więcej informacji, zobacz jak przesłać URB .

W ramach konfiguracji USB liczba interfejsów i ich ustawień alternatywnych są zmienne. W związku z tym trudno jest przewidzieć rozmiar buforu wymaganego do przechowywania deskryptora konfiguracji. Sterownik klienta musi zebrać wszystkie te informacje w dwóch krokach. Najpierw określ rozmiar bufora wymagany do przechowywania całego deskryptora konfiguracji, a następnie wydaj żądanie pobrania całego deskryptora. Sterownik klienta może uzyskać rozmiar w jeden z następujących sposobów:

Aby uzyskać deskryptor konfiguracji przez wywołanie klasy WdfUsbTargetDeviceRetrieveConfigDescriptor, wykonaj następujące kroki:

  1. Pobierz rozmiar buforu wymaganego do przechowywania wszystkich informacji o konfiguracji, wywołując element WdfUsbTargetDeviceRetrieveConfigDescriptor. Sterownik musi przekazać wartość NULL do bufora oraz zmienną przechowującą rozmiar bufora.
  2. Przydziel większy bufor na podstawie rozmiaru otrzymanego z poprzedniego wywołania funkcji WdfUsbTargetDeviceRetrieveConfigDescriptor.
  3. Wywołaj ponownie WdfUsbTargetDeviceRetrieveConfigDescriptor i wskaż wskaźnik do nowego bufora przydzielonego w kroku 2.
 NTSTATUS RetrieveDefaultConfigurationDescriptor (
    _In_  WDFUSBDEVICE  UsbDevice,
    _Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor 
    )
{
    NTSTATUS ntStatus = -1;

    USHORT sizeConfigDesc;

    PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;

    PAGED_CODE();

    *ConfigDescriptor  = NULL;

    ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
        UsbDevice, 
        NULL,
        &sizeConfigDesc);

    if (sizeConfigDesc == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor size.");

        goto Exit;
    }
    else
    {
        fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
            NonPagedPool, 
            sizeConfigDesc,
            USBCLIENT_TAG);

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

    RtlZeroMemory (fullConfigDesc, sizeConfigDesc);

    ntStatus = WdfUsbTargetDeviceRetrieveConfigDescriptor (
        UsbDevice, 
        fullConfigDesc,
        &sizeConfigDesc);

    if (!NT_SUCCESS(ntStatus))
    {           
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor.");

        goto Exit;
    }

    *ConfigDescriptor = fullConfigDesc;

Exit:

    return ntStatus;   
}

Aby pobrać deskryptor konfiguracji poprzez przesłanie żądania URB, wykonaj następujące kroki:

  1. Przydziel identyfikator URB, wywołując metodę WdfUsbTargetDeviceCreateUrb .
  2. Sformatuj URB, wywołując makro UsbBuildGetDescriptorRequest. Bufor transferu URB musi wskazywać bufor wystarczająco duży, aby pomieścić strukturę USB_CONFIGURATION_DESCRIPTOR.
  3. Prześlij identyfikator URB jako obiekt żądania WDF, wywołując element WdfRequestSend lub WdfUsbTargetDeviceSendUrbSynchronously.
  4. Po zakończeniu żądania sprawdź członek wTotalLength w USB_CONFIGURATION_DESCRIPTOR. Ta wartość wskazuje rozmiar buforu wymaganego do przechowywania pełnego deskryptora konfiguracji.
  5. Przydziel większy bufor w oparciu o rozmiar pobrany z wTotalLength.
  6. Wydaj to samo żądanie z większym buforem.

Poniższy przykładowy kod przedstawia wywołanie UsbBuildGetDescriptorRequest dla żądania uzyskania informacji o konfiguracji dla konfiguracji i-th:

NTSTATUS FX3_RetrieveConfigurationDescriptor (
    _In_ WDFUSBDEVICE  UsbDevice,
    _In_ PUCHAR ConfigurationIndex,
    _Out_ PUSB_CONFIGURATION_DESCRIPTOR *ConfigDescriptor 
    )
{
    NTSTATUS ntStatus = STATUS_SUCCESS;

    USB_CONFIGURATION_DESCRIPTOR configDesc;
    PUSB_CONFIGURATION_DESCRIPTOR fullConfigDesc = NULL;

    PURB urb = NULL;

    WDFMEMORY urbMemory = NULL;

    PAGED_CODE();

    RtlZeroMemory (&configDesc, sizeof(USB_CONFIGURATION_DESCRIPTOR));
    *ConfigDescriptor = NULL;

    // Allocate an URB for the get-descriptor request. 
    // WdfUsbTargetDeviceCreateUrb returns the address of the 
    // newly allocated URB and the WDFMemory object that 
    // contains the URB.

    ntStatus = WdfUsbTargetDeviceCreateUrb (
        UsbDevice,
        NULL,
        &urbMemory,
        &urb);

    if (!NT_SUCCESS (ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not allocate URB for an open-streams request.");

        goto Exit;
    }

       // Format the URB.
    UsbBuildGetDescriptorRequest (
        urb,                                                        // Points to the URB to be formatted
        (USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),  // Size of the URB.
        USB_CONFIGURATION_DESCRIPTOR_TYPE,                          // Type of descriptor
        *ConfigurationIndex,                                        // Index of the configuration
        0,                                                          // Not used for configuration descriptors
        &configDesc,                                                // Points to a USB_CONFIGURATION_DESCRIPTOR structure
        NULL,                                                       // Not required because we are providing a buffer not MDL
        sizeof(USB_CONFIGURATION_DESCRIPTOR),                       // Size of the USB_CONFIGURATION_DESCRIPTOR structure.
        NULL                                                        // Reserved.
        );

       // Send the request synchronously.
    ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
        UsbDevice,
        NULL,
        NULL,
        urb);

    if (configDesc.wTotalLength == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor size.");

        ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    // Allocate memory based on the retrieved size. 
       // The allocated memory is released by the caller.
    fullConfigDesc = (PUSB_CONFIGURATION_DESCRIPTOR) ExAllocatePoolWithTag (
        NonPagedPool, 
        configDesc.wTotalLength,
        USBCLIENT_TAG);

    RtlZeroMemory (fullConfigDesc, configDesc.wTotalLength);

    if (!fullConfigDesc)
    {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;

        goto Exit;
    }

       // Format the URB.
    UsbBuildGetDescriptorRequest (
        urb,                                                        
        (USHORT) sizeof( struct _URB_CONTROL_DESCRIPTOR_REQUEST ),  
        USB_CONFIGURATION_DESCRIPTOR_TYPE,                          
        *ConfigurationIndex,                                         
        0,                                                          
        fullConfigDesc,                                                 
        NULL,                                                       
        configDesc.wTotalLength,                       
        NULL                                                        
        );

       // Send the request again.
    ntStatus = WdfUsbTargetDeviceSendUrbSynchronously (
        UsbDevice,
        NULL,
        NULL,
        urb);

    if ((fullConfigDesc->wTotalLength == 0) || !NT_SUCCESS (ntStatus))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, 
            "%!FUNC! Could not retrieve the configuration descriptor.");

        ntStatus = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

       // Return to the caller.
    *ConfigDescriptor = fullConfigDesc;

Exit:

    if (urbMemory)
    {
        WdfObjectDelete (urbMemory);
    }

    return ntStatus;
}

Gdy urządzenie zwraca deskryptor konfiguracji, bufor żądania jest wypełniony deskryptorami interfejsu dla wszystkich ustawień alternatywnych, a deskryptory punktów końcowych dla wszystkich punktów końcowych w określonym ustawieniu alternatywnym. W przypadku urządzenia opisanego w temacie Układ urządzenia USB na poniższym diagramie przedstawiono sposób rozmieszczenia informacji o konfiguracji w pamięci.

Diagram układu deskryptora konfiguracji.

Człon bInterfaceNumber indeksowany od zera w USB_INTERFACE_DESCRIPTOR rozróżnia interfejsy w konfiguracji. Dla danego interfejsu element członkowski bAlternateSetting oparty na zera rozróżnia alternatywne ustawienia interfejsu. Urządzenie zwraca deskryptory interfejsu w kolejności wartości bInterfaceNumber , a następnie w kolejności wartości bAlternateSetting .

Aby wyszukać deskryptor interfejsu w ramach konfiguracji, sterownik klienta może wywołać USBD_ParseConfigurationDescriptorEx. W wywołaniu sterownik klienta udostępnia pozycję początkową w ramach konfiguracji. Opcjonalnie sterownik może określić numer interfejsu, ustawienie alternatywne, klasę, podklasę lub protokół. Rutyna zwraca wskaźnik do następnego deskryptora pasującego interfejsu.

Aby zbadać deskryptor konfiguracji dla punktu końcowego lub deskryptora łańcucha, użyj rutyny USBD_ParseDescriptors. Wywołujący określa pozycję początkową w konfiguracji oraz typ deskryptora, taki jak USB_STRING_DESCRIPTOR_TYPE lub USB_ENDPOINT_DESCRIPTOR_TYPE. Procedura zwraca wskaźnik do następnego deskryptora, który pasuje.