osdev-jpでは、OS開発に有用な情報を収集し公開しています

View My GitHub Profile

USB のホスト側の制御(特に xHCI)についてのメモ書き

USB 制御の概要

USB を制御するには,いくつかの層のドライバを書く必要がある.

USB 規格はあくまでもデータのやり取りの形式(フレームの構造,USB 機器とやり取りする際のプロトコルなど)を規定している. USB 規格と物理層をつなぐのがホストコントローラで,これは USB 規格とは別物.

ホストコントローラ規格には幾つか種類がある:UHCI, OHCI, EHCI, xHCI

資料リンク

USB キーボード入力までの道のり

ホストコントローラ(xHCI)の初期化から USB キーボードからデータを入力するまでの道のりを最短でまとめる. (以下の 4 つの記事には,uchan が 2019 年 6 月の 2 週間くらいで得た知見が反映されてないので,足りない記述が多々ある)

USB キーボード入力までの道のりのハマりポイント

以下,雑多なまとめ記事.

xHCI のレジスタ

xHCI のレジスタは大きく 2 箇所に存在する.

PCI コンフィギュレーション空間の BAR で MMIO 空間のアドレスを設定し,後は MMIO 空間のレジスタでいろいろ制御する感じになる.

MMIO 空間

“xHCI for Universal Serial Bus: Specification”(以降 “xHCI Spec” と書く)5.3 節によれば,規格書に出てくる MMIO レジスタは,すべて MMIO 空間の先頭を基準に配置されているらしい.規格書の中では一貫して MMIO 空間の先頭のことを Base と呼んでいる.

MMIO 空間の先頭(Base offset 00h)には xHCI Capability Registers が配置されている. Capability Registers の直後から Operational Registers が続く. xHCI Extended Capabilities の各レジスタはリンクリスト状に連なって存在する.

64 ビットレジスタの書き込み制約

MMIO 空間のレジスタに書き込みを行う際は,特別に記載がない限りバイト単位で書き込めない(xHCI Spec 5.1 Register Conventions 節). xHC が 64 ビットアドレッシングをサポートする場合(AC64 = 1),64 ビット幅のレジスタに書き込む際は Qword 幅で書き込む必要がある. すなわち一度の mov 命令で 8 バイトを書き込む. (規格書には 64 ビット mov ができないシステムで 64 ビットアドレッシングを行うための方法が記載されているが,ここでは割愛する)

64 ビット幅のレジスタはアドレスを記録するためのレジスタである. そのため xHC が 32 ビットアドレッシングをサポートする場合(AC64 = 0),64 ビット幅のレジスタの上位 32 ビットは使用されない. Dword 幅で書き込めばよい.

書き込みに関するビット幅の制約は 5 章で紹介されているレジスタ群だけでなく,7 章の Extended Capabilities の各レジスタにも当然適用される.

Capability Registers の読み込み制約

Capability Registers はすべて read-only なので書き込み制約はない.

読み込みについても特に記載がなく,また 1 バイトのレジスタが存在することから,バイト幅での読み込みができると考えて良さそうだ.

Operational Registers の読み書き制約

xHCI Spec 5.4 Host Controller Operational Registers によれば,特別に記載がない限りすべてのレジスタは Dword で読み書きする必要がある(規格書には読み込み時のビット幅制約しか記載がないが,恐らく書き込み時も Dword で行う必要があるだろう). 必要なら Dword で読んだ後でビットマスクをかける. Dword 中の特定の場所だけ変更したければ,read/modify/write を行うべし.

書き込みだけでなく読み込み幅の制約があるのは,Operational Registers は xHC によって変更されることがあるためだろう. 一回の mov で 32 ビットを読まないと,一貫性のある結果にならないことがあり得る.

Runtime Registers の読み書き制約

xHCI Spec 5.5 Host Controller Runtime Registers にある.基本的に Operational Registers の読み書き制約に準ずる.

ただし Runtime Registers に存在する Qword のアドレスフィールドには Qword 幅で書き込む必要がある.

