-
Notifications
You must be signed in to change notification settings - Fork 5
USB Memo
USB のホスト側の制御(特に xHCI)についてのメモ書き
2017/08/05 by uchan 2017/10/27 updated by uchan
USB を制御するには,いくつかの層のドライバを書く必要がある.
- USB クラスドライバ:例えば HID(キーボードやマウス)とか,Web カメラとか用のドライバ
- USB ドライバ:USB 規格で規定された,データのやり取りとかを実装する部分
- ホストコントローラドライバ:xHCI など,ホストコントローラを制御する部分
- PCI ドライバ:ホストコントローラは PCI バスに繋がることが前提になっているので,PCI ドライバも必要
USB 規格はあくまでもデータのやり取りの形式(フレームの構造,USB 機器とやり取りする際のプロトコルなど)を規定している. USB 規格と物理層をつなぐのがホストコントローラで,これは USB 規格とは別物.
ホストコントローラ規格には幾つか種類がある:UHCI, OHCI, EHCI, xHCI
- USB
- USB 規格の解説 http://wiki.osdev.org/USB
- xHCI
- インテルの xHCI 規格書 xHCI for Universal Serial Bus: Specification
- PCI
xHCI のレジスタは大きく 2 箇所に存在する.
- PCI コンフィギュレーション空間のレジスタ:MMIO 空間のスタートアドレスを設定する BAR を含む.
- MMIO 空間のレジスタ:こちらがメイン.USB 機器に送るデータをためるキューなどはこちらにある.
PCI コンフィギュレーション空間の BAR で 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 の各レジスタはリンクリスト状に連なって存在する.
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 はすべて read-only なので書き込み制約はない.
読み込みについても特に記載がなく,また 1 バイトのレジスタが存在することから,バイト幅での読み込みができると考えて良さそうだ.
xHCI Spec 5.4 Host Controller Operational Registers によれば,特別に記載がない限りすべてのレジスタは Dword で読み書きする必要がある(規格書には読み込み時のビット幅制約しか記載がないが,恐らく書き込み時も Dword で行う必要があるだろう). 必要なら Dword で読んだ後でビットマスクをかける. Dword 中の特定の場所だけ変更したければ,read/modify/write を行うべし.
書き込みだけでなく読み込み幅の制約があるのは,Operational Registers は xHC によって変更されることがあるためだろう. 一回の mov で 32 ビットを読まないと,一貫性のある結果にならないことがあり得る.
xHCI Spec 5.5 Host Controller Runtime Registers にある.基本的に Operational Registers の読み書き制約に準ずる.
ただし Runtime Registers に存在する Qword のアドレスフィールドには Qword 幅で書き込む必要がある.
xHCI Spec 5.6 Doorbell Registers によれば,Doorbell Registers は全部 32 ビット幅であり,Dword 幅で読み書きをする必要がある.
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 でも可 |
xHCI 規格書の 1.6 "Terms and Abbreviations" 参照.
- Device Slot
- "Device Slot" は個々の USB デバイスに紐づけられた xHC のインターフェース(DCBAA の 1 つのエントリ,1 つの Doorbell Array レジスタ,1 つの Device Context)を意味する.
- Device Context Base Address Array (DCBAA)
- 1 つの Device Slot を 1 つの Device Context データ構造と紐づけるもの.
- DCBAA の場所は Operational Registers の DCBAAP から指される.
- DCBAA は MaxSlotsEn + 1 個のエントリを持つ配列(となるようにプログラマがメモリを用意する).
- Device Context
- 個々の USB デバイスを記述するデータ構造.
- 1 つの Device Context はコンテキストデータ構造の配列.1 つの Slot Context と 31 個以下の Endpoint Context を持つ.
xHCI 規格書の 4.2 "Host Controller Initialization" にいろいろ書いてある. レジスタやメモリ上のデータ構造を初期化し,最終的に USBCMD レジスタの Run/Stop ビットを 1 にするとコントローラが動き出す.
- Host Controller Reset(USBCMD.HCRST に 1 を書き込む)
- PCI コンフィグレーションレジスタを除くレジスタ群が初期化される
- Primary Interrupter(Interrupter Register Set 0)の初期化
- R/S ビットを 1 にする前に Primary Interrupter レジスタ群を初期化する必要がある
- 最低限 Event Ring 関係のレジスタに設定を行う
この後,接続されているデバイスの列挙とそれぞれのデバイスの初期化を行う. やはり,電源投入時から接続されている USB デバイスについては UEFI が初期化してくれているので,やるべき作業はとても少ない. これは UEFI の実装に依るかもしれないが,UEFI の OSS 実装である OVMF(を載せた QEMU)では,USB デバイスの一般的な初期化は完了した状態で OS に処理が渡ってくる.
QEMU に -device usb-kbd
を指定すると PS/2 キーボードの代わりに USB キーボードが接続される.
OS に処理が渡った時点でこの USB キーボードは状態 "Configured" になっており,USB デバイスアドレスやいくつかのエンドポイントなどが設定済みだ.
ただ,USB キーボードから PC 方向のエンドポイントが Disabled になっているようなので,Configure Endpoint コマンドを発行する必要がある.
Table 54: Data Structure Max Size, Boundary, and Alignment Requirement Summary という表に,各データ構造のメモリ制約が記載されている. アライメント制約は忘れがちなので注意すべし.
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 つ以上の Command Descriptor(Command TRB の別名)を Command Ring に配置する.
- Host Controller Doorbell を鳴らす.
コマンドを発行するためには,発行先の Slot をまず有効化する必要がある.Enable Slot コマンドにより有効化できる(xHCI 規格書 4.5.3).
コマンドに対する結果などが通知されるキュー. 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 にのっている.