大原雄介の最新インターフェイス動向

USB 3.0 その3



 物理層にPCIe Gen2を使う事で、リンク速度は500MB/secを確保する目処は立ったが、その上位をどうするか? というのが次なる問題である。USB 1.1/2.0のプロトコルをそのまま実装する、という案は比較的早期に放棄されたようだ。理由は

・全二重通信が可能なUSB 3.0で、半二重通信をベースにしたUSB 1.1/2.0のプロトコルを使うのは無駄が多すぎる。

・半二重であることを除いても、そもそもUSB 2.0の通信方式には無駄が多いので、これを省きたかった。

・省電力のための仕組みをUSB 1.1/2.0では実装しておらず、これを追加で実装すると、既存のUSB 1.1/2.0のプロトコルとの互換性が損なわれる可能性が高い。それであれば、最初から新プロトコルを実装した方が早い。

というあたりと考えられる。1つ目と3つ目については比較的理解しやすいだろうが、2つ目についてはちょっと説明が必要であろう。

 図1は、USB 2.0の場合のパケットのやり取りの例を示したものだ。ここでは、USB HDDのようなものからデータを読み出すケースを考えてみる。

 USBにおいて、全てのデバイスは自発的に動作を行なうことはない。このため、USBホスト側からのリクエストを受けて、初めて動作することになる。このため、ホストから「あるブロックを読み出してホストに転送せよ」という要求(I/O Req)がまずホストからデバイスに送られる。これを受け取ったデバイスは「その命令を受け取った」というACKを返してから、実際に自分のデバイスのアクセスを行なうことになる。

 しかし、HDDアクセスには当然それなりの時間がかかることになる(それがどの位掛かるか、というのはデバイスI/Fはもとよりホストもわからない)。なので、デバイスが実際にHDDアクセスを掛け、データを取り込んでいる間も、ホストは定期的に「もうデータの転送準備が出来たか?」という問いかけ(Result Request)をデバイスI/Fに対して送り続ける。もちろんアクセスの最中はまだ準備ができていないので、この場合デバイスはホストに"NRDY(Not ReaDY)"を毎回送り返すわけだ。

 そのうちにHDDからの読み取りが終了、ホストに送り返す準備が出来るので、その次に来たResult Requestに対してデバイスは"RDY(ReaDY)"を送り返す。この"RDY"が返ってきたら、初めてホストはデータの転送要求(Xfer Req)をデバイスに送り出し、これを受けてデバイスがDATAを返すというわけだ。ここからわかる通り、USBでは転送をポーリングで監視しているために、これに伴うCPUの負荷が馬鹿にならない。

 USB HDDを使う際にやけにCPU負荷が掛かる、と感じられる人は結構いたと思う。筆者の場合、非力なマシン(Core Solo 1GHzを搭載した富士通のLIFEBOOK U)にUSB 2.0対応のマウスを接続すると、いきなりCPU負荷がガンと上がってしまい、むしろ使い勝手が落ちてしまうという経験をしたことがあるのだが、その理由の大半を占めるのがこのポーリングというわけだ。

 加えて言えば、USB 2.0ではホストと全てのデバイスが事実上1本の共用線で繋がっている形になる。だから、USB HDDのポーリング中は、他のデバイスが転送を行なったりすることができないわけで、これもプロトコル上は無駄が多い。

 ではUSB 3.0ではどうなるか? というと、図2のような転送が可能となる。まずI/Oリクエストをホストからデバイスに送るのは一緒だし、その後1回ホストからデバイスにResult Requestを送るところも一緒である。問題はその先である。ホスト側は、一度NRDYが返ってきたら後はその状態で待機することになる。一方デバイス側はそのままHDDアクセスを行ない、必要なデータを取得した段階でホストに改めてRDYを送り出す。これを受けてホストはXfer Reqを転送、これの返答という形でデバイスから読み取ったデータが送り出されることになる。

