Thursday, September 20, 2007

Windows Driver Programming[8]

Synchronous and Asynchronous IRP

IRP 只有一種,就是 IRP。會有 Synchronous 跟 Asynchronous 之分,是源自於 IRP 傳送的方式。如果你寫過 Socket 程式,那你應該會知道 Asynchronous I/O 跟 Synchronous I/O 的概念。 簡言之,便是 Synchronous IRP 一傳送,Driver 就得等這個 IRP 完成才能夠繼續執行,而 Asynchronous IRP 會繼續執行下去。 這個考量在 User mode Application 當然沒問題(其實還是會有啦,不然不會有一堆 Asynchronous API 出來),只要把這些東西放到 Thread 內,然後再用一些同步機制就可以搞定。 但是這一招在 Driver 裡面是不行的,Windows Driver 裡有定義每個函式能夠執行的 IRQL,而跑在 Dispatch Level 之上的 thread,是不能夠使用 Synchronous IRP 的, 因為它們不可以睡覺,而 Synchronous IRP 會讓該 thread 去睡覺。

使用 Asynchronous IRP 也有很大的困擾:同步的問題。在 Driver 裡面同步可不比 Application mode,一不小心就 BSOD 了,麻煩。 另外,Asynchronous IRP 必須在 Driver 內部配置跟釋放,而 Synchronous IRP 則是 Driver 配置,系統(I/O manager)釋放。

在這邊以 USB 當例子。

Allocate IRP

  • Synchronous IRP
  • PIRP IoBuildDeviceIoControlRequest(
     IN ULONG IoControlCode,
     IN PDEVICE_OBJECT DeviceObject,
     IN PVOID InputBuffer OPTIONAL,
     IN ULONG InputBufferLength,
     OUT PVOID OutputBuffer OPTIONAL,
     IN ULONG OutputBufferLength,
     IN BOOLEAN InternalDeviceIoControl,
     IN PKEVENT Event,
     OUT PIO_STATUS_BLOCK IoStatusBlock
    );
    

    IoControlCode:這個 IRP 所代表的操作。如果是 USB 的話,請設定為 IOCTL_INTERNAL_USB_SUBMIT_URB。
    DeviceObject:NdisMGetDeviceProperty() 的 NextDeviceObject 參數
    InputBuffer:Device 要輸出的記憶體位置。
    OutputBuffer:要輸入到 Device 的記憶體位置。
    Event:IRP 完成/取消/錯誤之後要設定的 Event。
    IoStatusBlock:Specifies an I/O status block to be set when the request is completed by lower drivers.

    通常這樣就可以了。如果是 USB 的話,Driver 還需要先產生一個 URB:

    VOID UsbBuildInterruptOrBulkTransferRequest(
     IN OUT PURB  Urb,
     IN USHORT  Length,
     IN USBD_PIPE_HANDLE  PipeHandle,
     IN PVOID  TransferBuffer OPTIONAL,
     IN PMDL  TransferBufferMDL OPTIONAL,
     IN ULONG  TransferBufferLength,
     IN ULONG  TransferFlags,
     IN PURB  Link
    );
    

    如果 Driver 要建立的是一個 USB Bulk 或 Interrupt Transfer 的話,請呼叫 UsbBuildInterruptOrBulkTransferRequest()。
    然後再設定 IRP 的參數:

    PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(irp);
    nextStack->Parameters.Others.Argument1 = pUrb;
    

    至於為甚麼要這樣?因為當初根本沒想到會有這種東西出現吧,所以只好用很奇怪的欄位塞入 URB。
  • Asynchronous IRP


  • PIRP IoAllocateIrp(
     IN CCHAR StackSize,
     IN BOOLEAN ChargeQuota
    );
    

    StackSize:NdisMGetDeviceProperty() 的 NextDeviceObject ->StackSize + 1

    然後再設定 IRP 的參數:

    PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(irp);
    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; // Internal Device Control for USB
    nextStack->Parameters.Others.Argument1 = pUrb; // for USB
    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB; // for USB submition
    

    最後再設定 IoCompleteRoutine:

    VOID IoSetCompletionRoutine(
     IN PIRP Irp,
     IN PIO_COMPLETION_ROUTINE CompletionRoutine,
     IN PVOID Context,
     IN BOOLEAN InvokeOnSuccess,
     IN BOOLEAN InvokeOnError,
     IN BOOLEAN InvokeOnCancel
    );
    

    NTSTATUS IoCompletion(
     IN PDEVICE_OBJECT DeviceObject,
     IN PIRP Irp,
     IN PVOID Context
    );
    

Send IRP

丟出 IRP 倒是很容易:

NTSTATUS IoCallDriver(
 IN PDEVICE_OBJECT DeviceObject,
 IN OUT PIRP Irp
);

DeviceObject:NdisMGetDeviceProperty() 的 NextDeviceObject 參數
  • Synchronous IRP

  • 呼叫完 IoCallDriver() 之後,如果回傳值是 STATUS_PENDING 的話,還得呼叫 KeWaitForSingleObject() 等待 IRP 完成。

    NTSTATUS KeWaitForSingleObject(
     IN PVOID Object,
     IN KWAIT_REASON WaitReason,
     IN KPROCESSOR_MODE WaitMode,
     IN BOOLEAN Alertable,
     IN PLARGE_INTEGER Timeout OPTIONAL
    );
    

    Object:IoBuildDeviceIoControlRequest() 所傳入的 Event,也就是 Driver 所配置的 Event Object。
  • Asynchronous IRP
  • 不需額外處理。

Cancel IRP

  • Synchronous IRP/Asynchronous IRP
  • BOOLEAN IoCancelIrp(IN PIRP Irp);
    

    一般而言,Calcel IRP 的動作幾乎只會在 Asynchronous IRP 內進行,這是因為 Synchronous IRP 一定會呼叫 KeWaitForSingleObject(),並且指定其 Timeout 為 INFINITE。 但是如果 Timeout 不是 INFINITE 呢?那可能得先為這個 IRP 安裝一個 Complete Routine,如果 KeWaitForSingleObject() 失敗的話, 再呼叫 IoCancelIrp(),然後再呼叫 KeWaitForSingleObject(),等待這個 IRP 被 Cancel。

    為 Synchronous IRP 呼叫 IoCancelIrp() 的機會很少,只是我很倒楣,常常會遇到,因為硬體不穩啊,常常一個簡單的 IO command 就死掉了, 所以 KeWaitForSingleObject() 就得設 Timeout,理論上,Timeout 之後的 Synchronous IRP 是不需要去作特別處理的, 不過如果不取消它,Windows 會有機會 BSOD,所以還是乖乖呼叫 IoCancelIrp() 吧。

Release IRP

  • Asynchronous IRP
  • VOID IoFreeIrp(IN PIRP Irp);
    

    Callers of IoFreeIrp must be running at IRQL <= DISPATCH_LEVEL.
  • Synchronous IRP
  • 不需要呼叫 IoFreeIrp()。

Conclusion

IRP 不困難,困難的是 IRP 的流程。

舉例,如果你的 Driver 發了一堆 Asynchronous IRP,結果在這些 IRP 還沒完成之前,OS 要結束你的 Driver 了, 你得先確保這些 IRP 都已經被正確的完成或取消,才能呼叫 IoFreeIrp(),然後再離開。

又,如果你的 Driver 收到一堆 IRP,結果又把他們放到旁邊不管,在 Driver 結束之前,得先統統完成他們才行。

1 comment:

Unknown said...

thanks

codeblock