借り初めのひみつきち

仮ブログです。

最小限の USB Hub 対応

USB バスはスター型トポロジーになっていて規格上は最大127台のデバイスを接続することができることになっていますが*1、実際のコンピューターには多い機種でも数個、ほとんどのモバイル PC にはたった1個の USB ポートしかコネクタがありません。
それ以上の機器を繋ぐには USB Hub を使ってポートを拡張する必要があります。

USB Hub とは。

USB Hub とは USB ポートを拡張して最大15台*2 の子 USB デバイスを接続できるようにするデバイスです。 USB Hub の下にさらに別の USB Hub を繋ぐことで規格上は5段まで拡張することができます。

USB Hub の主な役割は、ポートの抜き差し監視と通知、 USB パケットのルーティング、速度の異なるデバイスプロトコル変換等です。

Ethernet のハブとは異なり USB Hub はホストから見るとアドレスを持った単なる1デバイスで接続可能台数に含めます。
通常のデバイスと同様にコンフィグレーションを行ってクラスコードで USB Hub を認識したらクラスドライバに引き渡します。
クラスコードは以下のように定義されています。

f:id:neriring16:20191024094252p:plain

なお、この図には載っていませんが USB3 (SS) Hub のクラスコードは 9, 0, 3 になります。 USB 公式サイトからの引用ですが情報が古くあまり更新されていないようです。
single TT / multiple TTs の TT というのは HS 対応ハブに LS/FS デバイスを接続した時の変換用バッファのことで、 multiple TTs の方が若干パフォーマンスがよくなります。
LS/Low Speed デバイスというのは最大 1.5Mbps に対応した低速デバイスで主に HID (Human Interface Device) などに使われます。
FS/Full Speed デバイスというのは最大 12Mbps に対応した中速デバイス、 HS/High Speed デバイスは USB2 以降で最大 480Mbps に対応した高速デバイスです。
SS/Super Speed デバイスというのは USB3 以降で最大 5Gbps に対応した超高速デバイスです。

実は USB2 以下と USB3 以上では物理層などの下層のプロトコルが全く別になっていて、 デバイスツリー上では USB2 ルートハブの下には USB2 デバイス、 USB3 ルートハブの下には USB3 デバイスというようにそれぞれ別々のハブに接続されたデバイスとして見えています。そのため USB2 Hub と USB3 Hub の仕様にも細かい違いがいくつかあります。
以降この記事では USB2 Hub を前提に記述します。

USB Hub の初期化

USB Hub は1個のインタラプト転送エンドポイントでポートステータス変更イベントを通知し、コントロール転送の GET_STATUS で各ポートのステータスを取得し、 SET_FEATURE や CLEAR_FEATURE でそれぞれのステータスビットの制御をするのが主な流れになります。

まず最初に GET_DESCRIPTOR コントロール転送で Hub Descriptor を取得します。
Hub Descriptor には USB Hub に関する情報が記述されており、この中で最も重要な情報はハブの提供するポート数 (bNbrPorts) です。
なお、 xHCI の場合は Hub Descriptor をもとにスロットコンテキストの修正が必要です。

f:id:neriring16:20191024102043p:plain

f:id:neriring16:20191024101852p:plain
f:id:neriring16:20191024101904p:plain

bNbrPorts の情報をもとに全てのポートを有効化します。
まず、全てのポートに対して SET_FEATURE で PORT_POWER ビットを1にし、次に CLEAR_FEATURE で C_PORT_CONNECTION ビットをクリアします。この例のように C_ から始まるステータスビットは対応するステータスビットの内容が変化したことを示しています。
これで全てのポートが有効化され、抜き差しが検知できる状態になります。
なお、初期化の時点で既にデバイスが接続されているポートには改めて挿入イベントが発生しませんので後ほど対応します。

ポートの抜き差し検知とポートリセット

Hub の初期化が終わったら、 USB Hub イベントスレッドでインタラプト転送のエンドポイントからデータを読み込みます。
USB HUBのインタラプト転送エンドポイントから読み込まれるデータはステータス変更があったポートのビットマップになっていて、どこかのポートでステータス変更イベントがあると該当するビットが1になります。
注意点としてポート番号は1から始まるので最下位ビット(ビット位置0)は別の意味に使われていることと、一度ステータスチェンジイベントが発生するとイベント要因のステータスビットをクリアしない限り永遠にイベント通知され続ける点に注意が必要です。