【写真1】Protocol Layerの新機能。前回の図で言えば、上から2番目、Transaction層に関わる部分である

 この方式により、USB 3.0ではCPU負荷を劇的に減らし、かつ転送効率を大幅に上げることに成功した。PCIeではこれを「Asynchronous notifications」と称する(写真1)。通常Asyncronous notificationsでは、通常のデータ線とは別に割り込み通知を行なうためのInterrupt Lineを用意し、これを使って通知を行なう(PCIとかISAなどがこの代表例)方式を取る。PCIeの場合、割り込み線は用意しない代わりにこうした非同期通信専用パケットを用意し、割り込みなどはこちらを使うという形で実装しているが、USB 3.0ではNRDYとRDYを組み合わせる形でこれを実現しているあたりがスマートである。

 さらにこれを応用する形で、Out of order completionも実装された。これは何か? というと、図3のような転送が可能になったということだ。図2では転送命令が1個だけだったが、今度はReq 1~Req 3という3つの読み出し要求を順番に投入することを考えてみる。USB 2.0の場合では、図1が3つシーケンシャルに並ぶ(つまり所要時間は単純に3倍)形になるが、USB 3.0ではまずまとめて3つのリクエストを順次投入することが可能である。で、このリクエストは順次HDDに投入されるわけだが、HDD側もSATAだとNCQをサポートしている可能性があるから、そうすると必ずしも入れた順にデータが出てくるとは限らない。だからといって、入れた順になるように間にバッファを噛ませるのもボトルネックが増えるだけである。

 そこでUSB 3.0では、I/Oが終わった順にこれを返すことが可能である。図3の例で言えば、Req 2に対応したI/O #2がちょっと時間がかかり、先にReq 3に対応したI/O #3が終わったケースを想定している。この場合、デバイスは各々のI/Oが終わったタイミングでホストに対し、それぞれのRDYをその場で返すことができる。これに応じてホストは、Req 1→Req 3→Req 2の順でXfer Reqを発行し、各々の結果を受け取るというわけだ。

 こうしたメカニズムが可能になったのは、そもそも送受信を分離できたことと、もう1つはPeer-to-Peerでの接続形態に切り替えた事が大きい。USB 2.0の場合、送信と受信が1つの線で共用されていたから、送受信がぶつからないようにホストがUSBのツリー全体を管理しており、この結果としてデバイスからホストに「自発的に」データを送ることは不可能だった。ところがUSB 3.0では送信と受信が分離されたので、ホストがデータを送っている最中でもデバイスからデータを送りだすことが可能になっている。

 もう1つ重要なのが、Peer-to-Peerの接続になっていることだ。これは物理的な話というよりも論理的な話である。例えば図4のような構成を考えてみよう。ここでホストがあるリクエストをデバイス 1に送ろうとする。その場合、ホストはリクエストをRoot Hub(ホストの中にある赤い丸)に送る。すると無条件でRoot Hubはそのリクエストを配下の全ポートに送り、さらにその配下にあるHubもやはり全ポートにコピーして送るので、結局図5のようにブロードキャストしているのと同じ事になる。もちろん各デバイスは受け取ったリクエストの宛先を見て、自分宛でなければ破棄するから実害は無いのだが。逆にリクエストに対するレスポンスに関しては、宛先がホストしかない(USBではデバイス→デバイスという転送は原則として不可能である。これが可能なのはUSB On-The-Goという別規格である)から図6のように無駄にBroadcastすることなく伝わる。ただし、USB 2.0では複数のデバイスが同時にResponseを返すことは(当然)想定していないので、Hubのスペックにもこうした機能は盛り込まれていない。

 これに対し、USB 3.0ではブロードキャストが原則として廃止された(全デバイスに通知を行なう必要があるPowerDownとかResetといった管理用に、専用のブロードキャストのモードがわざわざ新設されたほどだ)。なのでホスト→デバイスもデバイス→ホストも、基本的には常にPeer-to-Peerとなり、関係ないデバイスに対して無駄にパケットが送りつけられることは無くなった。このため、USB 3.0のHubはちゃんと宛先を見てルーティングするほか、複数のポートからホスト向けのパケットを受け取った場合、これをきちんと扱えるようにすることも義務付けられた。このあたりは(ちょっとたとえが古いが)EthenetのHubに近い。昔のEthernetはいわゆるDumb Hubで、自分のあるポートで受け取ったパケットを無条件で他のパケットに送り出すという仕組みだった。ところがスイッチングHubが登場し、MACアドレスを見て目的のポートのみに送り出すようになった。USB 3.0のHubの動作は、このスイッチングHubに近い動作になったわけだ。

 こうした仕組みは、同時に省電力にも繋がることになる。デバイス→ホスト方向はともかくホスト→デバイスは、従来だと全てのポートにパケットを送っていたわけだが、USB 3.0では目的のポートにのみパケットを送る形に改められたわけで、無駄な電力を消費しないという観点で改善されたことになる。

 こうした機能を搭載するためにはUSB 2.0までからプロトコルを一新する必要があったが、だからといってPCIeのプロトコルを使ってしまうと、(既にPCIeのIPを持っているベンダーには楽だが)実装が面倒、という問題があった。PCIeのプロトコルは、最大32レーンでの転送にも耐えられるように設計されており、また柔軟な構成を取るためにかなり複雑なものになっていた。その最たるものがフロー制御である。

 PCIeでは「Credit Base Flow Control」と呼ばれるものが利用されている。これは簡単に言えば「転送を開始する前に、どれだけまとめて転送が行なえるかを確認し、転送後はその分をさっぴく」というもので、この管理に使われているのがCreditと呼ばれるものである。ちょうどキャッシュカードの与信管理みたいなものだと思えばよい。実際に商品を購入する前に、クレジットカード会社に与信枠が残っているかの確認が行なわれ、購入後には与信枠からその分が減らされる。で、使った分を支払うと、与信枠が復活するというものだ。これを実現するために、PCIeではトランザクション層とリンク層の両方にまたがる形でフロー制御が実装された。

 当初USB 3.0はこの仕組みをある程度持ち込む形で考えていたようだが、実装の負荷が予想以上に大変だと判断されたようだ。この結果USB 3.0では「バッファを4つ搭載する」という決め打ちのフロー制御が採用された。各ポート毎に必ず4つの受信バッファが設けられ、送信側は4つまでは一気に送ってもいいが、その後はバッファが空いたことを確認してから送信する、という仕組みが実装されている。これによりフロー制御はトランザクション層のみで実装が完結した。

 その分リンク層は負荷が減ったとも言えるのだが、その代わりにLink Trainingに新機能が追加された。PCIeの場合、(もうじきGen3も登場するが)今のところGen1/Gen2のどちらかで通信するパターンのみを考えればよいのに対し、USB 3.0では「相手がUSB 3.0に対応しているかどうか判らない」という可能性がある。これはUSB 3.0のホストにUSB 2.0 デバイスを繋ぐ場合や、逆にUSB 2.0ホストにUSB 3.0 デバイスを繋ぐ場合に効いてくる話である。

 特に後者の場合、相手が従来のUSB 2.0ホストのままだから、「まずUSB 3.0での通信にトライして、ダメだったらUSB 2.0の通信にトライする」を悠長に行なっていると、USB 2.0での通信にすら失敗する可能性があるし、ヘタをすると電力が間に合わない可能性がある。USB 2.0のホストはポートあたり最大500mAを供給できるはずだが、バスパワーのUSB Hubでは100mA以下しか供給されない場合もある。一方USB 3.0ではポートあたり最大900mAが可能だし、現実問題として100mA程度ではUSB 3.0の物理層が電力不足で誤動作する可能性もある。そこでUSB 3.0の信号レベルでの通信に先立ち、LFPS(Low Frequency Periodic Signaling)と呼ばれる方法でハンドシェイクを行ない、ここで成功したらUSB 3.0での通信を試すという仕組みが取り入れられた。LFPSは名前の通り信号の速度も遅く、省電力で動作可能であるから、100mA程度の供給があれば十分動作するというわけだ。

 こうした諸々の機能追加や変更が行なわれた結果、USB 3.0の実装は、「PCIe Gen2 x1レーンの物理層+USB 2.0の論理層」というレベルのものではなく、「PCIe Gen2 x1レーンの物理層を元にした独自物理層+PCIeの論理層をお手本にしたトランスポート層+USB 2.0に良く似たインターフェイス」という形になった。このあたりを良く踏まえたあるベンダーは「確かに当社はPCIeの物理層のIPも持っているし、USB 2.0のコントローラやデバイスのIPも持ち合わせているが、USB 3.0は全然別物なので、今のところ手がける予定はない」と2008年頃語っており、実際このベンダーは未だにUSB 3.0に参入していない。見かけより難易度が高いのがこのUSB 3.0の実装といえよう。

バックナンバー

(2010年 9月 2日)

[Text by 大原 雄介]