Menulis driver klien UDE

Artikel ini menjelaskan perilaku ekstensi kelas emulasi perangkat USB (UDE) dan tugas yang harus dilakukan driver klien untuk pengontrol host dan perangkat yang ditiru yang melekat padanya. Ini memberikan informasi tentang bagaimana driver kelas dan ekstensi kelas berkomunikasi dengan masing-masing melalui serangkaian fungsi rutinitas dan panggilan balik. Ini juga menjelaskan fitur yang diharapkan diterapkan driver klien.

Ringkasan

  • Objek dan handel UDE yang digunakan oleh ekstensi kelas dan driver klien.
  • Membuat pengontrol host yang ditimulasi dengan fitur untuk mengkueri kemampuan pengontrol dan mengatur ulang pengontrol.
  • Membuat perangkat USB virtual, menyiapkannya untuk manajemen daya dan transfer data melalui titik akhir.

API penting

Sebelum Anda mulai

  • Instal Windows Driver Kit (WDK) terbaru komputer pengembangan Anda. Kit ini memiliki file header dan pustaka yang diperlukan untuk menulis driver klien UDE, khususnya, Anda akan memerlukan:
    • Pustaka stub, (Udecxstub.lib). Pustaka menerjemahkan panggilan yang dilakukan oleh driver klien dan meneruskannya ke UdeCx.
    • File header, Udecx.h.
  • Instal Windows 10 pada komputer target Anda.
  • Biasakan diri Anda dengan UDE. Lihat Arsitektur: Emulasi Perangkat USB (UDE).
  • Biasakan diri Anda dengan Windows Driver Foundation (WDF). Bacaan yang direkomendasikan: Mengembangkan Driver dengan Windows Driver Foundation, ditulis oleh Penny Orwick dan Guy Smith.

Objek dan handel UDE

Ekstensi kelas UDE dan driver klien menggunakan objek WDF tertentu yang mewakili pengontrol host yang ditimulasi dan perangkat virtual, termasuk titik akhir dan URL yang digunakan untuk mentransfer data antara perangkat dan host. Driver klien meminta pembuatan objek dan masa pakai objek dikelola oleh ekstensi kelas.

  • Objek pengontrol host yang ditimulasi (WDFDEVICE)

    Mewakili pengontrol host yang ditimulasi dan merupakan handel utama antara ekstensi kelas UDE dan driver klien.

  • Objek perangkat UDE (UDECXUSBDEVICE)

    Mewakili perangkat USB virtual yang terhubung ke port pada pengontrol host yang ditimulasi.

  • Objek titik akhir UDE (UDECXUSBENDPOINT)

    Mewakili pipa data berurutan perangkat USB. Digunakan untuk menerima permintaan perangkat lunak untuk mengirim atau menerima data di titik akhir.

Menginisialisasi pengontrol host yang ditimulasi

