USBView の中身を見てみる

こんばんは、まさかた です。

 

さて、前回は、USBView の使い方についてお話させていただきましたが、そこで、「その中身については、また次の機会に!」と申し上げておりました。

ですので、今回は、USBView が、どのようにして USB デバイスを列挙して、情報を取得してきているのか?という観点から、USBView の中身についてお話したいと思います。

 

それでは、まずは USBView のソースが含まれるフォルダの中を見てみます。

 

C:\WINDDK\6001.18002\src\usb\usbview>dir

 ドライブ C のボリューム ラベルがありません。

 ボリューム シリアル番号は FEDB-4B1E です

 

 C:\WINDDK\6001.18002\src\usb\usbview のディレクトリ

 

2009/04/14 18:00 <DIR> .

2009/04/14 18:00 <DIR> ..

2008/01/03 11:12 1,846 bang.ico

2008/01/18 21:54 4,123 debug.c

2008/01/18 21:54 4,336 devnode.c

2008/01/18 21:54 33,349 dispaud.c

2008/01/18 21:54 35,286 display.c

2008/01/18 21:54 63,942 enum.c

2008/01/03 11:12 766 hub.ico

2008/01/18 21:54 1,090 makefile

2008/01/18 21:54 1,060 makefile.mk

2008/01/03 11:12 10,134 monitor.ico

2008/01/03 11:12 766 port.ico

2008/01/18 21:54 978 resource.h

2008/01/18 21:54 599 sources

2008/01/03 11:12 326 split.cur

2008/01/03 11:12 766 usb.ico

2008/01/18 21:54 9,258 usbdesc.h

2008/01/18 21:54 22,974 usbview.c

2008/01/18 21:54 7,113 usbview.h

2008/01/18 21:54 19,985 usbview.htm

2008/01/18 21:54 3,318 usbview.rc

2008/01/18 21:54 47,065 vndrlist.h

              21 個のファイル 269,080 バイト

               2 個のディレクトリ 35,974,352,896 バイトの空き領域

 

C:\WINDDK\6001.18002\src\usb\usbview>

USBView に含まれるファイル

 

フォルダの中には、ソースファイルに加えて、アイコンファイルや、リソースファイルなど、様々なファイルがありますが、今回見たいのはソースファイルだけですので、以下にソースファイルだけを抜き出して一覧にしてみました。

 

USBView のソースファイル一覧   

 

ファイル名 概要
debug.c メモリの確保と解放を監視するためのデバッグ用ルーチン
devnode.c Device Tree から、Host Controller や USB デバイスの情報を取得してくる関数、DriverNameToDeviceDesc 関数を定義
dispaud.c USB Audio の Descriptor の情報を GUI に表示するための関数を定義
display.c 一般的な USB デバイスの情報を GUI に表示するための関数を定義
enum.c USB デバイスを列挙し、情報を取得してくるための関数を定義
resource.h GUI のコントロールの ID を記述したヘッダファイル
usbdesc.h USB Audio や HID など、標準で定義されていない USB Descriptor の構造体を定義したヘッダファイル
usbview.c USBView.exe のエントリポイントと、GUI のイベントハンドラーを定義
usbview.h USBView のヘッダファイル
vndrlist.h USB のベンダーID と、対応するベンダー名のテーブルを定義

上記のように、USBView にはいくつかのソースファイルがありますが、デバイスの列挙と情報を取得する部分ということになりますと、主に enum.c を見ていけばいいかと思います。

この enum.c の中には、様々な関数が定義されており、複雑な処理を行っているように見えますが、USB デバイスの列挙とデバイスの情報を取得する流れを要約すると、以下のようになります。

 

Ø USB デバイスの列挙とデバイスの情報を取得する流れ

① システム上のHost Controller を列挙し、情報を取得

② Host Controller のRoot Hub の情報を取得

③ Hub の Port の列挙とPort 毎のデバイス情報の取得

              ※デバイスが Hub であれば、さらに③を繰り返す

④ 次の Host Controller を列挙し、①から繰り返し

 

 

 EnumUSBDevs

            Fig. USB デバイスの列挙とデバイスの情報を取得する流れ

 

さらに、enum.c に定義されている関数の呼び出しの関係と、さらにその中で行う処理が、大まかに上記の手順①~③のどれに当てはまるのかを一覧にしてみました。

ただし、これは大雑把なものですので、あくまでご参考までに。。

 

 

(*1) : [Option] – [Config Descriptors] が選択されている場合のみ

 

以下では、①~③のそれぞれの手順について、もう少し細かく見ていきたいと思います。

 

① Host Controller を列挙し、情報を取得

