2015年4月18日土曜日

EZ-USB FX2LP を動かしてみる (22) ホストから見たディスクリプタ採取

EZ-USB FX2LPを動かすよりも、Linuxホストの話題に偏っているこの頃ですが、またEZ-USB弄りに戻る日はそう遠くではないはずだ...

というわけで今回もホスト側の話です.知りたいのは、、、
ホスト(libusb)は、どのようにしてUSBデバイスのディスクリプタを採取するのか?

デバイスディスクリプタ構造体の追跡
Linuxマシン上のUSBアプリのmain()が走り始めて間もなく、LinuxマシンがUSBデバイスをサーチし終えた時点で、libusbがディスクリプタをメモリ上に展開したはずだ、とヒラサカは信じています.したがって、USB初期化を終えた時点で全てのディスクリプタを読めるはず(だと思います).

ゆえに、libusbをインストールしたLinuxマシンにある、
/usr/include/usb.h
を読んで、ディスクリプタ構造体の紐付き具合を紐解いてみるところから始めます.ただしこの企ては、後に挫折するコトになるんですが...(泣)

では、上位の構造体から下位の構造体へと降りてゆきましょう.

◆このusb_bus構造体が最上位の王様だと思う.next,prevとあるのは、Linuxマシンに接続されているUSBデバイスがN台あったとしたら、Nヶのusb_busが存在するからです.usb_device という意味ありげな構造体が紐付いています.
struct usb_bus {
  struct usb_bus *next, *prev;
  char dirname[LIBUSB_PATH_MAX + 1];
  struct usb_device *devices;
  uint32_t location;
  struct usb_device *root_dev;
};

◆では下へと降りてゆきます.usb_device構造体はこうなっています.やはりnext,prevがあります.この中に、usb_device_descriptor すなわちデバイスディスクリプタが実体で入っています.これで、デバイスディスクリプタが紐付きました.さらに、usb_config_descriptorポインタで居ます.コンフィグディスクリプタにも紐付き完了.
struct usb_device {
  struct usb_device *next, *prev;
  char filename[LIBUSB_PATH_MAX + 1];
  struct usb_bus *bus;
  struct usb_device_descriptor descriptor;
  struct usb_config_descriptor *config;
  void *dev; /* Darwin support */
  uint8_t devnum;
  unsigned char num_children;
  struct usb_device **children;
};

usb_device_descriptor へと降ります.これはUSB規格で定められた通りのデバイスディスクリプタ構造体です.各フィールドの意味はUSBの本でも読んでください.下位へ降りる道は途切れています.
struct usb_device_descriptor {
uint8_t  bLength;
uint8_t  bDescriptorType;
uint16_t bcdUSB;
uint8_t  bDeviceClass;
uint8_t  bDeviceSubClass;
uint8_t  bDeviceProtocol;
uint8_t  bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t  iManufacturer;
uint8_t  iProduct;
uint8_t  iSerialNumber;
uint8_t  bNumConfigurations;
};

◆下位へ降りる道はusb_config_descriptorから続いています.ここからは混迷の度合いを強めてゆきます.前半の細字のフィールドは、USB規格のコンフィグディスクリプタと同じです.問題はその後続のフィールドです.usb_interface はインターフェースディスクリプタへの紐です.その下にextraというフィールドがありますが、これは何かというと、デバイスディスクリプタにあるbNumConfigurationsが示す「コンフィグディスクリプタがN個ある」ケースにおける、2個目以降のコンフィグディスクリプタの格納場所と推測します.なぜ推測かというと.コンフィグディスクリプタが複数あるUSBデバイスを持ってないから確証がないためです.
struct usb_config_descriptor {
uint8_t  bLength;
uint8_t  bDescriptorType;
uint16_t wTotalLength;
uint8_t  bNumInterfaces;
uint8_t  bConfigurationValue;
uint8_t  iConfiguration;
uint8_t  bmAttributes;
uint8_t  MaxPower;

struct usb_interface *interface;

unsigned char *extra; /* Extra descriptors */
int extralen;
};

◆コンフィグディスクリプタ構造体の導きによって、usb_interface へと降りてゆきましょう.
シンプルな構造体です.usb_interface_descriptor がここにあります.インターフェースディスクリプタに紐付きました.
struct usb_interface {
struct usb_interface_descriptor *altsetting;
int num_altsetting;
};

