※ 色々と誤りや不適切な部分があると思うので気づいたら直します。
QEMUを触ってみたかったのと、実際にハードウェアと通信する
デバイスドライバを書いてみたかったので、
とりあえずQEMUに仮想PCIデバイス(キャラクタデバイス)を追加しました。
これは単純な機能のテストのみで、実用的なデバイスではありません。
なぜPCIキャラクタデバイスかというと、恐らく最も簡単だろうと判断したからです。
そもそもデバイスドライバ自体書いてみたことがなかったので、
それについてもまとめました。
分量が多くなったので、いくつかに分けています。
(2) 前提知識 (PCI / デバイスドライバ)
[(3) 開発/動作環境]
(http://qiita.com/rafilia/items/ff02c38be2ab01220fcb)
[(4) 仮想デバイス/ドライバの登録]
(http://qiita.com/rafilia/items/ba73703babb92276b55a)
[(5) I/Oポートの使用]
(http://qiita.com/rafilia/items/9e8d62444be472e573c2)
[(6) メモリマップドI/O / ioctl の使用]
(http://qiita.com/rafilia/items/8f2ec3c612d4002c12c0)
[(7) 割り込みの設定]
(http://qiita.com/rafilia/items/b719d8adf1f39d956670)
[(8) DMAの設定]
(http://qiita.com/rafilia/items/90bb23e7f4437bd2dd35)
作成したコードは以下にあります。
https://github.com/rafilia/system_simulation
作成したプログラムと主な機能
-
QEMU仮想デバイス (test_pci_device.c) : QEMU と一緒にコンパイル
- PCIデバイスをQEMUへ登録
- デバイスが使用する I/O ポート / メモリマップドI/O 領域の設定
- PCI コンフィギュレーション空間の設定
- I/O ポート / メモリマップドI/O に対する read/write 関数
- DMA転送/割り込みの発行
-
デバイスドライバ (test_pci_driver.c) : ローダブルモジュールとしてコンパイル
- PCI デバイスとしてのドライバの登録
- キャラクタデバイスとしての登録
- 割り込み、DMAの設定
- 各システムコールに対応する関数の設定
(open/close/read/write/llseek/unlocked_ioctl) - 割り込みハンドラを用意
- I/Oポート / メモリマップドI/O を用いてデバイスと通信
-
ユーザープログラム (test_pci_user.c)
- デバイスドライバに対するシステムコールを発行
(open/close/read/write/lseek/ioctl) - I/O ポート / メモリマップド I/O / 割り込み / DMA 転送を
チェックする関数
- デバイスドライバに対するシステムコールを発行
ユーザープログラムはファイルとしてキャラクタデバイスを扱います。
ユーザープログラムが使用するシステムコールに対応する関数をデバイスドライバに
用意する必要があります。必ずしも全てのシステムコール
をドライバに実装する必要はありません。関数名は基本的に同じですが
間にカーネルが入るために引数が異なります。
また一部関数名が異なるものもあります。
(ユーザー : close, lseek, ioctl / ドライバ : release, llseek, unlocked_ioctl)。
ドライバとデバイスの通信には in/out 命令 (I/Oポートの場合) やread/write
(メモリマップド I/O の場合) を用います。そのため、仮想デバイス側では
in/out, read/write 処理に対応する関数を用意します
(なお、QEMU仮想デバイスではどちらもread/writeという関数名になっています)。
DMA 転送を用いる場合は必要な初期化を設定した後で
out/write 命令などを用いてドライバがデバイスへ通信開始を知らせます。
なお、LinuxはBuildroot を用いて構成したものを使用し、
デバイスドライバはローダブルモジュールとして、Linux 起動後にinsmod/mknodする方針としました。
すべてのプログラムはホストコンピュータ(Ubuntu 64bit)上で
開発を行い、それらをマウントしたディスクイメージにコピーした後に
QEMUを起動しました。
アーキテクチャは 32bit x86 (i386) を対象にしています。
エンディアンの違いや32bit/64bitの違いは特に考えていません。
また今回はどのようにハードウェアと通信するか、といった点のみを考えたため、
実際のドライバで必要となる排他制御(一度に複数のアクセス要求があった場合にどうするか)や、
read/write にバッファを入れるといったことは行っていません。
参考にした本など
デバイスドライバ
Linux デバイスドライバの本には以下の3冊があります。
どれも Kernel 2.6 を対象としており5年以上前の本なのでやや古く、関数名などが変わっている
部分もあるため注意します(ドライバの関数名が ioctl > unlocked_ioctl など)。
特にオライリー本を参考にしましたが、PCIの部分については最後の洋書も参考になりました。
オライリー本の英語版pdfは公開されており、来年第四版が出る予定のようです。
また、PCIの実装についてはこのwebページ (http://free-electrons.com/doc/pci-drivers.pdf) も参考にしました。
QEMU
QEMU には特にまとまった資料がないため、頑張ってソースコードを読みます。
なお、多少のドキュメントが docs/ 以下や各ヘッダファイルに書いてあります。
また、hw/misc/pci-testdev.c といういかにもな名前のソースコードがあったので
これを参考にしました。
はじめ: [(1) 作ったもの]
(http://qiita.com/rafilia/items/f7646d12212da2a85bd8)