KMDF Toaster で I/O Control を受け取る
こんにちは。JS です。今回は、KMDF でどの様に I/O Control が処理されているのかを、Toaster サンプルを使ってお見せしたいと思います。
Toaster サンプルはこちらからダウンロードできます。
Toaster Sample Driver
https://code.msdn.microsoft.com/windowshardware/Toaster-7d256224
ダウンロードした ZIP ファイルを展開すると、Toaster Sample Driver フォルダが作られます。そこから C++ フォルダにある toaster.sln を Visual Studio で開きましょう。
ちなみに、この記事で私は Visual Studio 2013 と WDK 8.1 を使用しています。
(私の場合、フォルダは C:\ に置き、C:\Toaster Sample Driver フォルダとなるようにしています)
プロジェクトが 16 個もありますが、今回主に使うのは以下の 4 つです:
1. dynambus (バスドライバ)
2. wdffeatured (ファンクションドライバ)
3. toastercls (クラスインストーラー)
4. notify (アプリケーション)
WDM ドライバがどう I/O Control を処理するかについては、A寿さんのエントリ「アプリケーションからのI/O Controlをドライバで受け取る方法」で解説されています:
(1) ドライバのIRP_MJ_DEVICE_CONTROLのコールバックで、目的のI/O Controlが入ってきたかどうかを判断する
(2) 目的のI/O Controlが入ってきていたら、そのI/O Controlに対する処理を行う
また、A寿さんの記事では WDM ドライバの I/O Control 処理について実装の説明もしています。
KMDF の場合、どういう手順で実装しているのかというと、以下の 2 つのステップになります:
(1) EvtDeviceAdd 関数でキューオブジェクトを作成し、初期化を行った後、 I/O Control 対応のコールバックルーチンを設定する
(2) 送られてきた I/O Control に合わせた処理を実装する
このように、かなりシンプルなものになっていますが、もう少し詳しく見てみましょう。
今回のエントリは、3 つのセクションに大きく分かれます。
1. 処理手順の解説
2. Toaster のインストール
3. I/O Control の確認
1. 処理手順の解説
インストールの前に、I/O Control の処理方法をソースコードを通して見てみます。
KMDF では I/O の処理をキューオブジェクトというもので管理しています。ドライバに対するリクエストがアプリから来たとき、そのリクエストはまずキューに保管されます。そして、リクエストを処理できる状態になったら、そのリクエストの種類に応じたドライバの関数を呼ぶのです。
キュー自体にも、色々なカスタマイズができます。例としては以下があります:
・一つのデバイスに対して複数のキューを割り当てる
・リクエストの種類ごとに、キューの指定をはじめとする処理設定などができる
このように、WDF のキューというものはなかなか奥が深いのですが、それについては今回は割愛させていただきます。
それでは、ソースコード内の実装を見ていきます。まず、wdffeatured のソースコード toaster.c を見てみましょう:
290 WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, 291 WdfIoQueueDispatchParallel); // EvtIoCancel 292 293 queueConfig.EvtIoRead = ToasterEvtIoRead; 294 queueConfig.EvtIoWrite = ToasterEvtIoWrite; 295 queueConfig.EvtIoDeviceControl = ToasterEvtIoDeviceControl; |
この中の queueConfig 変数は WDF_IO_QUEUE_CONFIG 構造体というもので、キューに関するデバイスの状態・設定を管理します。この queueConfig を WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE 関数で初期化し、後に必要な変更を加えます。今回の Toaster サンプルの場合、I/O Control をハンドルする EvtIoDeviceControl の関数として、ToasterEvtIoDeviceControl が設定されています。EvtIoDeviceControl については、以下の資料をご参照ください。
EvtIoDeviceControl
https://msdn.microsoft.com/ja-jp/library/ff541758(v=vs.85).aspx
そして、以下の WdfIoQueueCreate 関数によって、デバイスに対しての I/O キューを作ります。
314 __analysis_assume(queueConfig.EvtIoStop != 0); 315 status = WdfIoQueueCreate(device, 316 &queueConfig, 317 WDF_NO_OBJECT_ATTRIBUTES, 318 &queue 319 ); 320 __analysis_assume(queueConfig.EvtIoStop == 0); |
以上がキューの設定になります。次に、ToasterEvtIoDeviceControl 関数を見ましょう:
859 VOID 860 ToasterEvtIoDeviceControl( 861 IN WDFQUEUE Queue, 862 IN WDFREQUEST Request, 863 IN size_t OutputBufferLength, 864 IN size_t InputBufferLength, 865 IN ULONG IoControlCode 866 ) : : 905 switch (IoControlCode) { 906 907 case IOCTL_TOASTER_DONT_DISPLAY_IN_UI_DEVICE: : : |
第 5 引数として I/O Control コードが提供されているのが見られます。WDM の場合はまず I/O スタックから I/O Control コードを引き出す必要がある反面、KMDF ではリクエストの受け取り方等を抽象化し、I/O Control コードの処理にコーディングを集中させる作り方になっています。上では、IOCTL_TOASTER_DONT_DISPLAY_IN_UI_DEVICE という I/O Control コードに対する処理が書いてあります。
読者の方ご自身で I/O Control コードを定義したい場合、その作り方については以下の資料に記載されていますのでご参照いただければ幸いです。
Defining I/O Control Codes
https://msdn.microsoft.com/en-us/library/windows/hardware/ff543023(v=vs.85).aspx
ソースコードについての基本的な説明は以上となりますので、次に Toaster ドライバをインストールします。
2. Toaster のインストール
この記事では、Visual Studio を介してターゲット PC でのインストールやデバッグを行っています。
一般的なドライバーのインストール方法については、まさかたさんの記事「WDK 8 のドライバー開発の新機能」に記載されていますのでそちらをご参照ください。
また、ネットワークでデバッグを行う場合は「Visual Studioを使ったネットワークカーネルデバッグの方法」に書いてある手順を見て頂けたら幸いです。
・ バスドライバ (dynambus) のインストール
まずは、Toaster デバイスを接続するためのバスドライバをインストールします。
インストールの前に、以下の 2 つの設定を事前に行います。
1.ターゲットのOSに合わせたパッケージの作成
「ソリューション 'toaster'」 を右クリックし、 [構成マネージャー] を選択します。そして、ソリューション構成・プラットフォームをターゲットPCに合わせます。
例として、Windows 8.1 の 32 bit をターゲット PC として使用されている場合、以下のような設定にします。
2.インストールするドライバの選択
Package フォルダを開き、package プロジェクトを右クリックし [プロパティ] を開きます。
[ 共通プロパティ] の [参照] から、必要なもの以外のプロジェクトを参照リストから外します。
バスドライバのみをインストールしたいので、 [新しい参照の追加] ボタンを押し、上の画像にあるように dynambus 以外はすべてチェックを外してしまいましょう。
さて、これからビルド&インストールを行いますが、参照の変更を行った場合はリビルドする必要があります。なので、今回、ドライバをビルドするときは、 [ソリューションのビルド] ではなく [ソリューションのリビルド] を選択するようにしましょう。
それでは、以上の設定でインストールしてみます。確認しましょう。
インストールが成功した場合、上の画像にあるように、デバイスマネージャーに「Toaster Dynamic Bus Enumerator」というデバイスが追加されます。(画像からは見られませんが、「システム デバイス」という項目に含まれています)
・ ファンクションドライバ (wdffeatured) とアプリケーション (Notify.exe) のインストール
次に、Toaster デバイス用のドライバと、デバイスを制御するためのアプリである Notify.exe をインストールします。
まず、参照プロジェクトを決めます。package プロジェクトの参照リストに再び行き、 dynambus を外します。そして、wdffeatured と Toaster デバイスのクラスをインストールする toastcls プロジェクトの 2 つを追加します。
また、構成プロパティも設定の変更も行います。
現在、 [構成プロパティ] > [Driver Install] > [Deployment] の設定として Driver Installation Optionsの項目が Install and Verifyに設定されてると思います。今回、ファンクションドライバをルート (Root) 列挙という形でインストールしますので、それに合わせて項目をルート列挙用の Hardware ID Driver Updateに変更します。
そして、空欄に、ファンクションドライバの INF ファイルに記載されてる Toaster の HWIDを記入します:
{b85b7c50-6a01-11d2-b841-00c04fad5171}\MsToaster
Driver Installation Options のオプションの説明については、以下の資料を参照していただけたらと思います。
ドライバーパッケージ プロジェクトの展開プロパティ
https://msdn.microsoft.com/ja-JP/library/windows/hardware/hh454835(v=vs.85).aspx
後、Remove previous driver versions... のチェックは外しておきましょう。最後に入れたドライバをアンインストールする為、チェックを外してない場合、先ほどインストールしたバスドライバがアンインストールされてしまいます。
最後に、Notify.exeはドライバではないので、このままではターゲット PC にコピーされません。なので、ドライバパッケージの一部としてコピーする方法を使います。
構成プロパティの [Driver Install] > [Package Files] を選択し。 <Edit...> をクリックします。exe ファイルのパスを参照することで、パッケージの中に含まれるようになります。
以上の設定を行った後、ビルドします。ビルド後、デバイスマネージャーを見ると、以下の画像にある通り、「Toaster」という項目が追加されるのが確認できます。
ちなみに、Notify.exe は Visual Studio がターゲット PC でパッケージを保存する場所にあります。デフォルトのパスは C:\DriverTest\Driversです。
以上でインストールは終わりです。次はデバッガーを使ってドライバーの挙動を見てみましょう。
3. I/O Control の確認
始めに、ToasterEvtIoDeviceControl 関数の中にブレイクポイントを貼ってみます。
今回の例では、以下の画像にある通り、IoControlCode の switch 文の行に貼っています。
次に、デバッガーを使いたいと思いますが、その前に一つ設定をします。
先ほどチェックを外した Remove previous driver versions... の項目に、チェックを付け直します。今回は、バスドライバではなく、最後に入れたファンクションドライバが最初にアンインストールされるので、ちゃんとアップデートという形でインストールされます。
設定が終わりましたら、デバッガーを実行しましょう。
デバッガーが接続したのを確認した後、ターゲット PC で Notify.exe を管理者権限で実行しましょう。以下のようなウィンドウが出ます。
まずは仮想バスで Toaster デバイスのプラグインをシミュレートしましょう。メニューバーより [Bus] > [PlugIn] を順にクリックし、Serial Number の欄に 「1」 と入力します。デバイスマネージャーに Microsoft WDF Featured Toaster が追加されたのが確認できます。
それで、Notify を使ってこのデバイスに I/O Control コードを送りましょう。 [Function] メニューから [Hide] を選択し、先ほど入力したのと同じ 「1」 をSerial Number 欄に入力します。すると、ブレイクポイントでブレイクします。
以上の反応から、アプリから Toaster に向けてリクエストを送り、Toaster の方で無事受け取れたことがわかります。
最後に、どんなリクエストが送られたか見てみましょう。
ステップボタン で処理を進めたら、IOCTL_TOASTER_DONT_DISPLAY_IN_UI_DEVICE というコマンドが送られてきたのがわかります。
ちなみに、この I/O Controlを処理すると、対象の Toaster デバイスがデバイスマネージャーに表示されなくなります。
以上で、Toaster を使った KMDF の I/O Control の説明を終わります。
Toaster のインストール方法についても長々と説明することになりましたが、Visual Studio でどういう風にドライバをインストールし、デバッグするのかを実践する例として参考になればと思います。
機会があれば、アプリ側の I/O Control に関する動作についても説明したいと思いますので、よろしくお願いします。