usb_interface_descriptor はこうなっていて、細字の前半部はUSB規格のインタフェースディスクリプタそのものです.その後続にusb_endpoint_descriptor があります.これでエンドポイントディスクリプタに紐付きました.ここにもextraフィールドがあって、コンフィグディスクリプタのbNumInterfacesが示すN個のインターフェースディスクリプタが存在し、それがextraフィールドに格納されていると想像されます.
struct usb_interface_descriptor {
uint8_t  bLength;
uint8_t  bDescriptorType;
uint8_t  bInterfaceNumber;
uint8_t  bAlternateSetting;
uint8_t  bNumEndpoints;
uint8_t  bInterfaceClass;
uint8_t  bInterfaceSubClass;
uint8_t  bInterfaceProtocol;
uint8_t  iInterface;

struct usb_endpoint_descriptor *endpoint;

unsigned char *extra; /* Extra descriptors */
int extralen;
};

◆最下位のディスクリプタであるエンドポイントディスクリプタusb_endpoint_descriptor はこうなっています.前半はUSB規格のエンドポイントディスクリプタそのものです.extraフィールドには、複数あるエンドポイントの情報が格納されると思われます.
struct usb_endpoint_descriptor {
uint8_t  bLength;
uint8_t  bDescriptorType;
uint8_t  bEndpointAddress;
uint8_t  bmAttributes;
uint16_t wMaxPacketSize;
uint8_t  bInterval;
uint8_t  bRefresh;
uint8_t  bSynchAddress;

unsigned char *extra; /* Extra descriptors */
int extralen;
};

ここで疑問
Q: それなりに重要と思われるストリングディスクリプタがどこにも紐付いてないけど、どこにあるの?
A: libusbがUSBデバイスをサーチした時点では、ストリングディスクリプタはメモリ上には存在しませんでした.USBデバイスをopen()して、get_string()関数でストリングをGETする必要がありました.

まぁ、これも当然と言えば当然かと思います.

Linuxマシンに10ヶぐらい接続されているUSBデバイスの全てについて、まだopen()もしてないのにストリングディスクリプタなんていう細かい情報まではメモリ上に全て展開しませんぜと、そういう事情かと思われました.

本連載16回で、同じディスクリプタを何度もホストがGETしに来るのは何故なのか、その意味を諮りかねていましたが、1)USBデバイスサーチの時  2)openした後  3)USBデバイスをコンフィグした後、のその都度GETするべき事情があるのだろうと悟りました.

ストリングをGETするには、usb_get_string() という関数を使います.この関数には何個目のストリングディスクリプタ構造体をGETするかを指定する引数があります.その引数は、デバイスディスクリプタにこの3つのフィールドがありますのでそれを使います.生産者名、製品名、製造番号、というわけです.
uint8_t  iManufacturer;
uint8_t  iProduct;
uint8_t  iSerialNumber;
#コンフィグとインターフェース構造体にもストリング構造体への指定番号が書かれています.

ここで挫折
上述の紐付きに従って、ディスクリプタを表示させるプログラムを作りましたが、上手く動きませんでした.libusbが、extraフィールドをdon't careの放ったらかしにしているように見えたからです.エンドポイントが複数存在するUSBデバイスはよくありますが、なぜかextraフィールドはいつも空っぽです.
extraフィールドがいつも空っぽでは、複数あるエンドポイントディスクリプタを読めません.挫折です.

エンドポインタディスクリプタを読む関数は使えるか?
ストリングディスクリプタの例に倣って、openしてからlibusbの関数を使ってみました.
usb_get_descriptor()
この関数に、エンドポインタを指定し、その何番目かを指定ればよいはずです.しかし、返答が空っぽか狂っているかです.なぜかというと、EZ-USB FX2LPのプログラムに、エンドポインタディスクリプタの問い合わせに反応するルーチンが存在しないからです.EZ-USBに限らず、他のUSBデバイスでも空っぽか狂っているかで同様でした.なんというコトでしょう.どうやったら複数あるエンドポインタの素性を知れるのだ?

全てのディスクリプタを読む方法
キーは、コンフィグディスクリプタの wTotalLength というフィールドにありました.
EZ-USB(に限らず)のディスクリプタは、コンフィグ→インターフェース(複数)→エンドポイント(複数)の順番で並べてあります.コンフィグディスクリプタを2度目に読むときに、後続のディスクリプタも一気読みしているんです.一気読みするためにxxBYTE読んでくれよな、と主張しているのがこのwTotalLength なんです.
USB初期化の時にコンフィグディスクリプタの要求なのに50BYTEとかたくさん読む瞬間があるのですが(しかも二度目)、それはこの一気読みのためだったのでした.なんて癖があるんだょ.
struct usb_config_descriptor {
uint8_t  bLength;
uint8_t  bDescriptorType;
uint16_t wTotalLength;
uint8_t  bNumInterfaces;
uint8_t  bConfigurationValue;
uint8_t  iConfiguration;
uint8_t  bmAttributes;
uint8_t  MaxPower;
};