まず、どうやって Host Controller を列挙するのか?という所ですが、前回の A寿さんの記事 にもありましたように、Host Controller デバイスにアクセスする場合にも、まずは、CreateFile() でデバイスハンドルをオープンする必要があります。

なので、「デバイスを列挙する」とは、CreateFile() で使用するシンボリックリンクを列挙するということになります。

そして、さらに DeviceIoControl() で、必要な IOCTL コードや構造体をセットすれば、必要な情報を取得できます。

そして、最後は CloseHandle() でハンドルをクローズします。

 

(1) CreateFile() で、Host Controller デバイスのハンドルをオープン

(2) DeviceIoControl() で、I/O Control(IOCTL)を発行して、Host Controller の情報を取得

(3) CloseHandle() で、Host Controller デバイスのハンドルをクローズ

 

実際、enum.c 内の EnumerateHostControllers() の中で、上記の手順で Host Controller にアクセスしているのが分かると思います。

 

(1) CreateFile() でデバイスハンドルをオープン

まずは、デバイスのシンボリックリンクを指定して、デバイスハンドルをオープンするわけですが、USBView では、以下の 2種類 の方法で、シンボリックリンクを列挙しています。

 

A) “ \\.\HCDn ” (n = 0..10) というシンボリックを決め打ちで使用

B) SetupDi API を使って、USB Host Controller クラスのシンボリックリンクを列挙

 

上記の 2種類 のシンボリックリンクでは、結果として同じデバイスにアクセスすることになりますが、A) では、”\\.\HCD0 ~ “\\.\HCD10 という文字列を for ループで作成して、シンボリックリンクとして使っています。

他方、B) では、もうひとつの方法として、SetupDi 関数を使用して、Host Controller のシンボリックリンクを列挙していて、例えば、以下のようなシンボリックリンクの文字列が使われます。

 

"\\.\pci#ven_8086&dev_2934&subsys_02111028&rev_02#3&172e68dd&2&e8#{3abf6f2d-71c4-462a-8a92-1e6861e6af27}"

 

SetupDi API については、こちらを参照していただきたいのですが、今回、USBView の中で使われているSetupDi 関数は以下の4つあります。

 

使われている SetupDi API

用途

SetupDiGetClassDevs

Host Controller のClass GUID

(=GUID_CLASS_USB_HOST_CONTROLLER)を指定して、USB Host Controller クラスのデバイス情報セットのハンドルを取得

SetupDiEnumDeviceInterfaces

SetupDiGetClassDevs で取得したハンドルを使って、

USB Host Controller クラスの Device Interface を列挙

SetupDiGetDeviceInterfaceDetail

Device Interface の情報として、

SP_DEVICE_INTERFACE_DETAIL_DATA 構造体 を取得し、メンバの DevicePath[] からシンボリックリンクを取得

SetupDiDestroyDeviceInfoList

使い終わったデバイス情報セットを破棄

 

それじゃ、この A) と B) の違いは何なの?と思う方もいらっしゃるかと思いますが、これは、シンボリックリンクの実態を winobj で見てみれば、多少見当がつくかと思います。

winobj は、Sysintenals にて提供している GUI ツールで、シンボリックリンクを始め、Device、Event、Mutex、Semaphore など、Object Manager の名前空間上にある、様々な Object の情報を視覚的に見ることができます。

(ツールのダウンロードはこちらからできます。)

実際に、HCDn と SetupDi 関数で取得してきたシンボリックリンクの実態を見てみたのが、以下の図です。

例えば、”HCDn” は、”\Device\USBFDO-n” (n=0..6)となっており、一方、SetupDi 関数で取ってきたシンボリックリンクは、”\Device\NTPNP_PCIxxxx” となっています。

これは、同じ Host Controller へのシンボリックリンクを、Host Controller のファンクションドライバ自身で作ったものと、PCI バス側で列挙した時に作ったものとの違いで、結局は同じデバイスを指しています。

 

 Find Symbolic Link by WinObj