f:id:neriring16:20191024102433p:plain

ビット演算をしてイベントの発生したポートがわかったら、 GET_STATUS でポートのステータスを読み込みます。
ポートステータスは32ビットのビットフィールドからなる構造体で、下位16ビットは現在のポートのステータスを表し、上位16ビットは変更のあったステータスビットが1になっています。

f:id:neriring16:20191024101648p:plain

f:id:neriring16:20191024101507p:plain

f:id:neriring16:20191024101520p:plain

ポートの抜き差しをすると C_PORT_CONNECTION ビットが1になり、対応する PORT_CONNECTION ビットの値を見るとデバイスの接続状態がわかります。C_PORT_CONNECTION ビットが1でかつ PORT_CONNECTION ビットが1の場合はデバイスが接続されたことを示しています。
次に SET_FEATURE で PORT_RESET ビットを1にするとポートリセットが行われ、しばらく後に正常終了すると C_PORT_RESET ビットと PORT_ENABLE ビットが1になるので CLEAR_FEATURE で C_PORT_RESET ビットをクリアします。

f:id:neriring16:20191024101456p:plain

f:id:neriring16:20191024101629p:plain

ここまででデバイスは USB バス上に接続され、まだコンフィグレーションされていない状態です。
次は xHC からデバイスにアクセスするための設定が必要になります。

xHCI のデバイススロット割り当て

まず、ルートハブに接続されたデバイスと同じように ENABLE_SLOT コマンドでデバイススロットを割り当てます。
次に ADDRESS_DEVICE コマンドで xHCI のデバイススロットと実際の USB デバイスの紐付けをしますが、この時に指定するスロットコンテキストの内容がルートハブに繋がったデバイスとそれ以外のハブに繋がったデバイスで異なり、どのハブのどのポートに繋がっているかの設定が必要です。

ここで重要となる要素が Route String です。 Route String は4ビットのハブのポート番号を5つ組み合わせた 4 x 5 = 20ビットの値です。

まず、ルートハブ直下のデバイスは全て Route String 0x00000 になります。
ルートハブにハブ0をつないだ場合、ハブ0のポート1に繋がったデバイスは Route String 0x00001 、ポート2に繋がったデバイスは Route String 0x00002 ... ポート15に繋がったデバイスは Route String 0x0000F になります。
ハブ0のポート1にハブ1を繋いだ場合、ハブ2のポート1に繋がったデバイスは Route String 0x00011、ポート2に繋がったデバイスは Route String 0x00021 になります。以降同じようにして5段目まで繋げます。
また、 Route String だけだとルートハブのどのポートに繋がったか判別できません。 Root Hub Port Number にはルートハブに繋がっている最初のハブと同じ値を指定します。

バイスの速度はポートステータスの PORT_LOW_SPEED ビットと PORT_HIGH_SPEED ビットから取得できます。 PORT_LOW_SPEED ビットが1の場合は LS デバイス、 PORT_HIGH_SPEED ビットが1の場合は HS デバイス、それ以外の場合は FS デバイスになります。 USB2 Hub には SS 以上のデバイスを認識する機能はありません。

ADDRESS_DEVICE コマンドが成功したらあとはルートポートに繋がったデバイスと同じように通信できる状態になるので、通常通りコンフィグレーションをして OS からデバイスが使用可能になります。

TL; DR

ここまでで USB Hub に繋がったデバイスが最小限使えるようになりました。

まだ接続処理が不安定で USB Transaction Error がよく出ます。これはある USB Transaction を通信中に別の USB Transaction を始めようとするとよく発生するエラーなので排他制御を頑張らないといけなそうです。
また、筆者の環境だけかもしれませんが、 USB Hub は他の通常のデバイスに比べて動作が不安定でうまくコンフィグレーションできないことがあるようです。

*1:実装上の上限はまた別の話

*2:USB3の場合