全USBデバイスのディスクリプタを読むホストプログラム
Linuxマシン上で動かすホストプログラムのソースコードはこちらです.めんどくさいので解説は割愛します.ヘボいソースコードなので読めばわかると思います.コメントほとんどありません.orz

ソースコードをLinuxマシンでコンパイルします.
gcc fx2d.c -lusb
a.outを実行すると、このような表示が出ます.一つのUSBデバイスにこれだけ表示が出ます.これはEZ-USBの例です.

Num Config/Interface/EP 1/1/4         ←各ディスクリプタが何ヶ存在するか? EPのみ4ヶ
======>open successfully           ←デバイスopen成功
 Manufacturer (Idx1) Cypress      ←ストリングディスクリプタの生産者名
 Product      (Idx2) EZ-USB        ←ストリングディスクリプタの製品名
======device descriptor======    ←デバイスディスクリプタ
 Length(18) 18 type(1) 1
 USB version 200              ←USB2.0
 device class 0 (No class)     ←デバイスクラス無し
 EP0 packet size 64      ←エンドポイント0のパケットサイズ64BYTE
 VID 04b4 PID 1004      ←VIDとPID
 Manufucturer 1     ←ストリングディスクリプタ構造体の1番目=製造社名
 Product 2           ←ストリングディスクリプタ構造体の2番目=製品名
 SerianNum 0        ←製造番号は無し
 Config Num 1       ←コンフィグは1つである
======>get config descriptor etc. 46BYTE     ←コンフィグを48BYTE一気読み  この中にコンフィグインターフェースエンドポイント(4ヶ)が入っている
09 02 2e 00 01 01 00 ffffff80 32 09 04 00 00 04 ffffffff 00 00 00 07 05 02 02 00 02 00 07 05 04 02 00 02 00 07 05 ffffff86 02 00 02 00 07 05 ffffff88 02 00 02 00
======config descriptor======     ←コンフィグディスクリプタ
 Length(9) 9 type(2) 2
 TotalLength 46       ←後続のエンドポイントまで全部で46BYTE一気読みしろ!
 NumInterface 1         ←インターフェースディスクリプタは1つである
 Config Value 1
 Configuration string Index 0   ←ストリングディスクリプタ構造体への番号だが(0なので)無し
 Attributes 128
 MaxPower 100mA         ←消費電流100mA (5V)
======interface descriptor======      ←インターフェースディスクリプタ
 Length(9) 9 type(4) 4
 Interface 0
 AlternateSetting 0
 NumEP 4                     ←エンドポイントディスクリプタは4つ
 Class ff
======endpoint descriptor======           ←エンドポイントディスクリプタ
 Length(7) 7 type(5) 5
 EP2 OUT bulk                    ←エンドポイント2  OUTPUT    バルク転送
 packet size 512                 ←最大パケット512BYTE
======endpoint descriptor======           ←エンドポイントディスクリプタ
 Length(7) 7 type(5) 5
 EP4 OUT bulk                    ←エンドポイント4  OUTPUT    バルク転送
 packet size 512                 ←最大パケット512BYTE
======endpoint descriptor======           ←エンドポイントディスクリプタ
 Length(7) 7 type(5) 5
 EP6 IN  bulk                      ←エンドポイント6  INPUT    バルク転送
 packet size 512                 ←最大パケット512BYTE
======endpoint descriptor======           ←エンドポイントディスクリプタ
 Length(7) 7 type(5) 5
 EP8 IN  bulk                      ←エンドポイント8  INPUT    バルク転送
 packet size 512                 ←最大パケット512BYTE
======extra data numbers======    ←構造体内のextralenフィールドがゼロばっかしという確認のため
      device cfgNum 1
    config extralen 0
         altsetting 1
 interface extralen 0
        EP extralen 0
              numEP 4

てなわけで、ホストから見たディスクリプタの受け渡しはだいたい納得しました.
今宵はここまでにしとうございます.

その21へ    その23へ

かしこ

INDEXぺーじへ
https://hirasakausb.blogspot.com/2019/03/ez-usb-fx2lp-index.html

4 件のコメント:

  1. ライブCDの部屋 WindOSが面白そう
    simosnet.com/livecdroom/index.html

    返信削除
    返信
    1. こんなにたくさんあるんですか...

      削除
    2. cdn44.atwikiimg.com/windos?cmd=upload&act=open&pageid=4&file=README-ja_shift_jis.txt
      使用方法 これを読むとusb-bootのない機種もCDを経由してUSBが起動するの・・
      データ領域を考え、USBにするのでしょうか?

      削除
    3. そうか、USBメモリをHDDと思ってくれればいいのか.
      CD/DVDブートはかったるいです.

      削除