以前 USB Hub の制御方法について記事を書きましたが、当時は USB2 Hub しか制御できていなかったのでアップデートします。
USB Hub の基本
USB ではそれぞれのデバイスに 1〜127 の 7bit のアドレスを割り当て、バス全体で最大 127 台までの USB デバイスを扱うことができます。
なお、アドレス 0 はコンフィグレーション用に予約されています。
実際の PC には多くても数個のポートしか繋ぐことができません。
それ以上のデバイスを接続するには USB Hub を使ってポートを拡張する必要があります。
USB Hub は Ethernet Hub とは異なり、独立したアドレスを持っているごく普通の USB デバイスです。
専用のドライバで制御しないとデバイスを接続してもホストと通信できません。
つまり 127 台の制限の中には Hub 自身も含まれるので、現実には Hub 以外のデバイスだけで 127 台接続することはできません。
FS / LS / HS / SS
USB 規格には大きく分けると4種類の転送速度が定められています。
- FS / FullSpeed は元々 USB 規格で想定された規格で 12Mbps で通信可能です。 LS 以外の全ての USB デバイスは互換性のために FS として動作可能なモードを持つはずです。現代では中途半端な速度のため FS ネイティブのデバイスはマイナーとなっています。
- LS / LowSpeed はキーボードやマウスなど低速なデバイスを低コストで実現するための規格で 1.5Mbps で通信可能です。電気仕様が FS と若干異なっていて接続した時点で認識できるようになっています。
- HS / HighSpeed は USB 2 からサポートされた規格で 480Mbps で通信可能です。 FS を拡張したような規格で、接続直後は FS デバイスとして動作し、 Chirp という特殊なプロトコルでネゴシエーションを行なってホストやハブとデバイスがお互いに HS 対応だった場合に HS に切り替わります。 XHCI ではこの動作はコントローラー側で隠蔽されるのでドライバ層で通常意識する事はありません。
- SS / SuperSpeed は USB3 からサポートされた規格で 5Gbps 以上の速度で通信可能です。 USB3 系は転送速度の規格が若干複雑になっていて、まとめて ESS (Enhanced SuperSpeed) と表現することがあります。 USB3 は USB2 と物理層が全く異なっており、 USB3 デバイスは USB2 コネクタと別に追加された信号線で通信を行います。 *1
USB2 Hub と USB3 Hub
USB2 を乱暴に表現すると USB1 を高速化させた規格であるのに対し、 USB3 は様々な面で USB2 系とは異なるテクノロジーで構成されていて SS/ESS デバイスと FS/LS/HS デバイスの間にはいくつか明確な違いが存在します。
このことは USB Hub においても重要な違いがあり、 USB2 系 (FS/LS/HS) のデバイスツリーと USB3 系 (SS/ESS) のデバイスツリーはルートハブの時点でそれぞれ別のツリーとして扱われます。
つまり、HS Hub の下には FS/LS/HS デバイス のみが接続され、 SS デバイス が接続されることはありません。
同様に、SS Hub の下には SS/ESS デバイスのみが接続され、 FS/LS/HS デバイスが接続されることはありません。
実際の物理的な USB3 Hub には USB2 デバイスを接続することができますが、これは内部的に HS Hub と SS Hub の2つの Hub として別々に動作しているためで、 SS Hub に直接 USB2 デバイスが繋がっているわけではありません。

SS Hub は USB 規格ではオプションとなっている uuid を必ず持っていることになっていて、同一のデバイスであれば接続するプロトコルや動作モードに関わらず同じ uuid を返す決まりがあり、 uuid を比較することで相方になっている SS Hub と HS Hub を特定することが可能です。
ルートハブ
XHCI などの USB ホストコントローラー直下にあるポートのことを特別にルートハブと呼びます。ルートハブは USB デバイスとしての通常の USB Hub とは若干異なり、ホストコントローラーで直接制御します。
XHCI のルートハブのポートは USB2 のポートと USB3 のポートに分かれていて、 USB2 ポートには FS/LS/HS デバイスのみが、 USB3 ポートには SS/ESS デバイスのみが接続できます。
ステータスレジスタは USB2 ポートと USB3 ポートで同じインターフェースを使う関係上、 USB Hub のステータスレジスタと主要なフラグの相対関係は似ていますが若干内容が異なります。
通常、外部に公開されている USB3 コネクタはルートハブの USB2 ポートと USB3 ポートを組み合わせて物理的にはひとつのコネクタになっていますが、内部的な制御の流れは USB2 系と USB3 系で別になっています。

