借り初めのひみつきち

仮ブログです。

USB/xHCI インタラプト転送の罠

USB プロトコルではホストとデバイスの役割が明確に分かれていて、ホスト (PC) から送受信命令を発行しないとデータ転送が始まりません。

ストレージのようなデバイスではこれで問題ありませんが、キーボードやマウスのように人間の気まぐれでいつデータが来るかわからないデバイスはこの方法だとデータを送るタイミングがわかりません。
PS/2 キーボードの場合は割り込みで送信タイミングを通知しますが、 USB で同様のケースはインタラプト転送という転送モードで対応します。

USB プロトコルのインタラプト転送はホストが一定の間隔でデバイスに問い合わせ、データを転送する必要がある場合のみ実際の転送が行われます。名前の予想に反して実際の動作はポーリングです。
一方、 xHCI ではインタラプト転送のエンドポイントに対して読み込み命令を発行するとホストコントローラーが定期的にデバイスに問い合わせ、実際に転送が行われるまで結果を遅延することで擬似的に割り込み動作を実現しています。
そのため、インタラプト転送ではポーリングの間隔値が重要な意味を持ちます。

実際の Interval の設定はエンドポイントの設定時にコンフィグレーションで取得した Endpoint Descriptor にある bInterval フィールドの値を元に Endpoint Context Data Structure の Interval フィールドに値を設定します。
これらのフィールドはどちらも8ビット値ですが、デバイスの速度や転送モードによって意味や取り得る範囲が異なるため、そのまま代入することはできません。

この値の関係は xHCI の仕様書に表が書かれています。

moe では以前からキーボード入力に大幅なラグが発生する事象を確認していて id:uchan_nos 氏に相談してみたところ Interval の設定ミスではないかということでソースコードと仕様書を改めて確認し、値の変換をせずそのまま代入していたのが原因と判明しました。

通常 HID デバイスは 10ms 前後の周期で動作し、 HID でよく使われる LS/FS デバイスのインタラプト転送の Endpoint Descriptor の bInterval の値も 10 前後になりますが、 xHCI Endpoint Context Data Structure の Interval は対数で指定するため、そのままの値を設定すると数百msと解釈されて大幅な遅延が発生します。

インタラプト転送では Interval の設定値も超重要です。