LoginSignup
4
0

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-12-11

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日を消費してしまった)

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0