Bagikan melalui


Memahami struktur kode driver klien USB (KMDF)

Dalam topik ini, Anda akan mempelajari tentang kode sumber untuk driver klien USB berbasis KMDF. Contoh kode dihasilkan oleh templat driver mode pengguna USB yang disertakan dengan Microsoft Visual Studio 2019.

Bagian ini menyediakan informasi tentang kode templat.

Untuk petunjuk tentang membuat kode templat KMDF, lihat Cara menulis driver klien USB (KMDF) pertama Anda.

Kode sumber driver

Objek driver mewakili instans driver klien setelah Windows memuat driver dalam memori. Kode sumber lengkap untuk objek driver ada di Driver.h dan Driver.c.

Driver.h

Sebelum membahas detail kode templat, mari kita lihat beberapa deklarasi dalam file header (Driver.h) yang relevan dengan pengembangan driver KMDF.

Driver.h, berisi file-file ini, termasuk dalam Windows Driver Kit (WDK).

#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>

#include "device.h"
#include "queue.h"
#include "trace.h"

File header Ntddk.h dan Wdf.h selalu disertakan untuk pengembangan driver KMDF. File header mencakup berbagai deklarasi dan definisi metode dan struktur yang Anda butuhkan untuk mengkompilasi driver KMDF.

Usb.h dan Usbdlib.h mencakup deklarasi dan definisi struktur dan rutinitas yang diperlukan oleh driver klien untuk perangkat USB.

Wdfusb.h mencakup deklarasi dan definisi struktur dan metode yang diperlukan untuk berkomunikasi dengan objek target I/O USB yang disediakan oleh kerangka kerja.

Device.h, Queue.h, dan Trace.h tidak disertakan dalam WDK. File header tersebut dihasilkan oleh templat dan dibahas nanti dalam topik ini.

Blok berikutnya di Driver.h menyediakan deklarasi jenis peran fungsi untuk rutinitas DriverEntry , dan rutinitas panggilan balik peristiwa EvtDriverDeviceAdd dan EvtCleanupCallback . Semua rutinitas ini diimplementasikan oleh pengemudi. Jenis peran membantu Pemverifikasi Driver Statis (SDV) menganalisis kode sumber driver. Untuk informasi selengkapnya tentang jenis peran, lihat Mendeklarasikan Fungsi dengan Menggunakan Jenis Peran Fungsi untuk Driver KMDF.

DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;

File implementasi, Driver.c, berisi blok kode berikut yang menggunakan alloc_text pragma untuk menentukan apakah fungsi DriverEntry dan rutinitas panggilan balik peristiwa berada dalam memori yang dapat di-pageable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif

Perhatikan bahwa DriverEntry ditandai sebagai INIT, sedangkan rutinitas panggilan balik peristiwa ditandai sebagai HALAMAN. Bagian INIT menunjukkan bahwa kode yang dapat dieksekusi untuk DriverEntry dapat di-pageable dan dibuang segera setelah driver kembali dari DriverEntry-nya. Bagian HALAMAN menunjukkan bahwa kode tidak harus tetap dalam memori fisik sepanjang waktu; ini dapat ditulis ke file halaman ketika tidak digunakan. Untuk informasi selengkapnya, lihat Mengunci Kode atau Data yang Dapat Di-Pageable.

Tak lama setelah driver Anda dimuat, Windows mengalokasikan struktur DRIVER_OBJECT yang mewakili driver Anda. Kemudian memanggil rutinitas titik masuk driver Anda, DriverEntry, dan meneruskan pointer ke struktur. Karena Windows mencari rutinitas berdasarkan nama, setiap driver harus menerapkan rutinitas bernama DriverEntry. Rutinitas melakukan tugas inisialisasi driver dan menentukan rutinitas panggilan balik peristiwa driver ke kerangka kerja.

