というわけで今回もホスト側の話です.知りたいのは、、、
ホスト(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;
};
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_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;
};
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
ライブCDの部屋 WindOSが面白そう
返信削除simosnet.com/livecdroom/index.html
こんなにたくさんあるんですか...
削除cdn44.atwikiimg.com/windos?cmd=upload&act=open&pageid=4&file=README-ja_shift_jis.txt
削除使用方法 これを読むとusb-bootのない機種もCDを経由してUSBが起動するの・・
データ領域を考え、USBにするのでしょうか?
そうか、USBメモリをHDDと思ってくれればいいのか.
削除CD/DVDブートはかったるいです.