QEMUに仮想PCIデバイスを追加する(1) : 作ったもの

__※ 色々と誤りや不適切な部分があると思うので気づいたら直します。__

QEMUを触ってみたかったのと、実際にハードウェアと通信する
デバイスドライバを書いてみたかったので、
とりあえずQEMUに仮想PCIデバイス(キャラクタデバイス)を追加しました。
これは単純な機能のテストのみで、実用的なデバイスではありません。
なぜPCIキャラクタデバイスかというと、恐らく最も簡単だろうと判断したからです。

そもそもデバイスドライバ自体書いてみたことがなかったので、
それについてもまとめました。

分量が多くなったので、いくつかに分けています。

[(2) 前提知識 (PCI / デバイスドライバ)](http://qiita.com/rafilia/items/7e30f1119614964ce4c9)
[(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

# 作成したプログラムと主な機能

1. QEMU仮想デバイス (test_pci_device.c) : QEMU と一緒にコンパイル
	- PCIデバイスをQEMUへ登録
	- デバイスが使用する I/O ポート / メモリマップドI/O 領域の設定
	- PCI コンフィギュレーション空間の設定
	- I/O ポート / メモリマップドI/O に対する read/write 関数
	- DMA転送/割り込みの発行

2. デバイスドライバ   (test_pci_driver.c) : ローダブルモジュールとしてコンパイル
	- PCI デバイスとしてのドライバの登録
	- キャラクタデバイスとしての登録
	- 割り込み、DMAの設定
	- 各システムコールに対応する関数の設定
		(open/close/read/write/llseek/unlocked_ioctl)
	- 割り込みハンドラを用意
	- I/Oポート / メモリマップドI/O を用いてデバイスと通信

3. ユーザープログラム (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冊があります。

1. [Linux デバイスドライバ 第3版](http://www.amazon.co.jp/Linux%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90-%E7%AC%AC3%E7%89%88-Jonathan-Corbet/dp/4873112532/ref=sr_1_2?ie=UTF8&qid=1420029314&sr=8-2&keywords=%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90)
2. [Linux デバイスドライバプログラミング](http://www.amazon.co.jp/Linux%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E5%B9%B3%E7%94%B0-%E8%B1%8A/dp/4797346426/ref=sr_1_3?ie=UTF8&qid=1420029314&sr=8-3&keywords=%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%83%89%E3%83%A9%E3%82%A4%E3%83%90)

3. [Essential Linux Device Drivers](http://www.amazon.co.jp/Essential-Drivers-Prentice-Software-Development-ebook/dp/B0015DTWEG/ref=sr_1_6?ie=UTF8&qid=1420029494&sr=8-6&keywords=linux+device+driver)

どれも 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)

次: [(2) 前提知識 (PCI / デバイスドライバ)](http://qiita.com/rafilia/items/7e30f1119614964ce4c9)