これらの接続状況は ACPI で定義されています。 myos では ACPI の対応が追いついていないのでこの辺りの情報は未対応です。
Compound Device
USB Hub 内蔵のキーボードや USB 接続の Dock のことを Compound Device と呼びます。
USB Hub は Compound Device を構成するための重要な要素にもなります。
Compound Device は物理的にはひとつのデバイスですが、 USB バスに接続されるとまずは USB Hub として接続され、 Hub のコンフィグレーションが終わるとその下にいくつかの追加の USB デバイスとして認識されます。
通常の USB Hub と Compound Device は Hub Descriptor に含まれる属性値で区別することができますが、実際のところデバイスマネージャの見た目に影響するだけで、通常の USB Hub とデバイスとして扱っても特に問題ないと思います。
Compound Device とよく似た言葉に Composite Device がありますが、 Composite Device は USB バス上では完全にひとつのデバイスとして振る舞う点が Compound Device と異なります。
RouteString
USB3 では Hub に繋がっているデバイスのルーティングのために RouteString という値を使います。
RouteString はルートハブ以外の経路のハブのポート番号を 4bit で表現したパスのような値で、最大5つまでの Hub を表現するため 4 x 5 = 20bit の値になっています。

SS Hub は RouteString を使ってルーティングするため、最大ポート数が 4bit で表現可能な 15 までに制限されます。
HS Hub は RouteString に依存しないため規格上 16 以上のポート番号を扱うことが可能で、その場合 RouteString は正確な経路を表現できません。
現実的には USB 規格でポートの消費電流の最低 100mA と最大 500mA の要件を満たすため、アップストリームから 500mA を供給してもらってハブ自身が消費する 100mA と各ダウンストリームポートに 100mA を分配できる4ポートが通常の Hub の上限となり、それ以上のポートをサポートするには電源を確保する必要があり扱いづらくなるため、4ポートより多い USB Hub はあまり存在しません。
HS Hub (USB2) の使い方
USB Hub にはひとつの Interrupt 転送エンドポイントがあり、ポートの抜き差しや異常発生時などのイベントを通知するのに使用します。
基本的な USB Hub の制御は、コントロール転送で各ポートに対して GET_STATUS
でステータスを取得し、 SET_FEATURE
または CLEAR_FEATURE
でオン・オフを制御します。



コンフィグレーション
まずは、通常の USB デバイスと同じように Device Descriptor や Configuration Descriptor を使って通常のコンフィグレーションを行います。
Device Descriptor のクラスコードを見て USB Hub であることを認識したら、クラスドライバをロードして USB Hub 固有の初期化を行います。
USB Hub はデバイスクラスのベースクラスが 09 で、 09_00_00 が FS hub、 09_00_01 と 09_00_02 が HS hub、 09_00_03 が SS Hub を指しています。
Base Class |
Sub Class |
Protocol |
|
09 |
00 |
00 |
FS Hub |
09 |
00 |
01 |
HS Hub with single TT |
09 |
00 |
02 |
HS Hub with multi TTs |
09 |
00 |
03 |
SS Hub |
FS Hub は基本的に HS Hub と同じ方法で扱えると思いますが、現代では入手困難なので確認できていません。
HS Hub の TT というのは FS/LS デバイスを接続した場合の速度変換用バッファのことで、 multi TTs 対応の Hub は複数の FS デバイスを接続したときの性能がシングルの時よりも若干向上するようです。
SS Hub は HS Hub と違いが多いので別項にまとめます。
Hub Descriptor
USB Hub 固有の初期化では、まず GET_DESCRIPTOR
リクエストで Hub Descriptor を取得します。
Hub Descriptor のサイズはポート数によって可変長なので正確なデータを得るためには取得方法を工夫する必要があります。*2



Hub Descriptor にはいくつかの属性がありますが、最も重要なメンバーはポート数を表す bNbrPorts
です。
USB2 Hub ではポート数が実質無制限 *3 なので Hub Descriptor も可変長になりますが、現実の Hub は4ポート以下が主流です。
XHCI ではデバイスが Hub の場合に特別な設定が必要なので、 Hub Descriptor を元に Slot Context の Hub、 MTT、 Number of Ports、 TTT あたりの Hub に関する設定を行い、最後に EVALUATE_CONTEXT コマンドを XHCI に送信して設定を反映します。

GET_STATUS SET_FEATURE CLEAR_FEATURE と各ポートの初期化
ハブの構成が終わったら各ポートの初期化を行います。
まずは、全てのポートに対して SET_FEATURE
で PORT_POWER
を送信して電源をオンにします。
for port in self.hub_desc.ports() {
self.set_port_feature(UsbHub2PortFeatureSel::PORT_POWER, port).await?;
}
次に、全てのポートに対して CLEAR_FEATURE
で C_PORT_CONNECTION
を送信します。
これは念の為で送らなくてもいい気がします。
最後に、全てのポートに対して GET_STATUS
でステータスを取得して PORT_CONNECTION
ビットを調べ、1だった場合は最初からデバイスが繋がっているので個別にポートリセット(後述)します。
Interrupt 転送ループ
USB Hub の初期化が完了したら Interrupt 転送エンドポイントのデータを読み込む無限ループに突入します。
Interrupt 転送エンドポイントから読み出せるデータは各ポートで状態変更イベントがあったことを示す数バイトのビットマップとなり、 0 以外の値を読み出した場合はどこかのポートで何かしらのイベントが発生したことを示しています。
ポート番号は1から始まるのでビット番号も1から始まり、ビット0は Hub 本体でイベントが発生したことを示す特殊なビットになります。