Contoh kode berikut menunjukkan rutinitas DriverEntry yang dihasilkan oleh templat.

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
    WDF_DRIVER_CONFIG config;
    NTSTATUS status;
    WDF_OBJECT_ATTRIBUTES attributes;

    //
    // Initialize WPP Tracing
    //
    WPP_INIT_TRACING( DriverObject, RegistryPath );

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Register a cleanup callback so that we can call WPP_CLEANUP when
    // the framework driver object is deleted during driver unload.
    //
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.EvtCleanupCallback = MyUSBDriver_EvtDriverContextCleanup;

    WDF_DRIVER_CONFIG_INIT(&config,
                           MyUSBDriver_EvtDeviceAdd
                           );

    status = WdfDriverCreate(DriverObject,
                             RegistryPath,
                             &attributes,
                             &config,
                             WDF_NO_HANDLE
                             );

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
        WPP_CLEANUP(DriverObject);
        return status;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Rutinitas DriverEntry memiliki dua parameter: penunjuk ke struktur DRIVER_OBJECT yang dialokasikan oleh Windows, dan jalur registri untuk driver. Parameter RegistryPath mewakili jalur khusus driver dalam registri.

Dalam rutinitas DriverEntry , driver melakukan tugas-tugas ini:

  • Mengalokasikan sumber daya global yang diperlukan selama masa pakai driver. Misalnya, dalam kode templat, driver klien mengalokasikan sumber daya yang diperlukan untuk pelacakan perangkat lunak WPP dengan memanggil makro WPP_INIT_TRACING .

  • Mendaftarkan rutinitas panggilan balik peristiwa tertentu dengan kerangka kerja.

    Untuk mendaftarkan panggilan balik peristiwa, driver klien terlebih dahulu menentukan pointer ke implementasi rutinitas EvtDriverXxx dalam struktur WDF tertentu. Driver kemudian memanggil metode WdfDriverCreate dan memasok struktur tersebut (dibahas di langkah berikutnya).

  • Memanggil metode WdfDriverCreate dan mengambil handel ke objek driver kerangka kerja.

    Setelah driver klien memanggil WdfDriverCreate, kerangka kerja membuat objek driver kerangka kerja untuk mewakili driver klien. Ketika panggilan selesai, driver klien menerima handel WDFDRIVER dan dapat mengambil informasi tentang driver, seperti jalur registrinya, informasi versi, dan sebagainya (lihat Referensi Objek Driver WDF).

    Perhatikan bahwa objek driver kerangka kerja berbeda dari objek driver Windows yang dijelaskan oleh DRIVER_OBJECT. Kapan saja, driver klien bisa mendapatkan penunjuk ke strukturDRIVER_OBJECT Windows dengan menggunakan handel WDFDRIVER dan memanggil metode WdfGetDriver .

Setelah panggilan WdfDriverCreate , kerangka kerja bermitra dengan driver klien untuk berkomunikasi dengan Windows. Kerangka kerja bertindak sebagai lapisan abstraksi antara Windows dan driver, dan menangani sebagian besar tugas driver yang rumit. Driver klien mendaftar dengan kerangka kerja untuk peristiwa yang diminati driver. Ketika peristiwa tertentu terjadi, Windows memberi tahu kerangka kerja. Jika driver mendaftarkan panggilan balik peristiwa untuk peristiwa tertentu, kerangka kerja akan memberi tahu driver dengan memanggil panggilan balik peristiwa terdaftar. Dengan demikian, pengemudi diberi kesempatan untuk menangani acara, jika diperlukan. Jika driver tidak mendaftarkan panggilan balik peristiwanya, kerangka kerja dilanjutkan dengan penanganan default peristiwa.

Salah satu panggilan balik peristiwa yang harus didaftarkan driver adalah EvtDriverDeviceAdd. Kerangka kerja memanggil implementasi EvtDriverDeviceAdd driver saat kerangka kerja siap untuk membuat objek perangkat. Di Windows, objek perangkat adalah representasi logis dari fungsi perangkat fisik tempat driver klien dimuat (dibahas nanti dalam topik ini).

Panggilan balik peristiwa lain yang dapat didaftarkan driver adalah EvtDriverUnload, EvtCleanupCallback, dan EvtDestroyCallback.

Dalam kode templat, driver klien mendaftar untuk dua peristiwa: EvtDriverDeviceAdd dan EvtCleanupCallback. Driver menentukan penunjuk ke implementasi EvtDriverDeviceAdd dalam struktur WDF_DRIVER_CONFIG dan panggilan balik peristiwa EvtCleanupCallback dalam struktur WDF_OBJECT_ATTRIBUTES .