Winobj でシンボリックリンクを見てみよう( クリックすると拡大できます

 

(2) DeviceIoControl() で情報を取得

この部分の処理はenum.c 内の EnumerateHostController() の中で実行されています。

実際には、EnumerateHostController() の中で呼び出している GetHCDDriverKeyName() の中で、(1) でオープンしたデバイスハンドルを使い、DeviceIoControl() にて IOCTL_GET_HCD_DRIVERKEY_NAME を指定して、Host Controller の Driver Key を取得しています。

さらに、devnode.c に定義されている DriverNameToDeviceDesc() の中で、この Driver Keyを元に、目的の Host Controller デバイスを探し出し、レジストリに保存されている情報を取得してきています。

ここでは、 CM_Get_Device_ID() を使って、Host Controller の Device Instance ID を取得してきています。

その他にも、 ”CM_” のプレフィックスを持つ PnP Configuration Manager Function を使うことで、レジストリに保存されているデバイスの情報を取得していますが、ここでは詳細な説明は割愛させていただきたいと思います。

(3) CloseHandle() でデバイスのハンドルをクローズ

enum.c の 399 行目もしくは、460 行目 付近でやってます。

使い終わったハンドルはきちんとクローズしましょう。

② Root Hub の情報を取得

Root Hub の情報を取得する場合も、Host Controller にアクセスした時と同じ手順を取ります。

 

(1) CreateFile() でデバイスハンドルをオープン

まず、GetRootHubName() の中で、DeviceIoControl() を、Host Controller のデバイスハンドルを使い、 IOCTL_USB_GET_ROOT_HUB_NAME を指定して、Root Hub のシンボリックリンクを取得しています。

Root Hub のシンボリックリンクは、DeviceIoControl() で取得できる USB_ROOT_HUB_NAME 構造体 の RootHubName[] メンバから知ることができます。

この Root Hub のシンボリックを使って、CreateFile() で Root Hub のデバイスハンドルをオープンします。

 

(2) DeviceIoControl() で情報を取得

(1) で、オープンしたデバイスハンドルを使い、DeviceIoControl()IOCTL_USB_GET_NODE_INFORMATION を指定して、Hub の情報を取ってきます。

結果として、 USB_NODE_INFORMATION 構造体 が取得できるのですが、さらにこの中に USB_HUB_INFORMATION 構造体 が含まれており、さらにその中に含まれる USB_HUB_DESCRIPTOR 構造体 から、Hub がサポートする Port の数が分かりますので、この情報を元に、手順③にて Port を列挙していきます。

 

typedef struct _USB_NODE_INFORMATION { USB_HUB_NODE NodeType; union { USB_HUB_INFORMATION HubInformation; USB_MI_PARENT_INFORMATION MiParentInformation; } u;} USB_NODE_INFORMATION, *PUSB_NODE_INFORMATION;

 

typedef struct _USB_HUB_INFORMATION { USB_HUB_DESCRIPTOR HubDescriptor; BOOLEAN HubIsBusPowered;} USB_HUB_INFORMATION, *PUSB_HUB_INFORMATION;

 

typedef struct _USB_HUB_DESCRIPTOR { UCHAR bDescriptorLength; UCHAR bDescriptorType; UCHAR bNumberOfPorts; // Hub がサポートする Port の数 USHORT wHubCharacteristics; UCHAR bPowerOnToPowerGood; UCHAR bHubControlCurrent; UCHAR bRemoveAndPowerMask[64]; } USB_HUB_DESCRIPTOR, *PUSB_HUB_DESCRIPTOR;

 

(3) CloseHandle() でデバイスのハンドルをクローズ

enum.c の 676 行目あたりでやっています。

やはり使い終わったハンドルはきちんとクローズします。

 

③ Hub の Port の列挙と Port 毎のデバイス情報の取得

ここの部分の処理は、enum.c 内の EnumerateHubPorts() 内に書かれており、処理としては大きく分けて以下の 3つです。

 

(1) Port の列挙

(2) デバイスのDevice Descriptor Endpoint Descriptor の取得

(3) Interface Descriptor Configuration Descriptorの取得

※(3) は、[Option] メニューから [Config Descriptors] がON になっている時のみ

 

(1) Portの列挙

まず、Port 毎のデバイスにアクセスする方法ですが、ここでは、これまでのように、新たに CreateFile() を使ってデバイス毎にハンドルをオープンするようなことはしません。

ここで使用するデバイスハンドルは、既に手順②でオープンした、Root Hub のデバイスハンドルになります。

つまり、USB Descriptor というデバイス毎に異なる情報を取ってくる時には、接続されている個々のデバイス用のクライアントドライバからではなく、Hub ドライバから取ってくるということになります。

こうした、どんな USB デバイスでも持つべき Descriptor の取得は、個々の USB のクライアント ドライバではなく、Hub ドライバの方で引き受けているということが伺えます。

 

実際に Port を列挙するには、DeviceIoControl() で、Hub のデバイスハンドルを使い、IOCTL コードとして、 IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX を使うことで可能です。

この時、DeviceIoControl() に入力引数として与える USB_NODE_CONNECTION_INFORMATION_EX 構造体 の ConnectionIndex メンバに Index を指定する(enum.c の798 行目)ことで、各 Port ごとに IOCTL コードを発行することができます。

そして、その Hub がサポートする Port の数は、既に手順②で取得していますので、全 Port の列挙を行うことができるわけです。

 

typedef struct _USB_NODE_CONNECTION_INFORMATION_EX { ULONG ConnectionIndex; // Port を Index で指定 USB_DEVICE_DESCRIPTOR DeviceDescriptor; // Device Descriptor UCHAR CurrentConfigurationValue; UCHAR Speed; BOOLEAN DeviceIsHub; // ハブデバイスなら True USHORT DeviceAddress; ULONG NumberOfOpenPipes; // Pipe の数 (PipeList[] の配列数) USB_CONNECTION_STATUS ConnectionStatus; // デバイスの接続の状態が分かる USB_PIPE_INFO PipeList[0]; // USB_PIPE_INFO 構造体の配列} USB_NODE_CONNECTION_INFORMATION_EX,

*PUSB_NODE_CONNECTION_INFORMATION_EX;

 

実際、各 Port ごとに ConnectionIndex を変えながら、DeviceIoControl() をコールして、デバイスの接続の有無を見ています。

これも、DeviceIoControl() から返ってくる USB_NODE_CONNECTION_INFORMATION_EX 構造体 から知ることができます。

もし、デバイスが接続されている場合には、 USB_NODE_CONNECTION_INFORMATION_EX 構造体ConnectionStatus メンバが、DeviceConnected となりますので、ここから、デバイスが接続されているかどうかが判断できます。

ちなみに、DeviceIsHub メンバから、このデバイスが Hub なのかが判断でき、Hub の場合は、さらに EnumerateHub() を再帰呼び出しして、さらに、その Hub の Portに接続されているデバイスを列挙していきます。

 

(2) Device Descriptor Endpoint Descriptor の取得

デバイスが接続されているのであれば、 USB_NODE_CONNECTION_INFORMATION_EX 構造体 から、さらに以下の情報を取得しています(enum.c 800 行目付近)。

 

Ø Device Descriptor

Ø Endpoint Descriptor

Device Descriptor は、 USB_NODE_CONNECTION_INFORMATION_EX 構造体 の、DeviceDescriptor メンバから取得できます。

このメンバは、 USB_DEVICE_DESCRIPTOR 構造体 ですが、このメンバを見ると、USB 2.0 Specification で定義されている Device Descriptor そのものと同じ構造であることが分かると思います。

 

typedef struct _USB_DEVICE_DESCRIPTOR { UCHAR bLength ; UCHAR bDescriptorType ; USHORT bcdUSB ; UCHAR bDeviceClass ; UCHAR bDeviceSubClass ; UCHAR bDeviceProtocol ; UCHAR bMaxPacketSize0 ; USHORT idVendor ; USHORT idProduct ; USHORT bcdDevice ; UCHAR iManufacturer ; UCHAR iProduct ; UCHAR iSerialNumber ; UCHAR bNumConfigurations ;} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR ;

 

次に、Endpoint Descriptor ですが、これは、PipeList[] メンバから取得できます。

PipeList の実態は、 USB_PIPE_INFO 構造体 の配列ですが、この中には、さらに EndpointDescriptor という USB_ENDPOINT_DESCRIPTOR 構造体 のメンバがあり、ここから、デバイスがサポートする Endpoint Descriptor が得られるわけです。

この Endpoint Descriptor も USB 2.0 Specification に定義されているものと同じ構造となっています。

 

typedef struct _USB_PIPE_INFO { USB_ENDPOINT_DESCRIPTOR EndpointDescriptor; ULONG ScheduleOffset;USB_PIPE_INFO, *PUSB_PIPE_INFO;

 

typedef struct _USB_ENDPOINT_DESCRIPTOR { UCHAR bLength ; UCHAR bDescriptorType ; UCHAR bEndpointAddress ; UCHAR bmAttributes ; USHORT wMaxPacketSize ; UCHAR bInterval ;} USB_ENDPOINT_DESCRIPTOR, *PUSB_ENDPOINT_DESCRIPTOR ;

 

(3) Interface Descriptor 、 Configuration Descriptor の取得

もし、USBView の [Option] メニューから [Config Descriptors] が選択されている場合には、Interface Descriptor と Configuration Descriptor の取得を行っていきます。

具体的には、DeviceIoControl() にて、 IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION を使って、デバイスの Configuration Descriptor と String Descriptor を取得してきます。

Configuraion Descriptor と、Endpoint Descriptor は、DeviceIoControl() で、 IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION を指定することで、取得できます。

この IOCTL コードでは、InBuffer に、 USB_DESCRIPTOR_REQUEST 構造体 をセットしますが、そのメンバの中にある wValue を使って、Descriptor のタイプを指定します。

同時に、OutBuffer にも USB_DESCRIPTOR_REQUEST 構造体 をセットするのですが、DeviceIoControl() が成功すると、無事、所望の Descriptor が同構造体の Data メンバに格納されて返ってきます。

さらに、wValue にて、USB_CONFIGURATION_DESCRIPTOR_TYPE を指定すると、返ってくる Descriptor には、全ての Interface、Endpoint、Class-specific、Vendor-specific の Descriptor も、その後ろにくっ付いて返ってくるので、ここから Interface Descriptor を取得することができます。

 

typedef struct _USB_DESCRIPTOR_REQUEST { ULONG ConnectionIndex; struct { UCHAR bmRequest; UCHAR bRequest; USHORT wValue; USHORT wIndex; USHORT wLength; } SetupPacket; UCHAR Data[0];} USB_DESCRIPTOR_REQUEST, *PUSB_DESCRIPTOR_REQUEST;

 

wValue

On input to the IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION I/O control request, the caller should specify the type of descriptor to retrieve in the high byte of wValue and the descriptor index in the low byte. The following table lists the possible descriptor types.

Descriptor type

Meaning

USB_DEVICE_DESCRIPTOR_TYPE

Instructs the USB stack to return the device descriptor.

USB_CONFIGURATION_DESCRIPTOR_TYPE

Instructs the USB stack to return the configuration descriptor and all interface, endpoint, class-specific, and vendor-specific descriptors associated with the current configuration..

USB_STRING_DESCRIPTOR_TYPE

Instructs the USB stack to return the indicated string descriptor.

USB_INTERFACE_DESCRIPTOR_TYPE

Instructs the USB stack to return the indicated interface descriptor.

USB_ENDPOINT_DESCRIPTOR_TYPE

Instructs the USB stack to return the indicated endpoint descriptor.

 

   

ちなみに、Configuration Descriptor は、 USB_CONFIGURATION_DESCRIPTOR 構造体 で、Interface Descriptor は、 USB_INTERFACE_DESCRIPTOR 構造体 で表されます。

それぞれの構造体の中には、iConfiguration および、iInterface というメンバがありますが、これは String Descriptor 上の Index になります。

String Descriptor とは、言わば Index と、それに対応する文字列を格納したテーブルのようなもので、ここに、USB の Descriptor で使う文字列群を格納することができます。

そして、Configuration や Interface の名前を、iConfiguration や iInterface が Index として指す文字列として String Descriptor から取得してくることができます。

ですので、iConficuration や iInterface が 0 以外の場合には、String Descriptor を使う必要があると判断して、String Descriptor をデバイスから取得してきて、Index に応じた文字列を GUI に表示します。

 

typedef struct _USB_CONFIGURATION_DESCRIPTOR { UCHAR bLength ; UCHAR bDescriptorType ; USHORT wTotalLength ; UCHAR bNumInterfaces ; UCHAR bConfigurationValue; UCHAR iConfiguration ; UCHAR bmAttributes ; UCHAR MaxPower ;} USB_CONFIGURATION_DESCRIPTOR, *PUSB_CONFIGURATION_DESCRIPTOR ;

 

typedef struct _USB_INTERFACE_DESCRIPTOR { UCHAR bLength ; UCHAR bDescriptorType ; UCHAR bInterfaceNumber ; UCHAR bAlternateSetting ; UCHAR bNumEndpoints ; UCHAR bInterfaceClass ; UCHAR bInterfaceSubClass ; UCHAR bInterfaceProtocol ; UCHAR iInterface ;} USB_INTERFACE_DESCRIPTOR, *PUSB_INTERFACE_DESCRIPTOR ;

 

typedef struct _USB_STRING_DESCRIPTOR { UCHAR bLength ; UCHAR bDescriptorType ; WCHAR bString[1] ;} USB_STRING_DESCRIPTOR, *PUSB_STRING_DESCRIPTOR ;

 

というわけで、これで、ようやく USBView で表示する情報がすべて揃ったことになります。

 

以上、コード1行1行や、構造体の全メンバについてなどの細かな話までできませんでしたが、今回の内容から、ユーザーモードアプリケーションがどのようにデバイスやドライバにアクセスしているのか、少しでも皆様のご理解の一助となれば幸いです。

また、これまでの説明の中で、SetupDi API については、詳しいお話は割愛しましたが、この API は、ドライバの情報を取得してきたり、ドライバをインストールする場合など、ドライバとは関係の深い API です。

こちらについても、いずれこの Blog でお話しすることもあるかもしれません。

 

それでは。