状態変更イベントがあったことを検知したら、該当するポートのステータスを GET_STATUS
で取得します。
GET_STATUS
で取得するデータは 32bit 値で、下位 16bit は現在のステータス、上位 16bit はステータスビットに変更があったことを示すフラグとなります。基本的には下位 16bit と上位 16bit は同じビットが同じステータスに対応しています。
例えば、ポートの接続状態が変化すると PORT_CONNECTION
ビットが変化し、同時に C_PORT_CONNECTION
ビットが1になります。
ステータスの C_PORT_CONNECTION
ビットが1になっていた場合は、次に PORT_CONNECTION
ビットの値を調べます。
PORT_CONNECTION
が1になっていた場合はデバイスが接続されたイベントなのでポートリセット(後述)を行なってデバイスのコンフィグレーションを行います。
0の場合はデバイスが切断されたのでデバイスツリーから該当のポートのデバイスを削除します。
ただし、 C_PORT_CONNECTION
と PORT_CONNECTION
だけで抜き差しを判断しようとすると、チャッタリングやノイズの影響でデバイスが存在しないのにデバイスを抜いたと判断するような誤動作が発生することが稀にあるので、実際の抜き差しの判断はドライバ内部で管理している接続状態も考慮した方が良さそうです。
C_PORT_CONNECTION
以外も本来はエラーのリカバリなどを行うべきですが、いずれかのビットが1の間は常にデータを送信してくるので、
C_
で始まるビットはとりあえず最後に CLEAR_FEATURE
でクリアしておきましょう。
ポートリセットとコンフィグレーション
USB Hub の各ポートにデバイスが接続されたことを検知したら、デバイスを使うための準備が必要です。
まずは、 SET_FEATURE
で PORT_RESET
を送信します。
少し待ってから GET_STATUS
でポートのステータスを取得し、 PORT_ENABLE
が1になっていることを確かめます。
デバイスのリセットが成功して Default
フェーズになり、 USB バス上では「アドレス 0 」で通信できる特殊な状態になっています。
次に、先ほど取得したステータスからデバイスの速度を判別します。
PORT_LOW_SPEED
が1の場合は LS デバイス、 PORT_HIGH_SPEED
が1の場合は HS デバイス、それ以外の場合は FS デバイスです。
先述の通り HS Hub に SS デバイスが接続されることはないので考慮する必要はありません *4
ホストコントローラーに Hub 自身の情報とポート番号とデバイスの速度を伝えて新しいデバイスを接続する準備をします。
XHCI の場合、ここで ENABLE SLOT コマンドでデバイススロットを割り当て、 Hub からもらった情報をもとに Device Context や Slot Context を作ります。
ルートハブに繋がっているデバイスと違い、ルーティング情報としてルートハブに繋がっているポート番号や RouteString を設定する必要があります。
デバイスの速度によっては parent Hub の情報も設定する必要があります。
最後に ADDRESS DEVICE コマンドを実行すると Device Context が有効になり、この時デバイスに SET_ADDRESS
リクエストが送信されてアドレスが割り当てられます。
ここまで終わると該当の USB デバイスは Address
フェーズになっているので、通常の USB デバイスと同じようにコンフィグレーションを進めると Configured
になってデバイスが使えるようになります。
なお、 Default
フェーズのデバイスが存在する状態で他のスロットでコマンドを実行すると宛先がよくわからなくなって USB Transaction Error が起こりやすくなります。ポートリセットからコンフィグレーションの間は排他制御した方が良いです。
SS Hub (USB3) の使い方
SS Hub では Hub Descriptor 、ステータスレジスタ、 Feature セレクタなど HS Hub と内容が異なりますが、基本的な考え方は HS Hub とあまり変わらないので差分を調整する程度で同じような流れで対応できます。


USB3 には FS/LS/HS の区別がないなど削除された機能や代わりに追加された機能もあるためステータスのビット位置も若干変わっています。
また、 SS Hub はポート数の上限が 15 までと決まっているため Hub Descriptor のサイズが固定長となります。
HS Hub と SS Hub の制御で大きく異なる点は、 SET_HUB_DEPTH
リクエストで Hub 自身の Route String の長さを設定しないと動作しません。

いかがでしたか?
ここまで実装することで、 myos はとりあえず USB Hub が動くようになって多くの USB デバイスを接続できるようになりました。