Berikut adalah ringkasan urutan di mana driver klien mengambil handel WDFDEVICE untuk pengontrol host yang ditiru. Kami menyarankan agar driver melakukan tugas-tugas ini dalam fungsi panggilan balik EvtDriverDeviceAdd .

  1. Panggil UdecxInitializeWdfDeviceInit dengan meneruskan referensi ke WDFDEVICE_INIT diteruskan oleh kerangka kerja.

  2. Inisialisasi struktur WDFDEVICE_INIT dengan informasi penyiapan sehingga perangkat ini tampak mirip dengan pengontrol host USB lainnya. Misalnya tetapkan nama FDO dan tautan simbolis, daftarkan antarmuka perangkat dengan GUID GUID_DEVINTERFACE_USB_HOST_CONTROLLER yang disediakan Microsoft sebagai GUID antarmuka perangkat sehingga aplikasi dapat membuka handel ke perangkat.

  3. Panggil WdfDeviceCreate untuk membuat objek perangkat kerangka kerja.

  4. Panggil UdecxWdfDeviceAddUsbDeviceEmulation dan daftarkan fungsi panggilan balik driver klien.

    Berikut adalah fungsi panggilan balik yang terkait dengan objek pengontrol host, yang dipanggil oleh ekstensi kelas UDE. Fungsi-fungsi ini harus diimplementasikan oleh driver klien.

    
    EVT_WDF_DRIVER_DEVICE_ADD                 Controller_WdfEvtDeviceAdd;
    
    #define BASE_DEVICE_NAME                  L"\\Device\\USBFDO-"
    #define BASE_SYMBOLIC_LINK_NAME           L"\\DosDevices\\HCD"
    
    #define DeviceNameSize                    sizeof(BASE_DEVICE_NAME)+MAX_SUFFIX_SIZE
    #define SymLinkNameSize                   sizeof(BASE_SYMBOLIC_LINK_NAME)+MAX_SUFFIX_SIZE
    
    NTSTATUS
    Controller_WdfEvtDeviceAdd(
        _In_
            WDFDRIVER Driver,
        _Inout_
            PWDFDEVICE_INIT WdfDeviceInit
        )
    {
        NTSTATUS                            status;
        WDFDEVICE                           wdfDevice;
        WDF_PNPPOWER_EVENT_CALLBACKS        wdfPnpPowerCallbacks;
        WDF_OBJECT_ATTRIBUTES               wdfDeviceAttributes;
        WDF_OBJECT_ATTRIBUTES               wdfRequestAttributes;
        UDECX_WDF_DEVICE_CONFIG             controllerConfig;
        WDF_FILEOBJECT_CONFIG               fileConfig;
        PWDFDEVICE_CONTEXT                  pControllerContext;
        WDF_IO_QUEUE_CONFIG                 defaultQueueConfig;
        WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS
                                            idleSettings;
        UNICODE_STRING                      refString;
        ULONG instanceNumber;
        BOOLEAN isCreated;
    
        DECLARE_UNICODE_STRING_SIZE(uniDeviceName, DeviceNameSize);
        DECLARE_UNICODE_STRING_SIZE(uniSymLinkName, SymLinkNameSize);
    
        UNREFERENCED_PARAMETER(Driver);
    
        ...
    
        WdfDeviceInitSetPnpPowerEventCallbacks(WdfDeviceInit, &wdfPnpPowerCallbacks);
    
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfRequestAttributes, REQUEST_CONTEXT);
        WdfDeviceInitSetRequestAttributes(WdfDeviceInit, &wdfRequestAttributes);
    
    // To distinguish I/O sent to GUID_DEVINTERFACE_USB_HOST_CONTROLLER, we will enable
    // enable interface reference strings by calling WdfDeviceInitSetFileObjectConfig
    // with FileObjectClass WdfFileObjectWdfXxx.
    
    WDF_FILEOBJECT_CONFIG_INIT(&fileConfig,
                                WDF_NO_EVENT_CALLBACK,
                                WDF_NO_EVENT_CALLBACK,
                                WDF_NO_EVENT_CALLBACK // No cleanup callback function
                                );
    
    ...
    
    WdfDeviceInitSetFileObjectConfig(WdfDeviceInit,
                                        &fileConfig,
                                        WDF_NO_OBJECT_ATTRIBUTES);
    
    ...
    
    // Do additional setup required for USB controllers.
    
    status = UdecxInitializeWdfDeviceInit(WdfDeviceInit);
    
    ...
    
    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&wdfDeviceAttributes, WDFDEVICE_CONTEXT);
    wdfDeviceAttributes.EvtCleanupCallback = _ControllerWdfEvtCleanupCallback;
    
    // Call WdfDeviceCreate with a few extra compatibility steps to ensure this device looks
    // exactly like other USB host controllers.
    
    isCreated = FALSE;
    
    for (instanceNumber = 0; instanceNumber < ULONG_MAX; instanceNumber++) {
    
        status = RtlUnicodeStringPrintf(&uniDeviceName,
                                        L"%ws%d",
                                        BASE_DEVICE_NAME,
                                        instanceNumber);
    
        ...
    
        status = WdfDeviceInitAssignName(*WdfDeviceInit, &uniDeviceName);
    
        ...
    
        status = WdfDeviceCreate(WdfDeviceInit, WdfDeviceAttributes, WdfDevice);
    
        if (status == STATUS_OBJECT_NAME_COLLISION) {
    
            // This is expected to happen at least once when another USB host controller
            // already exists on the system.
    
        ...
    
        } else if (!NT_SUCCESS(status)) {
    
        ...
    
        } else {
    
            isCreated = TRUE;
            break;
        }
    }
    
    if (!isCreated) {
    
        ...
    }
    
    // Create the symbolic link (also for compatibility).
    status = RtlUnicodeStringPrintf(&uniSymLinkName,
                                    L"%ws%d",
                                    BASE_SYMBOLIC_LINK_NAME,
                                    instanceNumber);
    ...
    
    status = WdfDeviceCreateSymbolicLink(*WdfDevice, &uniSymLinkName);
    
    ...
    
    // Create the device interface.
    
    RtlInitUnicodeString(&refString,
                         USB_HOST_DEVINTERFACE_REF_STRING);
    
    status = WdfDeviceCreateDeviceInterface(wdfDevice,
                                            (LPGUID)&GUID_DEVINTERFACE_USB_HOST_CONTROLLER,
                                            &refString);
    
    ...
    
    UDECX_WDF_DEVICE_CONFIG_INIT(&controllerConfig, Controller_EvtUdecxWdfDeviceQueryUsbCapability);
    
    status = UdecxWdfDeviceAddUsbDeviceEmulation(wdfDevice,
                                               &controllerConfig);
    
    // Create default queue. It only supports USB controller IOCTLs. (USB I/O will come through
    // in separate USB device queues.)
    // Shown later in this topic.
    
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&defaultQueueConfig, WdfIoQueueDispatchSequential);
    defaultQueueConfig.EvtIoDeviceControl = ControllerEvtIoDeviceControl;
    defaultQueueConfig.PowerManaged = WdfFalse;
    
    status = WdfIoQueueCreate(wdfDevice,
                              &defaultQueueConfig,
                              WDF_NO_OBJECT_ATTRIBUTES,
                              &pControllerContext->DefaultQueue);
    
    ...
    
    // Initialize virtual USB device software objects.
    // Shown later in this topic.
    
    status = Usb_Initialize(wdfDevice);
    
    ...
    
    exit:
    
        return status;
    }
    ```1.
    
    

Menangani permintaan IOCTL mode pengguna yang dikirim ke pengontrol host

Selama inisialisasi, driver klien UDE mengekspos GUID antarmuka perangkat GUID_DEVINTERFACE_USB_HOST_CONTROLLER. Ini memungkinkan driver untuk menerima permintaan IOCTL dari aplikasi yang membuka handel perangkat dengan menggunakan GUID tersebut. Untuk daftar kode kontrol IOCTL, lihat IOCTL USB dengan GUID antarmuka Perangkat: GUID_DEVINTERFACE_USB_HOST_CONTROLLER.

Untuk menangani permintaan tersebut, driver klien mendaftarkan panggilan balik peristiwa EvtIoDeviceControl . Dalam implementasi, alih-alih menangani permintaan, driver dapat memilih untuk meneruskan permintaan ke ekstensi kelas UDE untuk diproses. Untuk meneruskan permintaan, driver harus memanggil UdecxWdfDeviceTryHandleUserIoctl. Jika kode kontrol IOCTL yang diterima sesuai dengan permintaan standar, seperti mengambil deskriptor perangkat, ekstensi kelas memproses dan menyelesaikan permintaan dengan sukses. Dalam hal ini, UdecxWdfDeviceTryHandleUserIoctl selesai dengan TRUE sebagai nilai yang dikembalikan. Jika tidak, panggilan mengembalikan FALSE dan driver harus menentukan cara menyelesaikan permintaan. Dalam implementasi yang paling sederhana, driver dapat menyelesaikan permintaan dengan kode kegagalan yang sesuai dengan memanggil WdfRequestComplete.


EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL        Controller_EvtIoDeviceControl;

VOID
Controller_EvtIoDeviceControl(
    _In_
        WDFQUEUE Queue,
    _In_
        WDFREQUEST Request,
    _In_
        size_t OutputBufferLength,
    _In_
        size_t InputBufferLength,
    _In_
        ULONG IoControlCode
)
{
    BOOLEAN handled;
    NTSTATUS status;
    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(InputBufferLength);

    handled = UdecxWdfDeviceTryHandleUserIoctl(WdfIoQueueGetDevice(Queue),
                                                Request);

    if (handled) {

        goto exit;
    }

    // Unexpected control code.
    // Fail the request.

    status = STATUS_INVALID_DEVICE_REQUEST;

    WdfRequestComplete(Request, status);

exit:

    return;
}

Melaporkan kemampuan pengontrol host

Sebelum driver lapisan atas dapat menggunakan kemampuan pengontrol host USB, driver harus menentukan apakah kemampuan tersebut didukung oleh pengontrol. Driver membuat kueri tersebut dengan memanggil WdfUsbTargetDeviceQueryUsbCapability dan USBD_QueryUsbCapability. Panggilan tersebut diteruskan ke ekstensi kelas Emulasi Perangkat USB (UDE). Setelah mendapatkan permintaan, ekstensi kelas memanggil implementasi EVT_UDECX_WDF_DEVICE_QUERY_USB_CAPABILITY driver klien. Panggilan ini dilakukan hanya setelah EvtDriverDeviceAdd selesai, biasanya di EvtDevicePrepareHardware dan bukan setelah EvtDeviceReleaseHardware. Fungsi panggilan balik diperlukan.

Dalam implementasi, driver klien harus melaporkan apakah mendukung kemampuan yang diminta. Kemampuan tertentu tidak didukung oleh UDE seperti aliran statis.

NTSTATUS
Controller_EvtControllerQueryUsbCapability(
    WDFDEVICE     UdeWdfDevice,
    PGUID         CapabilityType,
    ULONG         OutputBufferLength,
    PVOID         OutputBuffer,
    PULONG        ResultLength
)

{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(UdeWdfDevice);
    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(OutputBuffer);

    *ResultLength = 0;

    if (RtlCompareMemory(CapabilityType,
                         &GUID_USB_CAPABILITY_CHAINED_MDLS,
                         sizeof(GUID)) == sizeof(GUID)) {

        //
        // TODO: Is GUID_USB_CAPABILITY_CHAINED_MDLS supported?
        // If supported, status = STATUS_SUCCESS
        // Otherwise, status = STATUS_NOT_SUPPORTED
    }

    else {

        status = STATUS_NOT_IMPLEMENTED;
    }

    return status;
}

Membuat perangkat USB virtual

Perangkat USB virtual ber perilaku yang mirip dengan perangkat USB. Ini mendukung konfigurasi dengan beberapa antarmuka dan setiap antarmuka mendukung pengaturan alternatif. Setiap pengaturan dapat memiliki satu titik akhir lagi yang digunakan untuk transfer data. Semua deskriptor (perangkat, konfigurasi, antarmuka, titik akhir) diatur oleh driver klien UDE sehingga perangkat dapat melaporkan informasi seperti perangkat USB nyata.

Catatan

Driver klien UDE tidak mendukung hub eksternal

Berikut adalah ringkasan urutan di mana driver klien membuat handel UDECXUSBDEVICE untuk objek perangkat UDE. Driver harus melakukan langkah-langkah ini setelah mengambil handel WDFDEVICE untuk pengontrol host yang ditiru. Kami menyarankan agar driver melakukan tugas-tugas ini dalam fungsi panggilan balik EvtDriverDeviceAdd .

  1. Panggil UdecxUsbDeviceInitAllocate untuk mendapatkan pointer ke parameter inisialisasi yang diperlukan untuk membuat perangkat. Struktur ini dialokasikan oleh ekstensi kelas UDE.

  2. Daftarkan fungsi panggilan balik peristiwa dengan mengatur anggota UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS lalu memanggil UdecxUsbDeviceInitSetStateChangeCallbacks. Berikut adalah fungsi panggilan balik yang terkait dengan objek perangkat UDE, yang dipanggil oleh ekstensi kelas UDE.

    Fungsi-fungsi ini diimplementasikan oleh driver klien untuk membuat atau mengonfigurasi titik akhir.

  3. Panggil UdecxUsbDeviceInitSetSpeed untuk mengatur kecepatan perangkat USB dan juga jenis perangkat, USB 2.0, atau perangkat SuperSpeed.

  4. Panggil UdecxUsbDeviceInitSetEndpointsType untuk menentukan jenis titik akhir yang didukung perangkat: sederhana atau dinamis. Jika driver klien memilih untuk membuat titik akhir sederhana, driver harus membuat semua objek titik akhir sebelum menyambungkan ke perangkat. Perangkat hanya boleh memiliki satu konfigurasi dan hanya satu pengaturan antarmuka per antarmuka. Dalam kasus titik akhir dinamis, driver dapat membuat titik akhir kapan saja setelah menyambungkan ke perangkat saat menerima panggilan balik peristiwa EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE . Lihat Membuat titik akhir dinamis.

  5. Panggil salah satu metode ini untuk menambahkan deskriptor yang diperlukan ke perangkat.

    • UdecxUsbDeviceInitAddDescriptor

    • UdecxUsbDeviceInitAddDescriptorWithIndex

    • UdecxUsbDeviceInitAddStringDescriptor

    • UdecxUsbDeviceInitAddStringDescriptorRaw

      Jika ekstensi kelas UDE menerima permintaan deskriptor standar yang telah disediakan driver klien selama inisialisasi dengan menggunakan salah satu metode sebelumnya, ekstensi kelas secara otomatis menyelesaikan permintaan. Ekstensi kelas tidak meneruskan permintaan tersebut ke driver klien. Desain ini mengurangi jumlah permintaan yang perlu diproses driver untuk permintaan kontrol. Selain itu, ini juga menghilangkan kebutuhan driver untuk menerapkan logika deskriptor yang memerlukan penguraian ekstensif paket penyiapan dan menangani wLength dan TransferBufferLength dengan benar. Daftar ini mencakup permintaan standar. Driver klien tidak perlu memeriksa permintaan ini (hanya jika metode sebelumnya dipanggil untuk menambahkan deskriptor):

    • USB_REQUEST_GET_DESCRIPTOR

    • USB_REQUEST_SET_CONFIGURATION

    • USB_REQUEST_SET_INTERFACE

    • USB_REQUEST_SET_ADDRESS

    • USB_REQUEST_SET_FEATURE

    • USB_FEATURE_FUNCTION_SUSPEND

    • USB_FEATURE_REMOTE_WAKEUP

    • USB_REQUEST_CLEAR_FEATURE

    • USB_FEATURE_ENDPOINT_STALL

    • USB_REQUEST_SET_SEL

    • USB_REQUEST_ISOCH_DELAY

      Namun, permintaan untuk antarmuka, khusus kelas, atau deskriptor yang ditentukan vendor, ekstensi kelas UDE meneruskannya ke driver klien. Driver harus menangani permintaan GET_DESCRIPTOR tersebut.

  6. Panggil UdecxUsbDeviceCreate untuk membuat objek perangkat UDE dan mengambil handel UDECXUSBDEVICE.

  7. Buat titik akhir statis dengan memanggil UdecxUsbEndpointCreate. Lihat Membuat titik akhir sederhana.

  8. Panggil UdecxUsbDevicePlugIn untuk menunjukkan ke ekstensi kelas UDE bahwa perangkat terpasang dan dapat menerima permintaan I/O pada titik akhir. Setelah panggilan ini, ekstensi kelas juga dapat memanggil fungsi panggilan balik pada titik akhir dan perangkat USB. Catatan Jika perangkat USB perlu dihapus saat runtime, driver klien dapat memanggil UdecxUsbDevicePlugOutAndDelete. Jika driver ingin menggunakan perangkat, driver harus membuatnya dengan memanggil UdecxUsbDeviceCreate.

Dalam contoh ini, deklarasi deskriptor diasumsikan sebagai variabel global, dinyatakan seperti yang ditunjukkan di sini untuk perangkat HID hanya sebagai contoh:

const UCHAR g_UsbDeviceDescriptor[] = {
    // Device Descriptor
    0x12, // Descriptor Size
    0x01, // Device Descriptor Type
    0x00, 0x03, // USB 3.0
    0x00, // Device class
    0x00, // Device sub-class
    0x00, // Device protocol
    0x09, // Maxpacket size for EP0 : 2^9
    0x5E, 0x04, // Vendor ID
    0x39, 0x00, // Product ID
    0x00, // LSB of firmware version
    0x03, // MSB of firmware version
    0x01, // Manufacture string index
    0x03, // Product string index
    0x00, // Serial number string index
    0x01 // Number of configurations
};

Berikut adalah contoh di mana driver klien menentukan parameter inisialisasi dengan mendaftarkan fungsi panggilan balik, mengatur kecepatan perangkat, menunjukkan jenis titik akhir, dan akhirnya mengatur beberapa deskriptor perangkat.


NTSTATUS
Usb_Initialize(
    _In_
        WDFDEVICE WdfDevice
    )
{
    NTSTATUS                                status;
    PUSB_CONTEXT                            usbContext;    //Client driver declared context for the host controller object
    PUDECX_USBDEVICE_CONTEXT                deviceContext; //Client driver declared context for the UDE device object
    UDECX_USB_DEVICE_STATE_CHANGE_CALLBACKS callbacks;
    WDF_OBJECT_ATTRIBUTES                   attributes;

    UDECX_USB_DEVICE_PLUG_IN_OPTIONS        pluginOptions;

    usbContext = WdfDeviceGetUsbContext(WdfDevice);

    usbContext->UdecxUsbDeviceInit = UdecxUsbDeviceInitAllocate(WdfDevice);

    if (usbContext->UdecxUsbDeviceInit == NULL) {

        ...
        goto exit;
    }

    // State changed callbacks

    UDECX_USB_DEVICE_CALLBACKS_INIT(&callbacks);
#ifndef SIMPLEENDPOINTS
    callbacks.EvtUsbDeviceDefaultEndpointAdd = UsbDevice_EvtUsbDeviceDefaultEndpointAdd;
    callbacks.EvtUsbDeviceEndpointAdd = UsbDevice_EvtUsbDeviceEndpointAdd;
    callbacks.EvtUsbDeviceEndpointsConfigure = UsbDevice_EvtUsbDeviceEndpointsConfigure;
#endif
    callbacks.EvtUsbDeviceLinkPowerEntry = UsbDevice_EvtUsbDeviceLinkPowerEntry;
    callbacks.EvtUsbDeviceLinkPowerExit = UsbDevice_EvtUsbDeviceLinkPowerExit;
    callbacks.EvtUsbDeviceSetFunctionSuspendAndWake = UsbDevice_EvtUsbDeviceSetFunctionSuspendAndWake;

    UdecxUsbDeviceInitSetStateChangeCallbacks(usbContext->UdecxUsbDeviceInit, &callbacks);

    // Set required attributes.

    UdecxUsbDeviceInitSetSpeed(usbContext->UdecxUsbDeviceInit, UdecxUsbLowSpeed);

#ifdef SIMPLEENDPOINTS
    UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeSimple);
#else
    UdecxUsbDeviceInitSetEndpointsType(usbContext->UdecxUsbDeviceInit, UdecxEndpointTypeDynamic);
#endif

    // Add device descriptor
    //
    status = UdecxUsbDeviceInitAddDescriptor(usbContext->UdecxUsbDeviceInit,
                                           (PUCHAR)g_UsbDeviceDescriptor,
                                           sizeof(g_UsbDeviceDescriptor));

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#ifdef USB30

    // Add BOS descriptor for a SuperSpeed device

    status = UdecxUsbDeviceInitAddDescriptor(pUsbContext->UdecxUsbDeviceInit,
                                           (PUCHAR)g_UsbBOSDescriptor,
                                           sizeof(g_UsbBOSDescriptor));

    if (!NT_SUCCESS(status)) {

        goto exit;
    }
#endif

    // String descriptors

    status = UdecxUsbDeviceInitAddDescriptorWithIndex(usbContext->UdecxUsbDeviceInit,
                                                    (PUCHAR)g_LanguageDescriptor,
                                                    sizeof(g_LanguageDescriptor),
                                                    0);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    status = UdecxUsbDeviceInitAddStringDescriptor(usbContext->UdecxUsbDeviceInit,
                                                 &g_ManufacturerStringEnUs,
                                                 g_ManufacturerIndex,
                                                 US_ENGLISH);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, UDECX_USBDEVICE_CONTEXT);

    status = UdecxUsbDeviceCreate(&usbContext->UdecxUsbDeviceInit,
                                &attributes,
                                &usbContext->UdecxUsbDevice);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#ifdef SIMPLEENDPOINTS
   // Create the default control endpoint
   // Shown later in this topic.

    status = UsbCreateControlEndpoint(WdfDevice);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

#endif

    UDECX_USB_DEVICE_PLUG_IN_OPTIONS_INIT(&pluginOptions);
#ifdef USB30
    pluginOptions.Usb30PortNumber = 2;
#else
    pluginOptions.Usb20PortNumber = 1;
#endif
    status = UdecxUsbDevicePlugIn(usbContext->UdecxUsbDevice, &pluginOptions);

exit:

    if (!NT_SUCCESS(status)) {

        UdecxUsbDeviceInitFree(usbContext->UdecxUsbDeviceInit);
        usbContext->UdecxUsbDeviceInit = NULL;

    }

    return status;
}

Manajemen daya perangkat USB

Ekstensi kelas UDE memanggil fungsi panggilan balik driver klien ketika menerima permintaan untuk mengirim perangkat ke status daya rendah atau mengembalikannya ke status kerja. Fungsi panggilan balik ini diperlukan untuk perangkat USB yang mendukung bangun. Driver klien mendaftarkan implementasinya dengan dalam panggilan sebelumnya ke UdecxUsbDeviceInitSetStateChangeCallbacks.

Untuk informasi selengkapnya, lihat Status Daya Perangkat USB.

Perangkat USB 3.0 memungkinkan fungsi individual untuk memasuki status daya yang lebih rendah. Setiap fungsi juga mampu mengirim sinyal bangun. Ekstensi kelas UDE memberi tahu driver klien dengan memanggil EVT_UDECX_USB_DEVICE_SET_FUNCTION_SUSPEND_AND_WAKE. Kejadian ini menunjukkan perubahan status daya fungsi dan menginformasikan driver klien tentang apakah fungsi dapat bangun dari status baru. Dalam fungsi , ekstensi kelas meneruskan nomor antarmuka fungsi yang bangun.

Driver klien dapat mensimulasikan tindakan perangkat USB virtual yang memulai bangun sendiri dari status daya tautan rendah, fungsi ditangguhkan, atau keduanya. Untuk perangkat USB 2.0, driver harus memanggil UdecxUsbDeviceSignalWake, jika driver diaktifkan bangun pada perangkat di EVT_UDECX_USB_DEVICE_D0_EXIT terbaru. Untuk perangkat USB 3.0, driver harus memanggil UdecxUsbDeviceSignalFunctionWake karena fitur usb 3.0 wake adalah per fungsi. Jika seluruh perangkat dalam status daya rendah, atau memasuki status seperti itu, UdecxUsbDeviceSignalFunctionWake membangunkan perangkat.

Membuat titik akhir sederhana

Driver klien membuat objek titik akhir UDE untuk menangani transfer data ke dan dari perangkat USB. Driver membuat titik akhir sederhana setelah membuat perangkat UDE dan sebelum melaporkan perangkat sebagai dicolokkan.

Berikut adalah ringkasan urutan di mana driver klien membuat handel UDECXUSBENDPOINT untuk objek titik akhir UDE. Driver harus melakukan langkah-langkah ini setelah mengambil handel UDECXUSBDEVICE untuk perangkat USB virtual. Kami menyarankan agar driver melakukan tugas-tugas ini dalam fungsi panggilan balik EvtDriverDeviceAdd .

  1. Panggil UdecxUsbSimpleEndpointInitAllocate untuk mendapatkan pointer ke parameter inisialisasi yang dialokasikan oleh ekstensi kelas.

  2. Panggil UdecxUsbEndpointInitSetEndpointAddress untuk mengatur alamat titik akhir dalam parameter inisialisasi.

  3. Panggil UdecxUsbEndpointInitSetCallbacks untuk mendaftarkan fungsi panggilan balik yang diterapkan driver klien.

    Fungsi-fungsi ini diimplementasikan oleh driver klien untuk menangani antrean dan permintaan pada titik akhir.

  4. Panggil UdecxUsbEndpointCreate untuk membuat objek titik akhir dan mengambil handel UDECXUSBENDPOINT.

  5. Panggil UdecxUsbEndpointSetWdfIoQueue untuk mengaitkan objek antrean kerangka kerja dengan titik akhir. Jika berlaku, ini dapat mengatur objek titik akhir menjadi objek induk WDF dari antrean dengan mengatur atribut yang sesuai.

    Setiap objek titik akhir memiliki objek antrean kerangka kerja untuk menangani permintaan transfer. Untuk setiap permintaan transfer yang diterima ekstensi kelas, ekstensi tersebut mengantrekan objek permintaan kerangka kerja. Status antrean (dimulai, dihapus menyeluruh) dikelola oleh ekstensi kelas UDE dan driver klien tidak boleh mengubah status tersebut. Setiap objek permintaan berisi Blok Permintaan USB (URB) yang berisi detail transfer.

Dalam contoh ini, driver klien membuat titik akhir kontrol default.

EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL IoEvtControlUrb;
EVT_UDECX_USB_ENDPOINT_RESET UsbEndpointReset;
EVT_UDECX_USB_ENDPOINT_PURGE UsEndpointEvtPurge;
EVT_UDECX_USB_ENDPOINT_START UsbEndpointEvtStart;

NTSTATUS
UsbCreateControlEndpoint(
    _In_
        WDFDEVICE WdfDevice
    )
{
    NTSTATUS                      status;
    PUSB_CONTEXT                  pUsbContext;
    WDF_IO_QUEUE_CONFIG           queueConfig;
    WDFQUEUE                      controlQueue;
    UDECX_USB_ENDPOINT_CALLBACKS  callbacks;
    PUDECXUSBENDPOINT_INIT        endpointInit;

    pUsbContext = WdfDeviceGetUsbContext(WdfDevice);
    endpointInit = NULL;

    WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);

    queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;

    status = WdfIoQueueCreate (Device,
                               &queueConfig,
                               WDF_NO_OBJECT_ATTRIBUTES,
                               &controlQueue);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    endpointInit = UdecxUsbSimpleEndpointInitAllocate(pUsbContext->UdecxUsbDevice);

    if (endpointInit == NULL) {

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto exit;
    }

    UdecxUsbEndpointInitSetEndpointAddress(endpointInit, USB_DEFAULT_ENDPOINT_ADDRESS);

    UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
    UdecxUsbEndpointInitSetCallbacks(endpointInit, &callbacks);

    callbacks.EvtUsbEndpointStart = UsbEndpointEvtStart;
    callbacks.EvtUsbEndpointPurge = UsEndpointEvtPurge;

    status = UdecxUsbEndpointCreate(&endpointInit,
        WDF_NO_OBJECT_ATTRIBUTES,
        &pUsbContext->UdecxUsbControlEndpoint);

    if (!NT_SUCCESS(status)) {
        goto exit;
    }

    UdecxUsbEndpointSetWdfIoQueue(pUsbContext->UdecxUsbControlEndpoint,
        controlQueue);

exit:

    if (endpointInit != NULL) {

        NT_ASSERT(!NT_SUCCESS(status));
        UdecxUsbEndpointInitFree(endpointInit);
        endpointInit = NULL;
    }

    return status;
}

Membuat titik akhir dinamis

Driver klien dapat membuat titik akhir dinamis atas permintaan ekstensi kelas UDE (atas nama driver hub dan driver klien). Ekstensi kelas membuat permintaan dengan memanggil salah satu fungsi panggilan balik ini:

*EVT_UDECX_USB_DEVICE_DEFAULT_ENDPOINT_ADD Driver klien membuat titik akhir kontrol default (Titik akhir 0)

*EVT_UDECX_USB_DEVICE_ENDPOINT_ADD Driver klien membuat titik akhir dinamis.

*EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE Driver klien mengubah konfigurasi dengan memilih pengaturan alternatif, menonaktifkan titik akhir saat ini, atau menambahkan titik akhir dinamis.

Driver klien mendaftarkan panggilan balik sebelumnya selama panggilannya ke UdecxUsbDeviceInitSetStateChangeCallbacks. Lihat Membuat perangkat USB virtual. Mekanisme ini memungkinkan driver klien untuk secara dinamis mengubah konfigurasi USB dan pengaturan antarmuka pada perangkat. Misalnya, ketika objek titik akhir diperlukan atau objek titik akhir yang ada harus dirilis, ekstensi kelas memanggil EVT_UDECX_USB_DEVICE_ENDPOINTS_CONFIGURE.

Berikut adalah ringkasan urutan di mana driver klien membuat handel UDECXUSBENDPOINT untuk objek titik akhir dalam implementasi fungsi panggilan baliknya.

  1. Panggil UdecxUsbEndpointInitSetEndpointAddress untuk mengatur alamat titik akhir dalam parameter inisialisasi.

  2. Panggil UdecxUsbEndpointInitSetCallbacks untuk mendaftarkan fungsi panggilan balik yang diterapkan driver klien. Mirip dengan titik akhir sederhana, driver dapat mendaftarkan fungsi panggilan balik ini:

  3. Panggil UdecxUsbEndpointCreate untuk membuat objek titik akhir dan mengambil handel UDECXUSBENDPOINT.

  4. Panggil UdecxUsbEndpointSetWdfIoQueue untuk mengaitkan objek antrean kerangka kerja dengan titik akhir.

Dalam contoh implementasi ini, driver klien membuat titik akhir kontrol default dinamis.

NTSTATUS
UsbDevice_EvtUsbDeviceDefaultEndpointAdd(
    _In_
        UDECXUSBDEVICE            UdecxUsbDevice,
    _In_
        PUDECXUSBENDPOINT_INIT    UdecxUsbEndpointInit
)
{
    NTSTATUS                    status;
    PUDECX_USBDEVICE_CONTEXT    deviceContext;
    WDFQUEUE                    controlQueue;
    WDF_IO_QUEUE_CONFIG         queueConfig;
    UDECX_USB_ENDPOINT_CALLBACKS  callbacks;

    deviceContext = UdecxDeviceGetContext(UdecxUsbDevice);

    WDF_IO_QUEUE_CONFIG_INIT(&queueConfig, WdfIoQueueDispatchSequential);

    queueConfig.EvtIoInternalDeviceControl = IoEvtControlUrb;

    status = WdfIoQueueCreate (deviceContext->WdfDevice,
                               &queueConfig,
                               WDF_NO_OBJECT_ATTRIBUTES,
                               &controlQueue);

    if (!NT_SUCCESS(status)) {

        goto exit;
    }

    UdecxUsbEndpointInitSetEndpointAddress(UdecxUsbEndpointInit, USB_DEFAULT_DEVICE_ADDRESS);

    UDECX_USB_ENDPOINT_CALLBACKS_INIT(&callbacks, UsbEndpointReset);
    UdecxUsbEndpointInitSetCallbacks(UdecxUsbEndpointInit, &callbacks);

    status = UdecxUsbEndpointCreate(UdecxUsbEndpointInit,
        WDF_NO_OBJECT_ATTRIBUTES,
        &deviceContext->UdecxUsbControlEndpoint);

    if (!NT_SUCCESS(status)) {
        goto exit;
    }

    UdecxUsbEndpointSetWdfIoQueue(deviceContext->UdecxUsbControlEndpoint,
        controlQueue);

exit:

    return status;
}

Melakukan pemulihan kesalahan dengan mengatur ulang titik akhir

Terkadang, transfer data dapat gagal karena berbagai alasan, seperti kondisi kios di titik akhir. Dalam kasus transfer yang gagal, titik akhir tidak dapat memproses permintaan sampai kondisi kesalahan dibersihkan. Ketika ekstensi kelas UDE mengalami transfer data yang gagal, ekstensi ini memanggil fungsi panggilan balik EVT_UDECX_USB_ENDPOINT_RESET driver klien, yang didaftarkan driver dalam panggilan sebelumnya ke UdecxUsbEndpointInitSetCallbacks. Dalam implementasi, driver dapat memilih untuk menghapus status HALT pipa dan mengambil langkah-langkah lain yang diperlukan untuk menghapus kondisi kesalahan.

Panggilan ini asinkron. Setelah klien selesai dengan operasi reset, driver harus menyelesaikan permintaan dengan kode kegagalan yang sesuai dengan memanggil WdfRequestComplete. Panggilan itu memberi tahu ekstensi klien UDE tentang penyelesaian operasi reset dengan status.

Catatan Jika solusi kompleks diperlukan untuk pemulihan kesalahan, driver klien memiliki opsi untuk mengatur ulang pengontrol host. Logika ini dapat diimplementasikan dalam fungsi panggilan balik EVT_UDECX_WDF_DEVICE_RESET yang didaftarkan driver dalam panggilan UdecxWdfDeviceAddUsbDeviceEmulation . Jika berlaku, driver dapat mengatur ulang pengontrol host dan semua perangkat hilir. Jika driver klien tidak perlu mengatur ulang pengontrol tetapi mengatur ulang semua perangkat hilir, driver harus menentukan UdeWdfDeviceResetActionResetEachUsbDevice dalam parameter konfigurasi selama pendaftaran. Dalam hal ini, ekstensi kelas memanggil EVT_UDECX_WDF_DEVICE_RESET untuk setiap perangkat yang terhubung.

Menerapkan manajemen status antrean

Status objek antrean kerangka kerja yang terkait dengan objek titik akhir UDE dikelola oleh ekstensi kelas UDE. Namun, jika driver klien meneruskan permintaan dari antrean titik akhir ke antrean internal lainnya, maka klien harus menerapkan logika untuk menangani perubahan alur I/O titik akhir. Fungsi panggilan balik ini terdaftar di UdecxUsbEndpointInitSetCallbacks.

Operasi penghapusan menyeluruh titik akhir

Driver klien UDE dengan satu antrean per titik akhir dapat menerapkan EVT_UDECX_USB_ENDPOINT_PURGE seperti yang ditunjukkan dalam contoh ini:

Dalam implementasi EVT_UDECX_USB_ENDPOINT_PURGE , driver klien diperlukan untuk memastikan semua I/O yang diteruskan dari antrean titik akhir telah selesai, dan bahwa I/O yang baru diteruskan juga gagal sampai EVT_UDECX_USB_ENDPOINT_START driver klien dipanggil. Persyaratan ini dipenuhi dengan memanggil UdecxUsbEndpointPurgeComplete, yang memastikan bahwa semua I/O yang diteruskan selesai dan I/O yang diteruskan di masa mendatang gagal.

Operasi mulai titik akhir

Dalam implementasi EVT_UDECX_USB_ENDPOINT_START , driver klien diharuskan untuk mulai memproses I/O pada antrean titik akhir, dan pada antrean apa pun yang menerima I/O yang diteruskan untuk titik akhir. Setelah titik akhir dibuat, titik akhir tidak menerima I/O hingga setelah fungsi panggilan balik ini kembali. Panggilan balik ini mengembalikan titik akhir ke status pemrosesan I/O setelah EVT_UDECX_USB_ENDPOINT_PURGE selesai.

Menangani permintaan transfer data (URL)

Untuk memproses permintaan I/O USB yang dikirim ke titik akhir perangkat klien, cegat panggilan balik EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL pada objek antrean yang digunakan dengan UdecxUsbEndpointInitSetCallbacks saat mengaitkan antrean dengan titik akhir. Dalam panggilan balik tersebut, proses I/O untuk IOCTL_INTERNAL_USB_SUBMIT_URB IoControlCode (lihat kode sampel di bawah metode penanganan URB).

Metode penanganan URB

Sebagai bagian dari url pemrosesan melalui IOCTL_INTERNAL_USB_SUBMIT_URB antrean yang terkait dengan titik akhir pada perangkat virtual, driver klien UDE bisa mendapatkan penunjuk ke buffer transfer permintaan I/O dengan menggunakan metode ini:

Fungsi-fungsi ini diimplementasikan oleh driver klien untuk menangani antrean dan permintaan pada titik akhir.

UdecxUrbRetrieveControlSetupPacket Mengambil paket penyiapan kontrol USB dari objek permintaan kerangka kerja tertentu.

UdecxUrbRetrieveBuffer Mengambil buffer transfer URB dari objek permintaan kerangka kerja yang ditentukan yang dikirim ke antrean titik akhir.

UdecxUrbSetBytesCompleted Mengatur jumlah byte yang ditransfer untuk URB yang terkandung dalam objek permintaan kerangka kerja.

UdecxUrbComplete Menyelesaikan permintaan URB dengan kode status penyelesaian khusus USB.

UdecxUrbCompleteWithNtStatus Menyelesaikan permintaan URB dengan kode NTSTATUS.

Di bawah ini adalah alur pemrosesan I/O umum untuk URB transfer USB OUT.

static VOID
IoEvtSampleOutUrb(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
)
{
    PENDPOINTQUEUE_CONTEXT pEpQContext;
    NTSTATUS status = STATUS_SUCCESS;
    PUCHAR transferBuffer;
    ULONG transferBufferLength = 0;

    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(InputBufferLength);

    // one possible way to get context info
    pEpQContext = GetEndpointQueueContext(Queue);

    if (IoControlCode != IOCTL_INTERNAL_USB_SUBMIT_URB)
    {
        LogError(TRACE_DEVICE, "WdfRequest %p Incorrect IOCTL %x, %!STATUS!",
            Request, IoControlCode, status);
        status = STATUS_INVALID_PARAMETER;
        goto exit;
    }

    status = UdecxUrbRetrieveBuffer(Request, &transferBuffer, &transferBufferLength);
    if (!NT_SUCCESS(status))
    {
        LogError(TRACE_DEVICE, "WdfRequest %p unable to retrieve buffer %!STATUS!",
            Request, status);
        goto exit;
    }

    if (transferBufferLength >= 1)
    {
        //consume one byte of output data
        pEpQContext->global_storage = transferBuffer[0];
    }

exit:
    // writes never pended, always completed
    UdecxUrbSetBytesCompleted(Request, transferBufferLength);
    UdecxUrbCompleteWithNtStatus(Request, status);
    return;
}

Driver klien dapat menyelesaikan permintaan I/O secara terpisah dengan DPC. Ikuti praktik terbaik berikut:

  • Untuk memastikan kompatibilitas dengan driver USB yang ada, klien UDE harus memanggil WdfRequestComplete pada DISPATCH_LEVEL.
  • Jika URB ditambahkan ke antrean titik akhir dan driver mulai memprosesnya secara sinkron pada utas driver panggilan atau DPC, permintaan tidak boleh diselesaikan secara sinkron. DPC terpisah diperlukan untuk tujuan tersebut, yang diantrekan driver dengan memanggil WdfDpcEnqueue.
  • Ketika ekstensi kelas UDE memanggil EvtIoCanceledOnQueue atau EvtRequestCancel, driver klien harus menyelesaikan URB yang diterima pada DPC terpisah dari utas pemanggil atau DPC. Untuk melakukan ini, driver harus menyediakan panggilan balik EvtIoCanceledOnQueue untuk antrean URB-nya .