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


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


 


































































  関数ツリー 処理
EnumerateHostControllers() Host Controller (HC) を列挙 
 +-EnumerateHostController()   特定の HC配下のハブを列挙 
   |-GetHCDDriverKeyName()  HCのDriver Keyを取得 
   |-DriverNameToDeviceDesc()  レジストリから、HCの情報を取得 
   |-GetRootHubName()  Root HubのSymbolic Linkを取得 
   +-EnumerateHub()  Hub配下のデバイスを列挙 
      +-EnumerateHubPorts()  Hubの各ポートに接続されたデバイスを列挙 
        |-GetDriverKeyName()  デバイスのDriver Keyを取得 
        |-DriverNameToDeviceDesc()  レジストリから、デバイスの情報を取得 
        |-GetConfigDescriptor()  Configuration Descriptorを取得(*1)
        |-AreThereStringDescriptors() String Descriptorが存在するかチェック(*1)
        |-GetAllStringDescriptors()  String Descriptorがあれば取得(*1)
        |  |-GetStringDescriptor()  Language ID Tableを取得(*1)
        |  +-GetStringDescriptors()   全言語コードのString Descriptorを取得(*1)
        |     +-GetStringDescriptor()  単一言語コードのString Descriptorを取得(*1)
        |-GetExternalHubName()   デバイスがExtHubなら、名前を取得
        +-EnumerateHub()  デバイスが、外部 Hubであれば再帰呼び出し 


 (*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 ツールで、シンボリックリンクを始め、DeviceEventMutexSemaphore など、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 DescriptorEndpoint Descriptor の取得


(3)     Interface DescriptorConfiguration 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 には、全ての InterfaceEndpointClass-specificVendor-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 で表示する情報がすべて揃ったことになります。


 


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


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


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


 


それでは。


 

Skip to main content