読者です 読者をやめる 読者になる 読者になる

livaの雑記帳

OSとか作ってみたい

自作OSにUSBを実装する

f:id:liva_h:20170322071340j:plain

はじめに

この記事は自作OSでUSBキーボードを実装しようとして半年くらい四苦八苦していた経緯の備忘録です。時系列で書いてくので、読みにくかったらごめんなさい。

USB周りの経験値がゼロな状態から実装した結果、遠回りやら勘違いやらいろいろしているのですが、そこら辺の苦労話が少しは伝わると良いなぁ、と思って書いたものです。

あと、上級者向けに伏線となった部分を赤字にしてあります。読んでいくにつれ、この伏線が回収される(=僕の努力が水の泡になる)のですが、是非伏線の段階で「m9(^Д^)プギャーwwwwww」してみてください。

 

あと、タイトルは若干釣りです。それも最後まで読んでもらえれば分かるかと。

ソースは以下の通り。

github.com

 

背景&きっかけ 

 

自分でOSを書いていると、キーボードで文字を入力したくなるわけです。C言語の入門でscanfとかやるのと同じですね。

キーボードからの文字入力を受け付けるにはキーボードドライバを書く必要があります。世にはいろいろなキーボードがあるのですが、恐らく一番ドライバの実装が簡単なのはPS/2キーボードです。(今時の中高生にPS/2って言って伝わるんだろうか

 

もちろん、今時PS/2端子なんて物凄く希少で、今手元のデスクトップマシン4台を見渡した所、1台にしかついていませんでした。自作PC向けのマザボなら結構残ってるのではないでしょうか。少なくともノートPCには絶対ついているわけないし、デスクトップでもメーカーPCとかでは付いている例は殆どないです。

 

今の主流は当然USBのキーボードなわけですが、とても有り難い事に今のチップセットにはUSBのキーボードをPS/2キーボードとしてエミュレーションしてくれる機能があります。恐らく、昔のOSにはUSBのデバドラを積んでいるものが少なく、そういったOSでも正常に動くようにエミュレーション機能が実装されたのでしょう。

 

結論としてはPS/2キーボードドライバを実装すれば今時のマシンでもキーボードが使えるはずなんです。はずなんですが、一部のマシンでキーボードが動かない。

最近のマザーだとPS/2エミュレーションが実装されてない物もあるみたいで、そういうのだときちんとUSBドライバを実装しない、なんて事をチラッと聞いたので、どうやらそういう事のようです。

 

というわけで、

PS/2エミュレーションが動かないなら、USBのデバドラ書けばいいじゃん。

と簡単に考えたのですが、これがそもそもの間違いだったのでした。。。

 

USBの仕組み

我らのosdev.org(Universal Serial Bus - OSDev Wiki)曰く、USB3.0を実装するにはXHCIEHCIUHCIOHCIを実装しなきゃいけないのだと。

 

なんのこっちゃ?って感じですね。

 

というわけで、まずUSBがどういう仕組で動くのかを解説します。

 

マザーボードチップセットにはホストコントローラというUSBデバイスを統括するハードウェアが入っています。以下の画像にあるExtensible Host Controllerの事ですね。

 

f:id:liva_h:20170322043351j:plain

(引用元)

 

OSはこのホストコントローラと通信を行う事でUSBデバイスを制御します。つまり、USBデバイスを認識するためには、このホストコントローラを制御する必要があり、そのためのデバイスドライバが必要になるわけです。(上の図でいうExtensible Host Controller Driver)

次にホストコントローラ経由でUSBデバイスと通信する上ではUSBの仕様で定義されたパケットを送らなければいけません。これを提供するのがUSBドライバです。

最後に、各USBデバイス固有のドライバ(USBパケットの上に特定のデータを載せ、固有のデバイス操作を実現する)が必要になります。(上の図のClass Driver)

 

つまり、USBデバイスをOSから制御するためには以下の3つが必要となります。

 

  1. USBドライバ
  2. ホストコントローラドライバ
  3. USBデバイスドライバ

 

今回はUSBキーボードですが、例えばUSBメモリを使いたければ、3のUSBデバイスドライバであるUSBメモリドライバを追加で実装する必要があります。

先程はXHCIが、EHCIがとか言っていましたが、これは2のホストコントローラドライバの事で、これらを実装してもUSBデバイスが使えるようになるわけではないんですね。

 

これだけでもお腹いっぱいなのですが、話はまだ続きます。

USBにはいろいろバージョンがあって(3.0とか2.0とか1.1とか)、各バージョンごとに異なるホストコントローラを使う事になります。つまりUSBのバージョン事に2のホストコントローラドライバを実装しなければいけません。osdev.orgから引用した記述はこの事で、USB1.1ならUHCIOHCIという2つのホストコントローラ、USB2.0ならUHCIOHCIEHCIUSB3.0ならUHCIOHCIEHCIXHCIを全て実装する必要があるとあります。

 

UHCIの実装(前半)

いろいろ書きましたが、USB1.1を動かすだけならUHCIOHCIを実装すれば良いのです。しかもUHCIOHCIかはマザボによって変わるのですが、Intel系のチップセットならUHCIなので、まあUHCIを実装すればとりあえず多くの場合は十分です。最近のPCは大体USB3.0対応ですが、それでもUHCIさえ動かえば、USB3.0ポートに繋いだキーボードが使えて幸せになれるという魂胆です。

OHCIIntelマザー以外が採用しているコントローラです。でも今時Intel以外のマザーなんてあるんですかね。AMDとか?AMDなんて今時だれが使うんだ。Ryzenが出たので、意外とAMDマザーがこれから増えるかもですね。あと良く使われる製品ではAppleOHCIです。Apple製品で自作OSやりたい人がいるとは思えませんが。

 

というわけで、UHCIドライバの実装に取り掛かりました。結構仕様理解するのが大変だったのだけど、ブログにするとあんまりこの苦労が伝わらないのが悲しいですね。

 

UHCIドライバを途中まで書いた所で、UHCIでキーボードが繋がっているか検出してみたのですが、QEMUでも実機でもキーボードが検出されませんでした

 

EHCIの実装

この時は仕様をよく理解してなかったのに加えて、「実験に使ってるキーボードはUSB2.0のキーボードだからEHCIじゃないと検出できないよなぁ」、と考えた結果、EHCIを実装する事にしました。UHCIの実装は途中でおあずけ。

UHCIの仕様と比較してEHCIは結構似ているので、仕様の理解はそこまで面倒ではないです。というわけで実装しました。

 

で、QEMUではEHCIでUSBキーボードが認識されて、めでたしめでたし。ってのが昨年末。じゃあこの三ヶ月間何やってたのか、となるわけですが、実はここからがめちゃくちゃ長かった。(まあいろいろあって本当は一月くらいしか取り組んでないんですけども)

 

まず、実機でEHCIからUSBキーボードを認識しようとしても、USBキーボードがつながっていない!

ここで伏線回収の一つ目。USBキーボード(Realforce)をMacに繋いでデバイスマネージャーを見ると、以下のように表示されます。

 

f:id:liva_h:20170322073040p:plain

これ、速度が完全にUSB1.1なんですよね。だからEHCIで検出されるわけがない。

最近のキーボードはUSB2.0でしょ、なんて思い込んでいたのですが、キーボードが帯域を必要とするわけもなく、殆どのキーボードはUSB1.1で接続されます。結論としては、EHCIの実装は無駄だった、という事ですね。

そして伏線回収の二つ目。実機ではUHCIはきちんとキーボードを検出していたのですが、UHCIドライバのバグでビットの扱いを間違えていたため、検出していない物と勘違いしていたのでした。ちなみに、仮にキーボードがUSB2.0であったとしても、EHCIを初期化していなければUHCIからちゃんと認識されます。つまり二重の意味でEHCIの実装は無駄でした。

 

あと、QEMUEHCIからUSBキーボードが見えていたのは、どうやらQEMUにはUSB2.0のキーボードとUSB1.1のキーボードの二種類のデバイスが実装されているらしく(きちんと検証していないので嘘かもしれない)、この時のオプション指定はUSB2.0キーボードだった模様。この後USB1.1のキーボードを再指定しました。

 

UHCIの実装(後半)

UHCIの実装は途中まで実装して放り投げてしまったわけですが、これをきちんと実装する事になりました。んで、実装しました。はい。

まあEHCIの実装時にUSBキーボードドライバとUSBドライバはもう実装済みでQEMU上でもデバッグが済んでいたため、今回は本当にホストコントローラのドライバを実装するだけでした。

で、QEMUでもUSBキーボード&UHCIが動いて、一区切りしたのですが。。。

 

衝撃の事実が発覚

そもそもの話として、USB2.0なマシンでlspciをすると、EHCIUHCI(or OHCI)が見えます。EHCIUSB2.0のデバイスしか管理せず、USB2.0のポートにUSB1.1のデバイスが接続された時はUHCI(or OHCI)にデバイスの管理を肩代わりさせます。(companion Host Controller)だからUSB2.0を動かすためにはEHCIUHCIの両方の実装が必要になる、というわけです。(UHCIOHCIを実装しないとUSB1.1バイスを接続しても使えない)

 

ところが、最近のマシンでlspciすると、XHCIEHCIはあるのですが、UHCIOHCIは存在しないんですよね。で、XHCIの仕様とかを軽く眺めると、「XHCIはcompanion Host ControllerがなくてもUSB1.1USB2.0なデバイスを使えるようにした!めっちゃ革命的!」とか書かれているわけですよ。伏線回収3つめですね。たぶんこれはosdev.orgの記述が誤りで、USB3.0を動かすためにはXHCIだけ実装すれば済みます。むしろUSB3.0なマシン(正確にはSkylake以降?)ではXHCIを実装してないとUSBポートが使えないとかなんとか。(Windows7がSkylake以降だとインストールできない、みたいな記事がネット上にチラホラあったり、BIOSのブートメニューにWindows7のインストールのためのPS/2エミュレーションの設定があったりしますよ)

 

ってことは、頑張ってUHCIEHCIのデバドラを実装したけど、ナウいマシンでは全部無駄!!!

 

実機デバッグ

しかしながら、もうここまで書いたデバドラを捨てるわけにもいかないので、Core2Duoなマシン(かれこれ8年前のマシン)で動かす事にしました。

ところが、うんともすんとも動かない。いくらQEMUで動くからと言って実機では動かないというのは自作OS界隈の常識ですが、それにしたって動かない。

 

答えから書くと、companion Host Controller周りの設定ミス(USB2.0ポートをUSB1.1コントローラに明け渡す処理)とか、割り込みの設定ミス(I/O APICの設定ミスやPCIのlegacy 割り込みの設定ミス)等が重なっていて、めちゃくちゃデバッグが大変でした。

同じマシンでFreeBSDを動かして、各種ハードウェアレジスタの値をダンプして確認しつつ、自作OSでの値を比較して、みたいなのを延々と繰り返す日々。。。

 

で、ここで最後の伏線が回収されます。

最初に、「一部のマシンでキーボードが認識されない(PS/2エミュレーションが動かない)」と書いたのですが、割り込み周りの設定ミスを修正した途端、すんなり動くように。

 

つまり、ここまでやってきた事、全てが無駄だった!!!!

 

まあでもめげずに最後まで頑張った結果、Core2DuoマシンできちんとUSBキーボードが動くようになりましたとさ。ナウいマシンでは相変わらずPS/2エミュレーションでキーボード動かしてるけどね!!!

 

 f:id:liva_h:20170322080705j:plain

USBキーボードから来たパケットをダンプした所。ちゃんとPS/2エミュレーションじゃなくてUSB経由でつながっています。

 

この後どうするの

軽く仕様書読んだんだけど、XHCIの実装ダルそうなんですよね。

誰か僕の代わりに実装してくれないかな。。。。