Ketika Windows siap untuk merilis struktur DRIVER_OBJECT dan membongkar driver, kerangka kerja melaporkan peristiwa itu ke driver klien dengan memanggil implementasi EvtCleanupCallback driver. Kerangka kerja memanggil panggilan balik itu tepat sebelum menghapus objek driver kerangka kerja. Driver klien dapat membebaskan semua sumber daya global yang dialokasikan di DriverEntry-nya. Misalnya, dalam kode templat, driver klien menghentikan pelacakan WPP yang diaktifkan di DriverEntry.

Contoh kode berikut menunjukkan implementasi panggilan balik peristiwa EvtCleanupCallback driver klien.

VOID MyUSBDriver_EvtDriverContextCleanup(
    _In_ WDFDRIVER Driver
    )
{
    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE ();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Stop WPP Tracing
    //
    WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );

}

Setelah perangkat dikenali oleh tumpukan driver USB, driver bus membuat objek perangkat fisik (PDO) untuk perangkat dan mengaitkan PDO dengan simpul perangkat. Simpul perangkat berada dalam formasi tumpukan, di mana PDO berada di bagian bawah. Setiap tumpukan harus memiliki satu PDO dan dapat memiliki objek perangkat filter (filter DO) dan objek perangkat fungsi (FDO) di atasnya. Untuk informasi selengkapnya, lihat Simpul Perangkat dan Tumpukan Perangkat.

Ilustrasi ini menunjukkan tumpukan perangkat untuk driver templat, MyUSBDriver_.sys.

tumpukan perangkat untuk driver templat.

Perhatikan tumpukan perangkat bernama "Perangkat USB Saya". Tumpukan driver USB membuat PDO untuk tumpukan perangkat. Dalam contoh, PDO dikaitkan dengan Usbhub3.sys, yang merupakan salah satu driver yang disertakan dengan tumpukan driver USB. Sebagai driver fungsi untuk perangkat, driver klien harus terlebih dahulu membuat FDO untuk perangkat dan kemudian memasangnya ke bagian atas tumpukan perangkat.

Untuk driver klien berbasis KMDF, kerangka kerja melakukan tugas tersebut atas nama driver klien. Untuk mewakili FDO untuk perangkat, kerangka kerja membuat objek perangkat kerangka kerja. Namun, driver klien dapat menentukan parameter inisialisasi tertentu yang digunakan kerangka kerja untuk mengonfigurasi objek baru. Kesempatan itu diberikan kepada driver klien ketika kerangka kerja memanggil implementasi EvtDriverDeviceAdd driver. Setelah objek dibuat dan FDO dilampirkan ke bagian atas tumpukan perangkat, kerangka kerja menyediakan driver klien dengan handel WDFDEVICE ke objek perangkat kerangka kerja. Dengan menggunakan handel ini, driver klien dapat melakukan berbagai operasi terkait perangkat.

Contoh kode berikut menunjukkan implementasi panggilan balik peristiwa EvtDriverDeviceAdd driver klien.

NTSTATUS
MyUSBDriver_EvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = MyUSBDriver_CreateDevice(DeviceInit);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Selama durasi, implementasi EvtDriverDeviceAdd menggunakan makro PAGED_CODE untuk memeriksa apakah rutinitas dipanggil di lingkungan yang sesuai untuk kode yang dapat dipaginasi. Pastikan Anda memanggil makro setelah mendeklarasikan semua variabel Anda; jika tidak, kompilasi gagal karena file sumber yang dihasilkan adalah file .c dan bukan file .cpp.

Implementasi EvtDriverDeviceAdd driver klien memanggil fungsi pembantu MyUSBDriver_CreateDevice untuk melakukan tugas yang diperlukan.

Contoh kode berikut menunjukkan fungsi pembantu MyUSBDriver_CreateDevice. MyUSBDriver_CreateDevice didefinisikan dalam Device.c.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
    WDF_OBJECT_ATTRIBUTES   deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    WDFDEVICE device;
    NTSTATUS status;

    PAGED_CODE();

    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    pnpPowerCallbacks.EvtDevicePrepareHardware = MyUSBDriver_EvtDevicePrepareHardware;
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);

    if (NT_SUCCESS(status)) {
        //
        // Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
        // inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
        // device.h header file. This function will do the type checking and return
        // the device context. If you pass a wrong object  handle
        // it will return NULL and assert if run under framework verifier mode.
        //
        deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
        deviceContext->PrivateDeviceData = 0;

        //
        // Create a device interface so that applications can find and talk
        // to us.
        //
        status = WdfDeviceCreateDeviceInterface(
            device,
            &GUID_DEVINTERFACE_MyUSBDriver_,
            NULL // ReferenceString
            );

        if (NT_SUCCESS(status)) {
            //
            // Initialize the I/O Package and any Queues
            //
            status = MyUSBDriver_QueueInitialize(device);
        }
    }

    return status;
}

