繋いでみた、の続き
前回さらっと、USB2だったとかUSB3だったとか書いてるけど、AndroidのフレームワークにUSB2なのかUSB3なのかを判別するAPIはありません。
Android Developers〜USB host overview〜
また大抵のUSB3機器はUSB2との互換性があります。つまりUSB3端末に繋げばUSB3機器として、USB2端末に繋げばUSB2機器として動作するということで、端末が認識したから・動作したからというだけではUSB3機器として動作しているかどうかはわかりません。
SoCのUSB関係のハードウエアやカーネルドライバーあたりはわかっているはずですが、アプリ側から知るすべがないので役に立ちません。じゃぁどうやって調べるかと言うと接続したUSB機器に尋ねます。接続されたUSB機器に「おまえは誰じゃ」「おまえは何ができるんじゃ」と尋ねるとUSB機器からはディスクリプタとして返事が返ってきます。USB機器側は自分に何ができるかとかUSB2なのかUSB3なのかがわかっていますでので、その返事=ディスクリプタ…その中でもデバイスディスクリプタというのを解析するとわかるのです。
Androidでディスクリプタを取得にするには次の処理をする必要があります。
- 接続されたUSB機器に対応するUsbDeviceを取得
- USB機器アクセスのパーミッションを取得
- UsbManager#openDeviceを呼び出してUsbDeviceConnectionオブジェクトを取得
- UsbDeviceConnection#getRawDescriptorsを呼び出してJavaのbyte配列としてUSBディスクリプタを取得
全部Javaで実行する必要はなくて、3.で取得したUsbDeviceConnectionオブジェクトからUsbDeviceConnection#getFileDescriptor
でファイルディスクリプタ(この値はnative側でファイルをopenした時に取得できるものと同じ)を取得してnative側でデバイスファイルとしてアクセスすると4.と同じことがnative側でもできます。
ちなみにGitHubで公開しているUVCCameraリポジトリやオレオレライブラリではファイルディスクリプタをnative側へ引き渡し、以降の処理はすべてnative側で行うようにしています。
USBディスクリプタは、BNF風に書くと次のようにディスクリプタが詰め込まれた構造になっています(風なのでいらんツッコミはいらんよ)。
1 2 3 4 5 6 |
USBディスクリプタ := (ディスクリプタ)+ ディスクリプタ := struct { uint8_t bLength; uint8_t bDescriptorType; (ディスクリプタの中身|ディスクリプタ)+ } __attribute__((packed)); |
各ディスクリプタがどういう順番で並んでいるかとその中身はUSB規格と各クラス規格(UVC規格等)で決まっています。ちなみに16ビット整数や32ビット整数な部分もありますが、すべてリトルエンディアンですので適時変換が必要になるのと16ビット境界や32ビット境界にアライメントされているかどうかは不定なのでバイトアクセス前提で変換する必要があります(endian.hのletoh16やletoh32じゃだめな場合があるってことね)。
今回USB3接続かどうかを判定する際に必要となるディスクリプタはデバイスディスクリプタで次のような構造になっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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; } __attribute__((packed)); |
おぉ〜構造体とかでてくると久しぶりに技術ブログっぽいぞ✨
簡易的には、USB2かUSB3かはbcdUSBをチェックするとわかります。具体的にはbcdUSB>=0x300ならUSB3、bcdUSB>=0x200ならUSB2です。擬似コードで書くなら次のような感じになります。
1 2 3 4 5 6 7 |
if (bcdUSB >= 0x300) { // USB3 } else if (bcdUSB >= 0x200) { // USB2 } else { // USB1 } |
ただしこれだけでは十分ではなくてUSB3での通信設定(スーパースピード)についてのディスクリプタも確認したほうがいいです。USB3での通信が可能である場合==スーパースピードに対応している場合には、エンドポイントディスクリプタの直後にスーパースピードエンドポイントコンパニオンディスクリプタが存在します(場合によってはさらにスーパースピードプラスアイソクロナスエンドポイントコンパニオンディスクリプタがついていることも)
USB3での最大5Gbpsでの通信(スーパースピード)に対応しているのであれば少なくともこのディスクリプタが存在しています。
1 2 3 4 5 6 7 |
struct usb_ss_ep_comp_descriptor { uint8_t bLength; uint8_t bDescriptorType; uint8_t bMaxBurst; uint8_t bmAttributes; uint16_t wBytesPerInterval; } __attribute__((packed)); |
細かいことを言えばバイナリデバイスオブジェクトストア(BOS)ディスクリプタのデバイスケイパビリティディスクリプタの1つであるスーパースピードUSBデバイスケイパビリティのwSpeedsSupportedフィールドを確認もしたほうがいいですが、まぁそこまでしなくても大抵は上記2つで十分でしょう。
ということで確認した結果が前回の記事になります。
繋いでみたはいいんだけど簡単には動かんかった(´・ω・`)
そもそも端末自体…ハードウエア・OS(カーネルドライバー)自体がUSB3に対応しているかどうか・動くものなのかどうかすらわからないところからのスタートなので、自分の作ってるユーザーランド側のドライバーがおかしいのかハードウエア・OSの側がおかしいのかわからんくてなんども挫折しかけました、というかふて寝しました(-_-)zzz
結果として言えば、その時点での自分のユーザーランド側ドライバーの間違い+ハードウエアand/orOS(カーネルドライバー)の不具合が色々発覚しました。ユーザーランド側ドライバーの間違いは直しましたが(後述)、ハードウエアand/orOS(カーネルドライバー)の不具合についてはいかんともしがたく、当たりの端末と当たりのカメラの組み合わせを引き当てれば動くというのが現在のAndroidのUSB3対応の現実です。つまりAndroidにUSB3はまだ早い・動けばラッキーということです。なのであんまり期待しちゃダメよー?