Deskriptor konfigurasi USB

Perangkat USB memaparkan kemampuannya dalam bentuk serangkaian antarmuka yang disebut konfigurasi USB. Setiap antarmuka terdiri dari satu atau beberapa pengaturan alternatif, dan setiap pengaturan alternatif terdiri dari satu set titik akhir. Topik ini menjelaskan berbagai deskriptor yang terkait dengan konfigurasi USB.

Konfigurasi USB dijelaskan dalam deskriptor konfigurasi (lihat struktur USB_CONFIGURATION_DESCRIPTOR ). Deskriptor konfigurasi berisi informasi tentang konfigurasi dan antarmukanya, pengaturan alternatif, dan titik akhirnya. Setiap deskriptor antarmuka atau pengaturan alternatif dijelaskan dalam struktur USB_INTERFACE_DESCRIPTOR . Dalam konfigurasi, setiap deskriptor antarmuka diikuti dalam memori oleh semua deskriptor titik akhir untuk antarmuka dan pengaturan alternatif. Setiap deskriptor titik akhir disimpan dalam struktur USB_ENDPOINT_DESCRIPTOR .

Misalnya, pertimbangkan perangkat webcam USB yang dijelaskan dalam Tata Letak Perangkat USB. Perangkat mendukung konfigurasi dengan dua antarmuka, dan antarmuka pertama (indeks 0) mendukung dua pengaturan alternatif.

Contoh berikut menunjukkan pendeskripsi konfigurasi untuk perangkat webcam USB:

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

Bidang bConfigurationValue menunjukkan angka untuk konfigurasi yang ditentukan dalam firmware perangkat. Driver klien menggunakan nilai angka tersebut untuk memilih konfigurasi aktif. Untuk informasi selengkapnya tentang konfigurasi perangkat USB, lihat Cara Memilih Konfigurasi untuk Perangkat USB. Konfigurasi USB juga menunjukkan karakteristik daya tertentu. bmAttributes berisi bitmask yang menunjukkan apakah konfigurasi mendukung fitur bangun jarak jauh, dan apakah perangkat didukung bus atau didukung sendiri. Bidang MaxPower menentukan daya maksimum (dalam unit miliamp) yang dapat diambil perangkat dari host, ketika perangkat didukung bus. Deskriptor konfigurasi juga menunjukkan jumlah total antarmuka (bNumInterfaces) yang didukung perangkat.

Contoh berikut menunjukkan deskriptor antarmuka untuk Pengaturan Alternatif 0 Antarmuka 0 untuk perangkat webcam:

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"

Dalam contoh sebelumnya, perhatikan nilai bidang bInterfaceNumber dan bAlternateSetting . Bidang-bidang tersebut berisi nilai indeks yang digunakan driver klien untuk mengaktifkan antarmuka dan salah satu pengaturan alternatifnya. Untuk aktivasi, driver mengirimkan permintaan select-interface ke tumpukan driver USB. Tumpukan driver kemudian membangun permintaan kontrol standar (SET INTERFACE) dan mengirimkannya ke perangkat. Perhatikan bidang bInterfaceClass . Deskriptor antarmuka atau deskriptor untuk salah satu pengaturan alternatifnya menentukan kode kelas, subkelas, dan protokol. Nilai 0x0E menunjukkan bahwa antarmuka adalah untuk kelas perangkat video. Selain itu, perhatikan bidang iInterface . Nilai itu menunjukkan bahwa ada dua deskriptor string yang ditambahkan ke deskriptor antarmuka. Deskriptor string berisi deskripsi Unicode yang digunakan selama enumerasi perangkat untuk mengidentifikasi fungsionalitas. Untuk informasi selengkapnya tentang deskriptor string, lihat Deskriptor String USB.

Setiap titik akhir, dalam antarmuka, menjelaskan satu aliran input atau output untuk perangkat. Perangkat yang mendukung aliran untuk berbagai jenis fungsi memiliki beberapa antarmuka. Perangkat yang mendukung beberapa aliran yang berkaitan dengan fungsi dapat mendukung beberapa titik akhir pada satu antarmuka.