EvtDriverDeviceAdd memiliki dua parameter: handel ke objek driver kerangka kerja yang dibuat dalam panggilan sebelumnya ke DriverEntry, dan penunjuk ke struktur WDFDEVICE_INIT . Kerangka kerja mengalokasikan struktur WDFDEVICE_INIT dan meneruskannya penunjuk sehingga driver klien dapat mengisi struktur dengan parameter inisialisasi untuk objek perangkat kerangka kerja yang akan dibuat.

Dalam implementasi EvtDriverDeviceAdd , driver klien harus melakukan tugas-tugas ini:

  • Panggil metode WdfDeviceCreate untuk mengambil handel WDFDEVICE ke objek perangkat baru.

    Metode WdfDeviceCreate menyebabkan kerangka kerja membuat objek perangkat kerangka kerja untuk FDO dan melampirkannya ke bagian atas tumpukan perangkat. Dalam panggilan WdfDeviceCreate , driver klien harus melakukan tugas-tugas ini:

    Komponen Windows, PnP dan manajer daya, mengirim permintaan terkait perangkat ke driver sebagai respons terhadap perubahan status PnP (seperti dimulai, dihentikan, dan dihapus) dan status daya (seperti berfungsi atau ditangguhkan). Untuk driver berbasis KMDF, kerangka kerja mencegat permintaan tersebut. Driver klien dapat mendapatkan pemberitahuan tentang permintaan dengan mendaftarkan rutinitas panggilan balik yang disebut panggilan balik peristiwa daya PnP dengan kerangka kerja, dengan menggunakan panggilan WdfDeviceCreate . Ketika komponen Windows mengirim permintaan, kerangka kerja menanganinya dan memanggil panggilan balik peristiwa daya PnP yang sesuai, jika driver klien telah mendaftar.

    Salah satu rutinitas panggilan balik peristiwa daya PnP yang harus diterapkan driver klien adalah EvtDevicePrepareHardware. Panggilan balik peristiwa tersebut dipanggil ketika manajer PnP memulai perangkat. Implementasi untuk EvtDevicePrepareHardware dibahas di bagian berikut.

    Konteks perangkat (terkadang disebut ekstensi perangkat) adalah struktur data (ditentukan oleh driver klien) untuk menyimpan informasi tentang objek perangkat tertentu. Driver klien meneruskan pointer ke konteks perangkatnya ke kerangka kerja. Kerangka kerja mengalokasikan blok memori berdasarkan ukuran struktur, dan menyimpan pointer ke lokasi memori tersebut di objek perangkat kerangka kerja. Driver klien dapat menggunakan pointer untuk mengakses dan menyimpan informasi dalam anggota konteks perangkat. Untuk informasi selengkapnya tentang konteks perangkat, lihat Ruang Konteks Objek Kerangka Kerja.

    Setelah panggilan WdfDeviceCreate selesai, driver klien menerima handel ke objek perangkat kerangka kerja baru, yang menyimpan pointer ke blok memori yang dialokasikan oleh kerangka kerja untuk konteks perangkat. Driver klien sekarang bisa mendapatkan penunjuk ke konteks perangkat dengan memanggil makro WdfObjectGet_DEVICE_CONTEXT .

  • Daftarkan GUID antarmuka perangkat untuk driver klien dengan memanggil metode WdfDeviceCreateDeviceInterface . Aplikasi dapat berkomunikasi dengan driver dengan menggunakan GUID ini. Konstanta GUID dideklarasikan di header, public.h.

  • Siapkan antrean untuk transfer I/O ke perangkat. Kode templat mendefinisikan MyUSBDriver_QueueInitialize, rutinitas pembantu untuk menyiapkan antrean, yang dibahas di bagian Kode sumber antrean .

Kode sumber perangkat

