TinyVisorのIOMMUのコードを読んでみたよ

BitVisorのアドベントカレンダーってことですが，せっかくなので亜種であるTinyVisorの話を書こうと思います．
以下の流れで勧めていきます

- TinyVisorってなに？
- 本題
- 所感等

---
# TinyVisorってなに？

## 概要
BitVisorはゲストを1つだけ持つことができ，それと引き換えに色々おもしろいことができます．（他の記事参照）
そのBitVisorに少し改良を加え，なんとTinyVisorは2つゲストが動きます！！ すごい 面白い
基本的にはBitVisorと似たような特性（パススルー等）を持ちあわせつつ， もれなくもう一個ゲストが動くため，可能性が広がるわけです．

## 仕組み
先程ちらっとパススルーと言いましたが，これを達成しつつ，ゲストを2つ動かすために物理的にハードウェアを分割しています．
と こんな話をしつつ，早速こんなことを書くのも心が痛いのですが，詳しくは公式のホームページを読むと詳しくわかるかと思います笑
（全部書いていると時間がない．．）

(https://ja.osdn.net/projects/tinyvisor/)

# 本題
私も現在勉強中ですが，ゲストを2つ動作させるためにBitVisorでは想定していなかったことを考慮する必要があります．
と言うのはVM0とVM1はそれぞれ独立して動作するため，メモリやデバイスへのアクセスを監視し，不正なアクセスがあると無効化したりしなくてはいけません．
例えば，VM0に割り当てたNICにVM1からアクセスできては困るわけです．
それに加えて，CPUとかメモリとかI/Oデバイスとかの分割に加えて，各VMの再起動等も独立してできます．

ということでBitVisorとTinyVisorを比較すると技術的に面白いことはたくさんあるのですが，
未だによくわからない
前にいじったけど忘れた
ということで，中でも記憶の新しいIOMMU周りの，特に``` driver/iommu.c ```の話に絞って書こうかなと思った次第です．

では前置きが長くなりましたが，ざっくり書いていきます．
（間違った解釈等ございましたら，ご意見ください．お願い致します．）

---
とりあえずこの辺から読んでいき，概要を追っていきたいと思います．

```
DRIVER_INIT(iommu_drvinit);
DRIVER_DEVINIT(iommu_devinit);
DRIVER_VMINIT(iommu_initvm);
DRIVER_START(iommu_enable);
```
読んで字のごとく，基本的に```driver/iommu.c```にはIOMMUの起動時の初期化及び初期設定が入っておりますので，以下の4つをスタートと見ていってもよいかと思います．
まあ上から読んでいっても問題ないと思います．(多分)

```
static void
iommu_drvinit(void)

```

ここではACPIのDMARテーブルを初期化したり，pci driverをlistに登録したりします．

```
static void
iommu_devinit(void)
{
	struct dmar_drhd_u *drhd;
	struct iommu *iommu;

	if (dmar_drhd_exists()) {
		for (drhd = dmar_get_next_drhd(NULL); drhd;
		     drhd = dmar_get_next_drhd(drhd)) {
			iommu = new_iommu(drhd->address);
			if (!iommu) {
				continue;
			}
			drhd->iommu = iommu;
		}
	}
(略)
	io_domain0 = create_domain(0);
	if (io_domain0 == NULL) {
		panic("Failed to create io domain 0");
	}

	LIST_FOREACH(iommu_list, iommu) {
		setup_all_root_entry_to_domain(iommu, io_domain0);
	}
	msgregister ("vtddump", vtddump_msghandler);
}
```
DRHDはIOMMUのベースアドレスとかデバイスの情報等を持っている構造体です．なので序盤ではIOMMUを作成そのベースアドレスをセットしたりしています．
後半で```io_domain0```と有りますが，IOMMUはdomain_idでどのVMにリソースを割り当てているかを管理することができるため，domain0を作っています．```create_domain()```で次に出てくるcontext tableも作成しています．
引き続き```setup_all_root_entry_to_domain``を見ていきます．

```
static void
setup_all_root_entry_to_domain(struct iommu *iommu,
			       struct io_domain *dom)
{
	int bus_no;
	struct root_entry *root;
	phys_t ctxtbl;

	for (bus_no = 0; bus_no < 256; bus_no++) {
		root = &iommu->root_entry[bus_no];
		if (root_entry_present(*root)) {
			printf("map_bus_to_domain: "
			       "root entry is already set. 0x02%x\n",
			       bus_no);
			continue;
		}
		clear_root_entry(*root);
		ctxtbl = dom->shared_ctxtbl[iommu->page_level - MIN_PAGE_LEVEL];
(略)
		set_root_present(*root);
(略)
}
```
ここでrootというのが出てきましたが，IOMMUはroot tableにbusごとのエントリを持っていて，
その各エントリーはcontext tableのアドレス等を持っているので，先程作成したdomain0のcontext tableを登録します．

つまり，この段階ではdomain0としてすべてのデバイスを登録してしまいます．
ここから，VM0, VM1用にそれぞれに書き換えていきます．

```
static void
iommu_initvm(void)
{
	struct io_domain *dom;
	struct iommu *iommu;

	if (vm_get_id() == 0) {
(略)
	} else {
		dom = create_domain(vm_get_id());
		if (!dom) {
			panic("Failed to create domain.");
		}
	}

	map_all_guest_pages(dom);
	map_assigned_devices_to_domain(dom);
(略)
}
```

この```vm_get_id()```はVMのidを取得するもので，0のときはすでにdomainが作成されているため，```map_all_guest_pages()```でゲストのアクセス可能なメモリ領域をマップしたあとmap_assigned_devices_to_domainでは特に何もしてません．(mmio周辺の設定はしてる)
1のときは先程の用にVM1用のdomainを作成し，メモリ領域をmapしてmap_assigned_devices_to_domainにそのdomainを渡しています．

```
static void
map_assigned_devices_to_domain(struct io_domain *dom)
{
	struct pci_device *dev;
	int bus_no;

	for (dev = pci_next_assgined_pci_device(NULL); dev;
	     dev = pci_next_assgined_pci_device(dev)) {
		map_mmio_resource(dom, dev);

		if (vm_get_id() == 0) {
			continue;
		}
		IOMMU_DBG("map bus 0x%x devfn 0x%x\n",
			  dev->bus_no, dev->devfn);
		map_device_to_domain(dom, dev->bus_no, dev->devfn);
		/*
		 * If assigned device is PCI-PCI bridge, Assign all
		 * devices behind it to a specified io domain.
		 */
(略)
}
```
デバイスごとにdomainに登録していきます． ```map_device_to_domain -> map_device_to_domain_iommu```と進んでいきます．

```
map_device_to_domain_iommu(struct iommu *iommu, struct io_domain *dom,
			   u8 bus_no, u8 devfn)
{
	struct context_entry *context;

	spinlock_lock(&iommu->unit_lock);
	
	context = get_context_entry(iommu, bus_no, devfn);
(略)

	clear_context_entry(*context);
	set_agaw(*context, iommu->page_level - MIN_PAGE_LEVEL);
	set_asr(*context, get_iopt_phys(dom, iommu->page_level));
	set_context_trans_type(*context, 0x0); /* means "ASR field
						  points to a multi-level
						  page-table" */

	enable_fault_handling(*context);
	set_context_domid(*context, dom->domain_id);
	set_context_present(*context);

	flush_cacheline_dw(iommu, context);
	spinlock_unlock(&iommu->unit_lock);
}
```
ここでは該当するbus番号のcontext_entryを取得してきて，
(すでにdomain0として登録されている)そのエントリーの初期化，更新を行いdomain_1のものとします．
```dom->domain_id```あたりを見るとわかりやすいでしょう．

これで終わりです．．．．．(内容がうすry)

---
# 所感

内容はともかく，
ユーザが増えるきっかけになると嬉しいです．

(そもそBitVisorではないし，貴重なドキュメントの機会の1日を消費してしまった)