前置き
本記事は「ゼロからのOS自作入門」のメモになります。
仕事上OSの知識があってもいいなと思って、取り組むことにしました。
各章に対し、1記事をOutputする予定です。
前記事:https://qiita.com/fuji3195/items/edcc36a90571ed922eef
参考として,以下のように章を学んでいます.
2~3周くらいは読まないと,中身の理解はできないです.
- まず章を全部読む
- 読み返しつつ手を動かして動作確認
- 書籍に載っている部分はプログラムをコメントアウトして書籍のコードを写経する
学ぶこと
- PCIについて
- USBドライバとxHCIの解説 (6-2)
- PCIデバイス概要 (6-3)
- マウス入力の実装
- カーソルの描画 (6-1)
- 長方形の描画 (6-1)
- ポーリングによるマウス入力 (6-4)
この章は文字が多く,今までと比べてヘビーに感じる.
そもそもPCIが難しいのが原因.
マウスカーソルと長方形の描画
マウスカーソルについては,前章の文字の入力と同様にすればよい.
- あらかじめカーソルの形を模した二次元配列を用意
- 適当な位置を先頭に,そこからdx,dyでずらして描画する
また,Windowsっぽい画面に変えるために,FillRectangle()
とDrawRectangle()
を実装する.
Fillのほうが長方形を塗りつぶすもので,Drawは長方形の枠だけを描画する.
これらを所定の位置に置くと,下のようにWindowsっぽい画面が描画できる.
USBドライバ
基本的には説明なので,以下にキーワードをまとめておく.
名称 | 意味 |
---|---|
USB | Universal Serial Bus |
Serial | 一本の信号線を使って,信号が1bitずつ送られることを意味する.逆に複数の信号を同時に送る場合はpallarel busと呼ぶ.pallarelよりもserialの方が高速化しやすい. |
ドライバ | コントローラや制御チップのようなハードウェアを扱うためのソフトウェアのこと.OSの一部といえる.USBドライバは,個々のUSBに搭載するターゲットドライバとUSBホストに搭載するホストドライバに分かれるが,今回はホストドライバを作っていく. |
xHCI | ホストコントローラの規格.ほかにもOHCI/UHCI/EHCIなどがあるが,規格が古いのでxHCIだけ考慮すればよい. |
API | Application Programing Interface. SWがほかのSWと相互にやり取りするのに使うインターフェース. |
クラスドライバ | USBターゲットの種類ごとに作るドライバ.HIDクラスやオーディオクラス,マスストレージクラスなど.クラスごとに動作が全く異なるため,それぞれ作る必要がある. |
なお,「USBドライバはkernel/usbにある」とあるが,osbook_06b時点ではソースコード自体は存在しない.なんで...
PCIデバイス探索
ホストコントローラドライバの下にPCIバスドライバを用意するため,PCI接続されたマウスを探す.
前提として,PCIの仕組みを簡単に知る必要がある.
PCIに準拠した機器は,内部に以下のような256byteのPCIコンフィグレーション空間を持つ.
0x40以降はDevice dependent region
という,デバイスごとに用意されている情報が入る.
この情報はCONFIG_ADDRESS
レジスタとCONFIG_DATA
レジスタを使って読み書きを行う.
CONFIG_ADDRESS
で読み書きしたいPCIコンフィグ空間の位置を指定し,CONFIG_DATA
でその値を読み出す.
CONFIG_ADDRESSレジスタの構造
bit | 内容 |
---|---|
31 | Enable bit. 1にするとCONFIG_DATAの読み書きがPCIコンフィグ空間に転送される. |
30:24 | 予約領域 |
23:16 | バス番号.0-255の値をとる.lspciの2番目の要素の部分 |
15:11 | デバイス番号.0-31の値をとる.lspciの3番目の要素の部分 |
10:8 | ファンクション番号.0-7の値をとる.lspciの最後の要素の部分 |
7:0 | レジスタオフセット. これでCONFIG_DATAのどの部分を選択するかを決める. 4byteごとにデータを取得するので,下位2bitはMaskする. |
lspciで例えば以下のような情報が得られる.
これは各機器に登録されている情報で, 先頭から2,3,4番目の数字 00:00.0
がそれぞれバス番号,デバイス番号,ファンクション番号に対応している.
CONFIG_ADDRESS / CONFIG_DATA レジスタの読み書き
アセンブラ関数を使う.
IOアドレス空間の0x0cf8 / 0x0cfcに存在することになっているため,これは定数にする.
アセンブラは引数の順番が決まっており,RDI --> RSI --> RDX --> RCX --> R8 --> R9 という順番で引数がレジスタにアサインされる.
そのため,例えばInOut32(uint16_t addr, uint32_t data)
では,di = addr
, esi = data
となる.
diは16bit, esiは32bitで使うときの形式であることに注意が必要.
out dx, eax
はDXに設定されたIOポートアドレス ( = addr --> diに入れたもの)に対して,EAXに設定された32bit ( = data --> esi)を出力する.
in eax, dx
は逆に,指定したIOポートアドレス( = addr --> di)から32bit整数を入力して返す.
System V AMD64 ABIの仕様により,戻り値はRAXなので,EAXもそのままでよい.
PCIデバイスの探索
アセンブラで作った関数を,C言語ベースの関数に焼き直す.(list 6.10)
その後,再帰的にデバイスを探索する.
まず,00:00.0 = Host Bridge
のPCIコンフィグ空間からヘッダタイプを読み出す.
header typeは8bitで,bit7が1の時はfunction0以外にもPCIデバイスがつながっていることを示す.
シングルファンクションの場合,00:00.0
のbit7は0になっている.
そのため,bus 0のみ調べればよい.
マルチファンクションの場合,00:00.0
のbit7は1になっており,その時1以降のfunctionで接続されたデバイスが分かるようになっている.
うーん,ここら辺はもう少しPCIについて詳しくならないと理解できなさそう...
ひとまず,PCIの256x32x8の65536個のPCIの接続を,PCIの仕様を生かして簡単にサーチできるようにした,というニュアンスを受け取った.
よく見ると,ScanFunctionの中にScanBusがあるので,PCI<-->PCIみたいなつなぎ方もできるようだ.
ReadClassCode()
はクラスコードを読み出す.
bit | 中身 | 例 |
---|---|---|
24:31 | Base Class. デバイスの大雑把な種別. | 0x0c = Serial通信Controller |
16:23 | Sub Class. デバイスの細かな種別 | 0x03 = USB (Base classが0x0cの時) |
08:15 | Programing Interface. レジスタレベルの仕様 | 0x20 = EHCI (USB 2.0), 0x30 = xHCI (USB 3.0) |
最後に,見つけたデバイスをdevicesに入れてる部分を追加し,PCIデバイスの列挙をmain.cppに記述する.
実際に実行すると以下のような画面になる.
ポーリングによるマウス入力
Log()
Log()
はprintkの代わりに使う出力関数.
先頭にLog Levelを入れることで,どの程度の意味を持つLogなのかが分かりやすくなる.
実装自体は非常に簡単.
PCI busからUSBを見つけ出す.
PCI接続は全探索したので,そこからUSB機器を探す.
PCIのコンフィグ空間から以下の条件を見つければよい.
- Base Class = 0x0c : シリアルバスコントローラ全体を示す
- Sub Class = 0x03 : Base Classが0x0cの時は,USBコントローラを示す
- device = 0x30 : USBコントローラが指定されているなら,xHCIを示す
BARの読み出し
BAR (Base Address Register)の中には,MMIO (Memory Mapped IO)が記録されている.
BARは0~5まであり,それぞれ32bit持っているが,MMIOはBAR0に格納されている.
USBの制御設定
書籍ではIntel Panther Point (Ivy Bridge : Intel Core-i 第3世代) に対応するためにEHCIをケアしているが,自分のPCはIvy Bridge世代ほど古くはないので,EHCIをケアする部分は不要.
xHCを起動し,マウスを探す.見つけたら,接続まで行う.
できたら,マウスを動かすのに伴って処理データが発生するので,それをwhile()文で処理していく(pollingという).
今回はpollingを使い,次からは割込み方式を使う予定.
逐次処理はpollingのほうが効率がいいが,マウスやキーボードのようなたまにしか発生しない動作は割込み方式のほうが効率がよい.
マウスカーソルクラスを導入し,main関数でインスタンスを生成するようにすると,動くようになる.
書籍の通り,カーソルがついてくるが,下の黒色の部分はマウスカーソルが通過すると青くなるのが分かる.
まとめ
5章から難易度と量が一気に上がった.
マウスをPollingによって動かす方法について学んだ.
PCI / USBの仕様を理解していないと,内部がほとんど理解できないため,非常に苦しい.
次回は割込みとFIFOについて.
割込みにすると,マウスを動かすのは効率的になるらしい.