Objek perangkat mewakili instans perangkat tempat driver klien dimuat dalam memori. Kode sumber lengkap untuk objek perangkat ada di Device.h dan Device.c.

Device.h

File header Device.h mencakup public.h, yang berisi deklarasi umum yang digunakan oleh semua file dalam proyek.

Blok berikutnya di Device.h mendeklarasikan konteks perangkat untuk driver klien.

typedef struct _DEVICE_CONTEXT
{
    WDFUSBDEVICE UsbDevice;
    ULONG PrivateDeviceData;  // just a placeholder

} DEVICE_CONTEXT, *PDEVICE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)

Struktur DEVICE_CONTEXT ditentukan oleh driver klien dan menyimpan informasi tentang objek perangkat kerangka kerja. Ini dinyatakan dalam Device.h dan berisi dua anggota: handel ke objek perangkat target USB kerangka kerja (dibahas nanti) dan tempat penampung. Struktur ini akan diperluas dalam latihan nanti.

Device.h juga menyertakan makro WDF_DECLARE_CONTEXT_TYPE , yang menghasilkan fungsi sebaris, WdfObjectGet_DEVICE_CONTEXT. Driver klien dapat memanggil fungsi tersebut untuk mengambil pointer ke blok memori dari objek perangkat kerangka kerja.

Baris kode berikut menyatakan MyUSBDriver_CreateDevice, fungsi pembantu yang mengambil handel WDFUSBDEVICE ke objek perangkat target USB.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    );

USBCreate mengambil pointer ke struktur WDFDEVICE_INIT sebagai parameternya. Ini adalah penunjuk yang sama yang diteruskan oleh kerangka kerja ketika memanggil implementasi EvtDriverDeviceAdd driver klien. Pada dasarnya, MyUSBDriver_CreateDevice melakukan tugas EvtDriverDeviceAdd. Kode sumber untuk implementasi EvtDriverDeviceAdd dibahas di bagian sebelumnya.

Baris berikutnya di Device.h mendeklarasikan deklarasi jenis peran fungsi untuk rutinitas panggilan balik peristiwa EvtDevicePrepareHardware . Panggilan balik peristiwa diimplementasikan oleh driver klien dan melakukan tugas seperti mengonfigurasi perangkat USB.

EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;

Device.c

File implementasi Device.c berisi blok kode berikut yang menggunakan alloc_text pragma untuk menentukan bahwa implementasi driver EvtDevicePrepareHardware berada dalam memori yang dapat dipaginasi.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif

Dalam implementasi untuk EvtDevicePrepareHardware, driver klien melakukan tugas inisialisasi khusus USB. Tugas-tugas tersebut termasuk mendaftarkan driver klien, menginisialisasi objek target I/O khusus USB, dan memilih konfigurasi USB. Tabel berikut ini memperlihatkan objek target I/O khusus yang disediakan oleh kerangka kerja. Untuk informasi selengkapnya, lihat Target I/O USB.

Objek target I/O USB (handel) Dapatkan handel dengan memanggil... Deskripsi
Objek perangkat target USB (WDFUSBDEVICE) WdfUsbTargetDeviceCreateWithParameters Mewakili perangkat USB dan menyediakan metode untuk mengambil deskriptor perangkat dan mengirim permintaan kontrol ke perangkat.
Objek antarmuka target USB (WDFUSBINTERFACE ) WdfUsbTargetDeviceGetInterface Mewakili antarmuka individual dan menyediakan metode yang dapat dipanggil driver klien untuk memilih pengaturan alternatif dan mengambil informasi tentang pengaturan.
Objek pipa target USB (WDFUSBPIPE) WdfUsbInterfaceGetConfiguredPipe Mewakili pipa individual untuk titik akhir yang dikonfigurasi dalam pengaturan alternatif saat ini untuk antarmuka. Tumpukan driver USB memilih setiap antarmuka dalam konfigurasi yang dipilih dan menyiapkan saluran komunikasi ke setiap titik akhir dalam antarmuka. Dalam terminologi USB, saluran komunikasi itu dikenal sebagai pipa.

Contoh kode ini menunjukkan implementasi untuk EvtDevicePrepareHardware.