Semua jenis titik akhir (kecuali titik akhir default) harus menyediakan deskriptor titik akhir sehingga host bisa mendapatkan informasi tentang titik akhir. Deskriptor titik akhir menyertakan informasi, seperti alamat, jenis, arah, dan jumlah data yang dapat ditangani titik akhir. Transfer data ke titik akhir didasarkan pada informasi tersebut.

Contoh berikut menunjukkan deskriptor titik akhir untuk perangkat webcam:

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

Bidang bEndpointAddress menentukan alamat titik akhir unik yang berisi nomor titik akhir (Bit 3..0) dan arah titik akhir (Bit 7). Dengan membaca nilai-nilai tersebut dalam contoh sebelumnya, kita dapat menentukan bahwa deskriptor menjelaskan titik akhir IN yang angka titik akhirnya adalah 2. Atribut bmAttributes menunjukkan bahwa jenis titik akhir bersifat isochronous. wMaxPacketSizefield menunjukkan jumlah maksimum byte yang dapat dikirim atau diterima titik akhir dalam satu transaksi. Bit 12..11 menunjukkan jumlah total transaksi yang dapat dikirim per mikroframe. bInterval menunjukkan seberapa sering titik akhir dapat mengirim atau menerima data.

Cara mendapatkan deskriptor konfigurasi

Deskriptor konfigurasi diperoleh dari perangkat melalui permintaan perangkat standar (GET_DESCRIPTOR), yang dikirim sebagai transfer kontrol oleh tumpukan driver USB. Driver klien USB dapat memulai permintaan dengan salah satu cara berikut:

  • Jika perangkat hanya mendukung satu konfigurasi, cara termampunya adalah dengan memanggil metode WdfUsbTargetDeviceRetrieveConfigDescriptor yang disediakan kerangka kerja.

  • Untuk perangkat yang mendukung beberapa konfigurasi, jika driver klien ingin mendapatkan deskriptor konfigurasi selain yang pertama, driver harus mengirimkan URB. Untuk mengirimkan URB, driver harus mengalokasikan, memformat, lalu mengirimkan URB ke tumpukan driver USB.

    Untuk mengalokasikan URB, driver klien harus memanggil metode WdfUsbTargetDeviceCreateUrb . Metode ini menerima pointer ke URB yang dialokasikan oleh tumpukan driver USB.

    Untuk memformat URB, driver klien dapat menggunakan makro UsbBuildGetDescriptorRequest . Makro mengatur semua informasi yang diperlukan dalam URB, seperti nomor konfigurasi yang ditentukan perangkat untuk mengambil deskriptor. Fungsi URB diatur ke URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE (lihat _URB_CONTROL_DESCRIPTOR_REQUEST) dan jenis deskriptor diatur ke USB_CONFIGURATION_DESCRIPTOR_TYPE. Dengan menggunakan informasi yang terkandung dalam URB, tumpukan driver USB membangun permintaan kontrol standar dan mengirimkannya ke perangkat.

    Untuk mengirimkan URB, driver klien harus menggunakan objek permintaan WDF. Untuk mengirim objek permintaan ke tumpukan driver USB secara asinkron, driver harus memanggil metode **WdfRequestSend**. Untuk mengirimnya secara sinkron, panggil metode WdfUsbTargetDeviceSendUrbSynchronously .

    Driver WDM: Driver klien Windows Driver Model (WDM) hanya bisa mendapatkan pendeskripsi konfigurasi dengan mengirimkan URB. Untuk mengalokasikan URB, driver harus memanggil rutinitas USBD_UrbAllocate . Untuk memformat URB, driver harus memanggil makro UsbBuildGetDescriptorRequest . Untuk mengirimkan URB, driver harus mengaitkan URB dengan IRP, dan mengirimkan IRP ke tumpukan driver USB. Untuk informasi selengkapnya, lihat Cara Mengirimkan URB.

Dalam konfigurasi USB, jumlah antarmuka dan pengaturan alternatifnya bervariasi. Oleh karena itu, sulit untuk memprediksi ukuran buffer yang diperlukan untuk menahan deskriptor konfigurasi. Driver klien harus mengumpulkan semua informasi tersebut dalam dua langkah. Pertama, tentukan buffer ukuran apa yang diperlukan untuk menahan semua deskriptor konfigurasi, lalu terbitkan permintaan untuk mengambil seluruh deskriptor. Driver klien bisa mendapatkan ukuran dengan salah satu cara berikut:

Untuk mendapatkan deskriptor konfigurasi dengan memanggil WdfUsbTargetDeviceRetrieveConfigDescriptor, lakukan langkah-langkah berikut:

  1. Dapatkan ukuran buffer yang diperlukan untuk menyimpan semua informasi konfigurasi dengan memanggil WdfUsbTargetDeviceRetrieveConfigDescriptor. Driver harus melewati NULL di buffer, dan variabel untuk menahan ukuran buffer.
  2. Alokasikan buffer yang lebih besar berdasarkan ukuran yang diterima melalui panggilan WdfUsbTargetDeviceRetrieveConfigDescriptor sebelumnya.
  3. Panggil WdfUsbTargetDeviceRetrieveConfigDescriptor lagi dan tentukan penunjuk ke buffer baru yang dialokasikan di langkah 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;   
}

Untuk mendapatkan pendeskripsi konfigurasi dengan mengirimkan URB, lakukan langkah-langkah berikut:

  1. Alokasikan URB dengan memanggil metode WdfUsbTargetDeviceCreateUrb .
  2. Format URB dengan memanggil makro UsbBuildGetDescriptorRequest . Buffer transfer URB harus menunjuk ke buffer yang cukup besar untuk menahan struktur USB_CONFIGURATION_DESCRIPTOR .
  3. Kirimkan URB sebagai objek permintaan WDF dengan memanggil WdfRequestSend atau WdfUsbTargetDeviceSendUrbSynchronously.
  4. Setelah permintaan selesai, periksa anggota USB_CONFIGURATION_DESCRIPTOR wTotalLength. Nilai tersebut menunjukkan ukuran buffer yang diperlukan untuk berisi deskriptor konfigurasi lengkap.
  5. Alokasikan buffer yang lebih besar berdasarkan ukuran yang diambil di wTotalLength.
  6. Terbitkan permintaan yang sama dengan buffer yang lebih besar.

Contoh kode berikut menunjukkan panggilan UsbBuildGetDescriptorRequest untuk permintaan guna mendapatkan informasi konfigurasi untuk konfigurasi 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;
}

Saat perangkat mengembalikan deskriptor konfigurasi, buffer permintaan diisi dengan deskriptor antarmuka untuk semua pengaturan alternatif, dan deskriptor titik akhir untuk semua titik akhir dalam pengaturan alternatif tertentu. Untuk perangkat yang dijelaskan dalam Tata Letak Perangkat USB, diagram berikut menggambarkan bagaimana informasi konfigurasi ditata dalam memori.

Diagram tata letak deskriptor konfigurasi.

Anggota bInterfaceNumber berbasis nol dari USB_INTERFACE_DESCRIPTOR membedakan antarmuka dalam konfigurasi. Untuk antarmuka tertentu, anggota bAlternateSetting berbasis nol membedakan antara pengaturan alternatif antarmuka. Perangkat mengembalikan deskriptor antarmuka dalam urutan nilai bInterfaceNumber lalu dalam urutan nilai bAlternateSetting .

Untuk mencari deskriptor antarmuka tertentu dalam konfigurasi, driver klien dapat memanggil USBD_ParseConfigurationDescriptorEx. Dalam panggilan, driver klien menyediakan posisi awal dalam konfigurasi. Secara opsional driver dapat menentukan nomor antarmuka, pengaturan alternatif, kelas, subkelas, atau protokol. Rutinitas mengembalikan penunjuk ke deskriptor antarmuka yang cocok berikutnya.

Untuk memeriksa deskriptor konfigurasi untuk deskriptor titik akhir atau string, gunakan rutinitas USBD_ParseDescriptors . Pemanggil menyediakan posisi awal dalam konfigurasi dan jenis deskriptor, seperti USB_STRING_DESCRIPTOR_TYPE atau USB_ENDPOINT_DESCRIPTOR_TYPE. Rutinitas mengembalikan penunjuk ke deskriptor pencocokan berikutnya.