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

livaの雑記帳

OSとか作ってみたい

自作OSにおけるUEFI対応戦略

たまにはtwitterのまとめじゃなくて、ちゃんとした記事でも。

自作OS界隈でUEFI流行ってるというか、なんかUEFIすごーい!って崇めてる人が多いようなのでその雑感。

 

UEFIとは

最近主流のブートファームウェアBIOSの高機能版。

最近のUEFIマザーは大体BIOS後方互換性も実装している。

 

BIOSが互換実装されているというのは大きくて、正直今のマザーならBIOSからMBRブートしても何ら問題はない。

でもまあ、新しいデバイスに対応させたいというのはOSを自分で書きたい人の夢だし(たぶん)、何時後方互換が無くなるかも分からないので、UEFI対応するリソースがあるなら、移るべき。

 

UEFIアプリとして作るべき?

UEFIファームウェアは各種デバイス等を扱う関数を提供していて、ネットワークデバイスさえ簡単に扱えるし、プロトコルスタックも実装されているから、もうすぐにパケットでおしゃべりできて凄いらしい。(使った事はない)

なので、UEFIの豊富なハードウェアインターフェースを活用する事で、簡単にOSが自作できると主張する人もいる。確かにそれは1つの戦略としてアリではあるとは思う。

 

自分でOS作りたい人の悩みとして、世の中に星の数程存在するハードウェアを一々サポートしていたらとてもではないけれど間に合わない、というのがある。(昨今のそこそこ大きなハードウェアのデバドラを自力で書くだけの実力がない、という理由もあるだろう)

「OSを実装したい」人には、「アプリケーション向けの抽象化インタフェースを作りたい人」と「ハードウェアを制御したい人」という二種類の人種が存在すると僕は考えている。このうちの前者に相当する人なら、ハードウェアのサポートはあくまでユーザーの利便性のために渋々実装しなければならない類の事なので、デバドラの実装をUEFIファームウェアに投げてしまうという近道を通るのは1つの手だと思う。

ちなみに、僕は後者の「ハードウェアを制御したい人」なので、ちょっとそういうのとは相容れない感じの人間。僕は組み込みシステムとかはあんまり触りたくないのだけど、意外と組み込み屋さんに思考が近いのかなーなんて最近思い始めた。ただし、ハードウェアを制御するのはOSというよりはデバドラなので、厳密には僕みたいな人は「OSを実装してる」人ではないのかもしれない。

あと、そうそう「OSを自作したい人」の中には「ウィンドウを描画したり、GUIかっこよくしたりしたい」という人が一定数いるのだけど、それはOSではなくウィンドウマネージャなので、gnomeとか作れば良いのではないでしょうか。

 

さて、UEFIファームウェアで楽をしたい人はそれはそれで良いと思っているのだけど、UEFIでデバイス触って遊んでても一切独自性はないので、そういう人は早く独自の抽象化インターフェースを作るのに進むのが良いと思います。

「デバイスとかは直接触りたいんだけど、UEFIを通せば簡単だから、UEFI越しに触ればいいや」、ってのは意味不明なので、デバイスを触りたい人はUEFIを崇めてないで、地道にデバドラ書こう。それしかデバイスを触る近道はない。

 

UEFIアプリはあくまでプログラムローダー

UEFIのコンソーシアム的にはUEFIアプリってのはあくまでブートローダーとして使って欲しいらしい、という事を耳にした。最近の殆どのOSは自前でハードウェアを制御するから、ブートローダーとしてのみUEFIを使えれば良い。僕もそれが正しいと思う。

くどいけど、UEFIを使ってOSを作るという方向性自体は否定しない。OSを作ってやりたい事ってのがデバイスを弄る事以外であるならね。

僕みたいなデバイスを直接弄りたい系の人は、さっさとExitBootServices()(だっけ?)を読んで、UEFIを抜ければ良い。結局UEFI経由でデバイスを触るのは楽だけども、デバイスを直接触れてないから、性能を最大限引き出したりする用途では使えない。ExitBootServices()呼び出すまではUEFIがデバイスを握ってるから、OSから制御する事もできないし。(たぶん。触ってないので、もしかしたら間違ってるかも)

 

grub-efiいいよ

UEFIgrubを殺すのだ、という話も聞いた。もしかしたらUEFIコンソーシアム的にはそうなのかもしれない。でも、フルスクラッチでOS作ってる人程grubを使えば良いと思う。

UEFIアプリを使うとめちゃくちゃローダーが簡単に書けるらしくて、たしかにそれは凄い。grubってめちゃくちゃ大規模だけど、それ相当の物が自分で作れてしまうのは革新的だとは思う。でも、結局ローダーである以上、ローダーとカーネルの間の橋渡しインターフェースとかは自分で考えなきゃいけないわけで、その労力はデカい。

OSを作りたい人はOSが作りたいのであって、ローダーとカーネルの間の橋を作りたいわけではない。本当にそういうのを作りたいのであれば、OSではなくてgrubを置き換える素晴らしいブートローダーを書けば良いだけ。

「全て自分で作りたいからOSを作るんだ!」という人もいて、その心意義は凄いし、見習わねばとは思うし、言いたい事も分からないではないのだけど、残念ながら僕のような非凡な人間にはそんな事をやっている暇はない。OSを作るので精一杯。あと、時々「ハードウェアはUEFIがあるから全部使えるようになるんだ、これでついに全部ゼロからOSを作れる!」という人がいるけど、それは流石によくわからん。自分で全部作りたいと言ってる割にUEFIファームウェアにおんぶに抱っこだよそれ、みたいな。

 

というわけで、ファイルシステム設計したり、プロセス構造実装したり、みたいな目的で自作OSやっているのでないなら、grub使うのが近道なのではなかろうか。UEFIアプリで自作ローダーを作るのは自分のOSがそこそこ大きくなってきて、grubだとやりたい事ができない、ってなった時だと思うなぁ。

 

grubのpf2ファイルの解析

UEFIサポートによりフレームバッファにテキストを描画しなければならなくなったので、grubのpf2ファイルを使って描画する事にしてみた。

UTF-32ってのが良い。vga/textだとasciiしか描画できない。

 

 

日本語表示する機会なんてたぶんないけど、描画できる事は大事。

UEFI対応

諸事情により、UEFIに対応したい。

現状grub-pcからのmultiboot2仕様による起動なので、grub-efiに載せ替えれば動くだろと思ったら動かなかった。

 

とりあえずこれでgrub-uefiからの起動には成功。次は画面出力。

 

 

画面出力はできたのだけど、これまでvga/textに描画してたのが、フレームバッファになってしまったので、フォント描画をやらなければならず、ツライ。その話は別記事で。

自作osでlinuxのhello worldバイナリを動かす

elfのロードまではできているので、実際に実行して、linuxシステムコールを処理する云々の話。

自作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の実装ダルそうなんですよね。

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

 

 

I/O APICのEOIが動かない

 

結局QEMU2.8.0で動かない問題は解決しなかったので、issueを立てた。

github.com

 

 

雑記(3月)