NTSTATUS
MyUSBDriver_EvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourceList,
    _In_ WDFCMRESLIST ResourceListTranslated
    )
{
    NTSTATUS status;
    PDEVICE_CONTEXT pDeviceContext;
    WDF_USB_DEVICE_CREATE_CONFIG createParams;
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;

    UNREFERENCED_PARAMETER(ResourceList);
    UNREFERENCED_PARAMETER(ResourceListTranslated);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = STATUS_SUCCESS;
    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);

    if (pDeviceContext->UsbDevice == NULL) {

        //
        // Specifying a client contract version of 602 enables us to query for
        // and use the new capabilities of the USB driver stack for Windows 8.
        // It also implies that we conform to rules mentioned in the documentation
        // documentation for WdfUsbTargetDeviceCreateWithParameters.
        //
        WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createParams,
                                         USBD_CLIENT_CONTRACT_VERSION_602
                                         );

        status = WdfUsbTargetDeviceCreateWithParameters(Device,
                                                    &createParams,
                                                    WDF_NO_OBJECT_ATTRIBUTES,
                                                    &pDeviceContext->UsbDevice
                                                    );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceCreateWithParameters failed 0x%x", status);
            return status;
        }

        //
        // Select the first configuration of the device, using the first alternate
        // setting of each interface
        //
        WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&configParams,
                                                                     0,
                                                                     NULL
                                                                     );
        status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
                                                WDF_NO_OBJECT_ATTRIBUTES,
                                                &configParams
                                                );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceSelectConfig failed 0x%x", status);
            return status;
        }
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Berikut adalah melihat lebih dekat tugas driver klien seperti yang diimplementasikan oleh kode templat:

  1. Menentukan versi kontrak driver klien dalam persiapan untuk mendaftarkan dirinya dengan tumpukan driver USB yang mendasar, yang dimuat oleh Windows.

    Windows dapat memuat tumpukan driver USB 3.0 atau USB 2.0, tergantung pada pengontrol host tempat perangkat USB terpasang. Tumpukan driver USB 3.0 baru dalam Windows 8 dan mendukung beberapa fitur baru yang ditentukan oleh spesifikasi USB 3.0, seperti kemampuan streaming. Tumpukan driver baru juga menerapkan beberapa peningkatan, seperti pelacakan dan pemrosesan Blok Permintaan USB (URB) yang lebih baik, yang tersedia melalui serangkaian rutinitas URB baru. Driver klien yang ingin menggunakan fitur tersebut atau memanggil rutinitas baru harus menentukan versi kontrak USBD_CLIENT_CONTRACT_VERSION_602. Driver klien USBD_CLIENT_CONTRACT_VERSION_602 harus mematuhi seperangkat aturan tertentu. Untuk informasi selengkapnya tentang aturan tersebut, lihat Praktik Terbaik: Menggunakan URL.

    Untuk menentukan versi kontrak, driver klien harus menginisialisasi struktur WDF_USB_DEVICE_CREATE_CONFIG dengan versi kontrak dengan memanggil makro WDF_USB_DEVICE_CREATE_CONFIG_INIT .

  2. Memanggil metode WdfUsbTargetDeviceCreateWithParameters . Metode ini memerlukan handel ke objek perangkat kerangka kerja yang diperoleh driver klien sebelumnya dengan memanggil WdfDeviceCreate dalam implementasi driver EvtDriverDeviceAdd. Metode WdfUsbTargetDeviceCreateWithParameters :

    • Mendaftarkan driver klien dengan tumpukan driver USB yang mendasar.
    • Mengambil handel WDFUSBDEVICE ke objek perangkat target USB yang dibuat oleh kerangka kerja. Kode templat menyimpan handel ke objek perangkat target USB dalam konteks perangkatnya. Dengan menggunakan handel tersebut, driver klien dapat memperoleh informasi khusus USB tentang perangkat.

    Anda harus memanggil WdfUsbTargetDeviceCreate alih-alih WdfUsbTargetDeviceCreateWithParameters jika:

    Driver tersebut tidak diperlukan untuk menentukan versi kontrak klien dan oleh karena itu harus melewati Langkah 1.

  3. Memilih konfigurasi USB.

    Dalam kode templat, driver klien memilih konfigurasi default di perangkat USB. Konfigurasi default mencakup Konfigurasi 0 perangkat dan Pengaturan Alternatif 0 dari setiap antarmuka dalam konfigurasi tersebut.

    Untuk memilih konfigurasi default, driver klien mengonfigurasi struktur WDF_USB_DEVICE_SELECT_CONFIG_PARAMS dengan memanggil fungsi WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES . Fungsi ini menginisialisasi anggota Jenis ke WdfUsbTargetDeviceSelectConfigTypeMultiInterface untuk menunjukkan bahwa jika beberapa antarmuka tersedia, maka pengaturan alternatif di setiap antarmuka tersebut harus dipilih. Karena panggilan harus memilih konfigurasi default, driver klien menentukan NULL dalam parameter SettingPairs dan 0 di parameter NumberInterfaces . Setelah selesai, anggota MultiInterface.NumberOfConfiguredInterfacesdari WDF_USB_DEVICE_SELECT_CONFIG_PARAMS menunjukkan jumlah antarmuka tempat Pengaturan Alternatif 0 dipilih. Anggota lain tidak dimodifikasi.

    Catatan Jika driver klien ingin memilih pengaturan alternatif selain pengaturan default, driver harus membuat array struktur WDF_USB_INTERFACE_SETTING_PAIR . Setiap elemen dalam array menentukan nomor antarmuka yang ditentukan perangkat dan indeks pengaturan alternatif untuk dipilih. Informasi tersebut disimpan dalam konfigurasi perangkat dan deskriptor antarmuka yang dapat diperoleh dengan memanggil metode WdfUsbTargetDeviceRetrieveConfigDescriptor . Driver klien kemudian harus memanggil WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES dan meneruskan array WDF_USB_INTERFACE_SETTING_PAIR ke kerangka kerja.

