OS自作~QEMU用ビデオドライバの基礎~
前回のあらすじ
前回の記事では、FrameBufferの取得方法について調べました。
最後に「次回は解像度の取得・設定方法の調査と実装」をすると書いたんですが、ちょっと時間がなかったので今回は取得方法の実装までです。
ビデオドライバーでやりたいこと
解像度とかFrameBufferの取得方法の話を書いてますけど、最終的に作りたいのはビデオドライバです。なので、一度作ろうと思ってるビデオドライバについて具体的に説明します。
まず、前回やったようにPCIeデバイスの探索をして、ビデオカードが見つかると、ベンダーIDを確認して、それに対応するドライバーを連れてきます。
ここでドライバーはGPUとMMIO(Memory Mapped I/O)を通じて情報をやり取りし、解像度やらビット深度やらの設定をします。今回はこれの基礎作りです。
そして、その先にはスペシャルファイルを使ってユーザー空間からフレームバッファーに書き込めるようにして、Xorgのようなグラフィックシステムで制御できるようにしたいんですが、ちょっとこれは先のお話。
MMIOの仕組みの調査
さて、実際にGPUを制御するために使うMMIOの仕様を調べるんですが、ちょっとNvidiaのGPUよりQEMUの仮想ビデオカードのほうが仕様が単純だったので、とりあえず開発しやすくするためにQEMUのビデオドライバを作ることにしました。
QEMUの仕様書はGithubのレポジトリにありました(これです)。見ればわかりますが、単純明快です。前回フレームバッファを取得した時にBAR0,1と一致したのも、ちゃんとPCI Region0を見れば納得できます。
お目当てのMMIOのベースアドレスもPCI Region2にあって、MMIOの詳細も下に書いてあります。この中で重要なのはEDIDとbochs dispi interface registersです。
それぞれ見ていきます。
EDID(Extended Display Identification Data)
まずはEDIDです。とりあえずwikiを見ればわかりますが、要はディスプレイ情報を渡してくれる構造体です。この中に対応している解像度等の情報が含まれているので、これを読み取るのがビデオドライバの最初の仕事です。ただ、wikiの表だとイマイチそれぞれの項目の意味がわかりにくいので、VESAが策定している仕様書を読むといいと思います。
ビデオドライバが知る必要があるのは、とりあえず対応している解像度です。これはStandard Timingsに情報があって、その詳細情報がDetailed Timing Descriptorにあります。
ここも仕様がわかったところで時間がないので構造体だけ簡単に作ったので、コードを示します。
pub struct EDID {
data: &'static [u8]
}
こんだけです。設定ごとに保存することとかも考えましたが、やはりビットそのものを持っていた方があとから融通が効きますし、収まりもいいので少なくとも現時点ではこれを使っていくことにします。
bochs dispi interface registers(BGA)
タイトルの括弧書きがどう見ても略称じゃないんですが、正しくはBochs Graphics Adaptorの略です。EDIDは最初から知っていたのであっさり行きましたけど、こっちは何も知らなかったので調べるのに苦労しました。
苦労して見つけたwikiがこれです。多分QEMU特有のシステムで、要約するとインデックスレジスターに欲しい情報のインデックスを書き込むと、データレジスタから設定の入出力ができるようです。ただし、現在はPCI I/OportではなくMMIOを使ってやっているので、少し読み書きのお作法が違います。
これもまた説明読んでただけだと意味がわからなくて、先程示したQEMUの仕様書に
と書いてあって、混乱していたんですが、どっかのGitのページを見つけて解決しました。つまり、インデックスが小さい方から順にデータが並んでいるから、アドレスにインデックスを左シフトしたものを足せばいい感じになるよってことだったわけです。
これを踏まえてこんなコードを書きました。
enum BGARegisters {
VBE_DISPI_INDEX_ID = 0,
VBE_DISPI_INDEX_XRES = 1,
VBE_DISPI_INDEX_YRES = 2,
VBE_DISPI_INDEX_BPP = 3,
VBE_DISPI_INDEX_ENABLE = 4,
VBE_DISPI_INDEX_BANK = 5,
VBE_DISPI_INDEX_VIRT_WIDTH = 6,
VBE_DISPI_INDEX_VIRT_HEIGHT = 7,
VBE_DISPI_INDEX_INDEX_X_OFFSET = 8,
VBE_DISPI_INDEX_INDEX_Y_OFFSET = 9,
}
unsafe fn bga_write_register(mmio_base: u32, index: u32, value: u16) {
((mmio_base + 0x500 + (index << 1)) as *mut u16).write(value)
}
unsafe fn bga_read_register(mmio_base: u32, index: u32) -> u16 {
return ((mmio_base + 0x500 + (index << 1)) as *mut u16).read()
}
別にenumは作らなくても良かったんですが、可読性上げるために作りました。
これを使ってXRESとかYRESとかを読み出してあげれば解像度がちゃんと取得できました。これにEDIDで取得した情報をもとに新しい解像度を書き込めばビデオドライバの完成です!
次にやること
結局あんまり実装は進んでいませんが、ここまで分かったのは大きな進歩です。解像度の取得・設定方法が分かった時はかなり嬉しかったです。
というわけで、次回は今度こそQEMU用のドライバをちゃんと動くように実装します。
今回は対して実装してないのでGIthubのコミットのリンクは貼りません。