Doorbell Registers の読み書き制約

xHCI Spec 5.6 Doorbell Registers によれば,Doorbell Registers は全部 32 ビット幅であり,Dword 幅で読み書きをする必要がある.

Extended Capabilities の読み書き制約

Extended Capabilities の各レジスタに関する読み書き制約は xHCI Spec に記載がない. そのため,実装が許せばバイトアクセスが可能であるし,バイトアクセスができない実装があってもおかしくない. どのパソコンでも動かせるようなドライバを作るなら,バイトアクセスできない前提でプログラムを書くのが良いだろう.

ただし xHCI Spec 5.1 Register Conventions 節に次の注釈がある.

The USB Legacy Support (USBLEGSUP) Extended Capability requires support for Byte accesses for Semaphore address, refer to section 7.1.

USBLEGSUP レジスタは 32 ビット幅ではあるが,とある事情によりバイト幅の読み書きができる必要があるという意味だ. 特に,バイト幅の書き込みができることが重要である. USBLEGSUP はどの xHC 実装であってもバイトアクセス可能なはずである.

制約まとめ

レジスタのアクセス制約をまとめる.

レジスタ種別 読み 書き
Capability Registers 特に制約はない 書き込みは不可
Operational Registers Dword Dword
Runtime Registers Dword Dword(64 ビットアドレスフィールドは Qword)
Doorbell Registers Dword Dword
Extended Capabilities 特に制約はない 特に制約はない
USBLEGSUP Byte でも可 Byte でも可

Device Slot 関連の用語

xHCI 規格書の 1.6 “Terms and Abbreviations” 参照.

xHC の初期化

xHCI 規格書の 4.2 “Host Controller Initialization” にいろいろ書いてある. レジスタやメモリ上のデータ構造を初期化し,最終的に USBCMD レジスタの Run/Stop ビットを 1 にするとコントローラが動き出す.

  1. Host Controller Reset(USBCMD.HCRST に 1 を書き込む)
    • PCI コンフィグレーションレジスタを除くレジスタ群が初期化される
  2. Primary Interrupter(Interrupter Register Set 0)の初期化
    • R/S ビットを 1 にする前に Primary Interrupter レジスタ群を初期化する必要がある
    • 最低限 Event Ring 関係のレジスタに設定を行う

この後,接続されているデバイスの列挙とそれぞれのデバイスの初期化を行う. (電源投入時から接続されている USB デバイスについては UEFI が一度初期化してくれているのだが,xHC をリセットしたらデバイスの初期化ももう一度しなければならない)

QEMU に -device usb-kbd を指定すると PS/2 キーボードの代わりに USB キーボードが接続される.

データ構造のメモリ制約

Table 54: Data Structure Max Size, Boundary, and Alignment Requirement Summary という表に,各データ構造のメモリ制約が記載されている. アライメント制約は忘れがちなので注意すべし.

Command Ring

xHC に対するコマンドの発行は Command Ring というキューにより行う. Command Ring にコマンドを積み,xHC にコマンド発行の合図を送ると xHC がコマンドを読み取って動作する. OS が「プロデューサ」,xHC が「コンシューマ」の役目である.

Command Ring は 1 つの xHC インスタンスにつき 1 つだけ存在する. Command Ring は TRB(Transfer Request Block)というデータ構造をやり取りするためのキューだ. TRB にはいくつも種類があるが,Command Ring で処理できる TRB の種類は限られている.

Command Ring にコマンドを発行するやり方:

  1. 1 つ以上の Command Descriptor(Command TRB の別名)を Command Ring に配置する.
  2. Host Controller Doorbell を鳴らす.

コマンドを発行するためには,発行先の Slot をまず有効化する必要がある.Enable Slot コマンドにより有効化できる(xHCI 規格書 4.5.3).

Event Ring

コマンドに対する結果などが通知されるキュー. xHCI 規格書 4.9.4 Figure 19: Segmented Event Ring Example