Kode sumber antrean

Objek antrean kerangka kerja mewakili antrean I/O untuk objek perangkat kerangka kerja tertentu. Kode sumber lengkap untuk objek antrean berada di Queue.h dan Queue.c.

Queue.h

Mendeklarasikan rutinitas panggilan balik peristiwa untuk peristiwa yang dinaikkan oleh objek antrean kerangka kerja.

Blok pertama di Queue.h mendeklarasikan konteks antrean.

typedef struct _QUEUE_CONTEXT {

    ULONG PrivateDeviceData;  // just a placeholder

} QUEUE_CONTEXT, *PQUEUE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)

Mirip dengan konteks perangkat, konteks antrean adalah struktur data yang ditentukan oleh klien untuk menyimpan informasi tentang antrean tertentu.

Baris kode berikutnya menyatakan fungsi MyUSBDriver_QueueInitialize, fungsi pembantu yang membuat dan menginisialisasi objek antrean kerangka kerja.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    );

Contoh kode berikutnya mendeklarasikan deklarasi jenis peran fungsi untuk rutinitas panggilan balik peristiwa EvtIoDeviceControl . Panggilan balik peristiwa diimplementasikan oleh driver klien dan dipanggil ketika kerangka kerja memproses permintaan kontrol I/O perangkat.

EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;

Queue.c

File implementasi, Queue.c, berisi blok kode berikut yang menggunakan alloc_text pragma untuk menentukan bahwa implementasi driver MyUSBDriver_QueueInitialize berada dalam memori yang dapat di-pageable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif

WDF menyediakan objek antrean kerangka kerja untuk menangani alur permintaan ke driver klien. Kerangka kerja membuat objek antrean kerangka kerja ketika driver klien memanggil metode WdfIoQueueCreate . Dalam panggilan itu, driver klien dapat menentukan opsi konfigurasi tertentu sebelum kerangka kerja membuat antrean. Opsi tersebut mencakup apakah antrean dikelola daya, memungkinkan permintaan panjang nol, atau merupakan antrean default untuk driver. Objek antrean kerangka kerja tunggal dapat menangani beberapa jenis permintaan, seperti kontrol baca, tulis, dan I/O perangkat. Driver klien dapat menentukan panggilan balik peristiwa untuk setiap permintaan tersebut.