Interrupter 毎に Event Ring が用意されている. 0 番目の Interrupter(Interrupter 0)に対する Event Ring を “Primary Event Ring”,その他の Interrupter に対するものを “Secondary Event Ring” と呼ぶ.

各 Interrupter は Interrupter Register Set というレジスタセットを持っていて,その中の ERSTBA,ERDP がそれぞれ Event Ring のベースアドレス,Event Ring の読み出しポインタを示す. Interrupter Register Set は Runtime Registers(Capability Registers の RTSOFF でオフセットを取得できる)に存在する. Interrupter 0 のための Interrupter Register Set 0 は,Runtime Registers の 0x0020h にある.

各 Event Ring は実際にはいくつものセグメント(Event Ring Segment)からなる. 各セグメントのベースアドレスとサイズ(Event Ring Segment に格納できる TRB の数)は,Event Ring Segment Table(ERST)に設定されている. ERST がどこにあるかは,Event Ring Segment Table Base Address(ERSTBA,上述)レジスタに設定されている.

TRB のためのキューは 3 種類ある.Transfer Ring,Command Ring,Event Ring. Event Ring だけ,xHC がプロデューサ,OS がコンシューマとなる.他の 2 つは逆.

xHC は Producer Cycle State ビット(=PCS,初期値 1)を持っていて,書き込みポインタが Event Ring を 1 週するたびにビットを反転させる. xHC が Event TRB をキューに突っ込むときに PCS ビットを TRB の Cycle ビットに設定する.

OS も Consumer Cycle State ビット(=CCS,初期値 1)を保持し,読み込みポインタが Event Ring を 1 週するたびにビットを反転させる. 読み込んだ TRB の Cycle ビットが CCS と一致すれば有効な要素,一致しなければ無効な要素と判断できる. ビットが一致しない TRB を読み込んだら,その TRB の先頭アドレスを Event Ring Dequeue Pointer(xHC 側が保持する読み込みポインタ)に設定すると,xHC はそこまでのデータが全部 OS 側で処理されたと認識する.

コントロール転送

例えば USB デバイスのディスクリプタを得たいときには,コントロールパイプ経由で Setup/Data/Status ステージを実行する. xHCI ではそれぞれ Setup Stage TRB, Data Stage TRB, Status Stage TRB というのが用意されている.

これらステージの実行例が Figure 7 にのっている.

Get Descriptor

USB デバイスに関する各種の情報を扱う Descriptor を読み込む操作が Get Descriptor.

DEVICE descriptor にはベンダ ID や USB クラスコードなどが記載されている.

Universal Serial Bus 3.1 Specification には Descriptor の構造が載っている.

Descriptor Types Value
DEVICE 1
CONFIGURATION 2
STRING 3
INTERFACE 4
ENDPOINT 5
Reserved 6
Reserved 7
INTERFACE_POWER1 8
OTG 9
DEBUG 10
INTERFACE_ASSOCIATION 11
BOS 15
DEVICE CAPABILITY 16
SUPERSPEED_USB_ENDPOINT_COMPANION 48
SUPERSPEEDPLUS_ISOCHRONOUS_ENDPOINT_C OMPANION 49

xHCI のエラッタ

チップセットによってはホストコントローラが規格通りに動かないことがある. メーカーにより公式に認定されたハードウェアの問題はエラッタとして公開されている.

例えば “N-series Intel Pentium Processors and Intel Celeron Processors Specification Update (Revision 016, Dec. 2018)” にはこんなエラッタがある.

“xHCI Host Controller Reset May Lead to System Hang”

解決策としては HCRST ビットを立ててから他のレジスタを触る前に 1ms 以上のウェイトを入れましょう,ということのようだ.

その他にも USB 関連のエラッタがあるので,USB ドライバを書く際には参考にするとよいだろう. また,きっと N シリーズ以外のチップセットにもエラッタがあると思うので,対象とするチップセットの仕様書は要チェック.