Driver klien juga harus menentukan jenis pengiriman. Jenis pengiriman objek antrean menentukan bagaimana kerangka kerja mengirimkan permintaan ke driver klien. Mekanisme pengiriman dapat berurutan, secara paralel, atau oleh mekanisme kustom yang ditentukan oleh driver klien. Untuk antrean berurutan, permintaan tidak dikirimkan sampai driver klien menyelesaikan permintaan sebelumnya. Dalam mode pengiriman paralel, kerangka kerja meneruskan permintaan segera setelah tiba dari manajer I/O. Ini berarti driver klien dapat menerima satu permintaan saat memproses permintaan lain. Dalam mekanisme kustom, klien secara manual menarik permintaan berikutnya dari objek antrean kerangka kerja ketika driver siap untuk memprosesnya.

Biasanya, driver klien harus menyiapkan antrean di callback peristiwa EvtDriverDeviceAdd driver. Kode templat menyediakan rutinitas pembantu, MyUSBDriver_QueueInitialize, yang menginisialisasi objek antrean kerangka kerja.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    )
{
    WDFQUEUE queue;
    NTSTATUS status;
    WDF_IO_QUEUE_CONFIG    queueConfig;

    PAGED_CODE();
    
    //
    // Configure a default queue so that requests that are not
    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
    // other queues get dispatched here.
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
         &queueConfig,
        WdfIoQueueDispatchParallel
        );

    queueConfig.EvtIoDeviceControl = MyUSBDriver_EvtIoDeviceControl;

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

    if( !NT_SUCCESS(status) ) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
        return status;
    }

    return status;
}

Untuk menyiapkan antrean, driver klien melakukan tugas-tugas ini:

  1. Menentukan opsi konfigurasi antrean dalam struktur WDF_IO_QUEUE_CONFIG . Kode templat menggunakan fungsi WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE untuk menginisialisasi struktur. Fungsi menentukan objek antrean sebagai objek antrean default, dikelola daya, dan menerima permintaan secara paralel.
  2. Menambahkan panggilan balik peristiwa driver klien untuk permintaan I/O untuk antrean. Dalam templat, driver klien menentukan penunjuk ke panggilan balik peristiwanya untuk permintaan kontrol I/O perangkat.
  3. Memanggil WdfIoQueueCreate untuk mengambil handel WDFQUEUE ke objek antrean kerangka kerja yang dibuat oleh kerangka kerja.

Berikut cara kerja mekanisme antrean. Untuk berkomunikasi dengan perangkat USB, aplikasi terlebih dahulu membuka handel ke perangkat dengan memanggil rutinitas SetDixxx dan CreateHandle. Dengan menggunakan handel ini, aplikasi memanggil fungsi DeviceIoControl dengan kode kontrol tertentu. Bergantung pada jenis kode kontrol, aplikasi dapat menentukan buffer input dan output dalam panggilan tersebut. Panggilan akhirnya diterima oleh Manajer I/O, yang kemudian membuat permintaan (IRP) dan meneruskannya ke driver klien. Kerangka kerja mencegat permintaan, membuat objek permintaan kerangka kerja, dan menambahkannya ke objek antrean kerangka kerja. Dalam hal ini, karena driver klien mendaftarkan panggilan balik peristiwanya untuk permintaan kontrol I/O perangkat, kerangka kerja memanggil panggilan balik. Selain itu, karena objek antrean dibuat dengan bendera WdfIoQueueDispatchParallel, panggilan balik dipanggil segera setelah permintaan ditambahkan ke antrean.

VOID
MyUSBDriver_EvtIoDeviceControl(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, 
                TRACE_QUEUE, 
                "!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d", 
                Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode);

    WdfRequestComplete(Request, STATUS_SUCCESS);

    return;
}

Ketika kerangka kerja memanggil panggilan balik peristiwa driver klien, kerangka kerja meneruskan handel ke objek permintaan kerangka kerja yang menyimpan permintaan (dan buffer input dan outputnya) yang dikirim oleh aplikasi. Selain itu, ia mengirimkan handel ke objek antrean kerangka kerja yang berisi permintaan. Dalam panggilan balik peristiwa, driver klien memproses permintaan sesuai kebutuhan. Kode templat hanya menyelesaikan permintaan. Driver klien dapat melakukan lebih banyak tugas yang terlibat. Misalnya, jika aplikasi meminta informasi perangkat tertentu, dalam panggilan balik peristiwa, driver klien dapat membuat permintaan kontrol USB dan mengirimkannya ke tumpukan driver USB untuk mengambil informasi perangkat yang diminta. Permintaan kontrol USB dibahas dalam Transfer Kontrol USB.