0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

bhyveのソースコードリーディングメモ(iommu_init():その5:Remapping H/Wをenableにする)

Posted at

参考ドキュメント

前回ドキュメント

ASADA氏の公開記事

この記事から引用したときは「ChapterXX」とする。例えば第15回の記事なら(Chapter15)となる。

Write BufferのFlush

まず、wtd_wbflush()を呼び出す。
この関数の説明に入る前に述べておくことがある。それはIOMMUと他のデバイス間でのページテーブルやroot,context tableに関する一貫性(coherency)についてである。

IOMMUとRAMコントローラ間の整合

RAMコントローラ内にはWrite Bufferがあり、書き込み要求をバッファする。
仕様書6.8「Write Buffer Flushing」によると、古いタイプのDRHDは、ページテーブルウォークする際に、DRAMへの書き込みをバッファするメモリコントローラ内のWrite bufferをflushしたりsnoopしたりしなかった。
よって、ソフト側でページテーブルを書き換えたときは整合を保つために明示的にWrite BufferをFlushする必要があった。
Remapping H/Wがこの手の古いタイプのものかどうかは、Required Write Buffer Flushing(RWBF)が1かどうかで見分ける。1の場合は、ソフトが明示的にWrite BufferをFlushする必要がある。

IOMMUとCPU間の整合

CPU経由でページテーブルを書き換えたとき、ある時点でその書き込みがRAMに反映されていないことがある(CPUキャッシュがdirty(modified)な状態)。

vtd_wbflush()
static void
vtd_wbflush(struct vtdmap *vtdmap)
{
	- Extend Capability Register
	if (VTD_ECAP_COHERENCY(vtdmap->ext_cap) == 0)
		pmap_invalidate_cache();

まず、vtdmap,すなわちDRHDのレジスタext_capを確認している。ext_capは仕様書10.4.3「Extended Capability Register」を見てほしい。そこのCビット(bit0)を見ている。

上記の処理は「H/Wがページテーブル系のメモリやcontext tableなどのH/Wにアクセスしても一貫性を保ってくれるのであればなにもしないが、保たないというのであれば、CPU内キャッシュ内の該当メモリに対するエントリと実際のメモリが不整合を起こす可能性があるのでCPUキャッシュを破棄する。」ことをしている。

次に、先に説明したWrite BufferをflushしないといけないタイプのIOMMUかを確認し、そうであればWrite Bufferをflushする。

	if (VTD_CAP_RWBF(vtdmap->cap)) {
		vtdmap->gcr = VTD_GCR_WBF;
		while ((vtdmap->gsr & VTD_GSR_WBFS) != 0)
			;
	}

flushはGCR(global command register)のWBFビット(bit27)を1にして実施する。
そして、その完了はGSR(Global Status Register)のWBFSビット(bit27)を見て判断する。
WBFSビットはwrite-buffer flush開始で1となり、終わるとH/Wが0クリアする。

ルートテーブルのアドレスをセットする。

続いて、RTA(Root Table Address)レジスタにルートテーブルの物理アドレスを書き込む。

		/* Update the root table address */
		vtdmap->rta = vtophys(root_table);

実は、RTAレジスタにルートテーブルの物理アドレスを書き込んだだけではだめで、その変更を有効にする必要がある。

		vtdmap->gcr = VTD_GCR_SRTP;
		while ((vtdmap->gsr & VTD_GSR_RTPS) == 0)
			;

先ほどのGCRレジスタの今度はSRTPビット(bit30)をセットする。
そして、GSRレジスタのRTPSビット(bit30)を見てルートテーブルの物理アドレスがDMA Remapping H/Wに反映されるのを待つ。

ルートテーブルの物理アドレスを有効にする操作は、DMA Remapping機能を有効にする前に実施しなくてはならない旨、仕様書に記載されている。

Remapping H/Wが持つキャッシュなどのInvalidate

続いて、以下の処理を実行する。

		vtd_ctx_global_invalidate(vtdmap);
		vtd_iotlb_global_invalidate(vtdmap);

remapping H/Wが持つキャッシュ

仕様書6.2「Address Translation Caches」によると、remapping H/Wがアドレス解決をするにあたり、いくつかのキャッシュを持つ旨記載されている。
その中に以下2点がある。

Context-cache

Context-Tableのキャッシュ。Context-tableは第三回でも述べたように、PCIデバイスごとのアドレス変換テーブルを特定するためのテーブルである。

IOTLB

ページテーブルをたどったときの結果をキャッシュする。第三回でも述べたように、ページテーブルは各デバイスごとのゲスト物理アドレスとホスト物理アドレスの変換を行うものである。

Context-cacheのinvalidate

vtd_ctx_global_invalidate()はContext-cacheをinvalidateする。
要するに、context-table変えたらキャッシュを破棄せよということである。

static void
vtd_ctx_global_invalidate(struct vtdmap *vtdmap)
{

	vtdmap->ccr = VTD_CCR_ICC | VTD_CCR_CIRG_GLOBAL;
	while ((vtdmap->ccr & VTD_CCR_ICC) != 0)
		;
}

Invalidateを行うには、CCRレジスタのICCビットをセットする。
これに加えて、CCRレジスタのCIRGビットを設定し、invalidateするキャッシュの範囲を指定する。このビットは2bit分のビットフィールドがあり、01を書く必要がある。 01は、「global(全ドメイン)」を意味する。

そして、context-cacheをinvalidateし終わったら、ICCがクリアされるのでそれまで待つ。

IOTLBのinvalidate

仕様書によると、context-cacheの内容はIOTLBでも使われているかもしれないとのこと。
よって、context-cacheが終わったあとで、IOTLBのInvalidateもしないとならない。
これを実施しているのは、vtd_iotlb_global_invalidate()である。

static void
vtd_iotlb_global_invalidate(struct vtdmap *vtdmap)
{
	int offset;
	volatile uint64_t *iotlb_reg, val;

	vtd_wbflush(vtdmap);

	offset = VTD_ECAP_IRO(vtdmap->ext_cap) * 16;
	iotlb_reg = (volatile uint64_t *)((caddr_t)vtdmap + offset + 8);
	
	*iotlb_reg =  VTD_IIR_IVT | VTD_IIR_IIRG_GLOBAL |
		      VTD_IIR_DRAIN_READS | VTD_IIR_DRAIN_WRITES;

	while (1) {
		val = *iotlb_reg;
		if ((val & VTD_IIR_IVT) == 0)
			break;
	}
}

最初に、IOTLBのレジスタの位置を特定している。これはECAPレジスタのIROビットフィールドを読むことで特定可能である。
仕様書には以下のように述べられている。

ECAP:IRO: IOTLB Register(IOTLB Register Offset)
This field specifies the offset to the IOTLB registers relative to the register base address of this remapping hardware unit.

If the register base address is X, and the value reported in this field is Y, the address for the IOTLB registers is calculated as X+(16*Y).

ここで、iotlb_regを求めるときに「+ 8」している。
これは、仕様書10.4.8の「IOTLB Registers」でわかる。IOTLBレジスタの先頭から+8した位置にあるのは「IOTLB Invalidate Register」である。

	*iotlb_reg =  VTD_IIR_IVT | VTD_IIR_IIRG_GLOBAL |
		      VTD_IIR_DRAIN_READS | VTD_IIR_DRAIN_WRITES;

書き込んでいるのは以下のとおりである。

|ビット名|ビット位置|意味|
|-------:||-------:||-------:|
|VTD_IIR_IVT |bit63 |IOTLBをInvalidateする。また、Invalidateが完了するとH/Wが0にクリアする|
|VTD_IIR_IIRG_GLOBAL |bit60 |全ドメイン分の(Global)エントリをすべて対象|
|VTD_IIR_DRAIN_READS |bit49 |IOTLB Invalidateの際に必ずread DMAをdrainする。CAPレジスタでDRDがクリアされている場合(未サポート)は無視。(0の場合、read DMAはdrainされるかもしれない)という扱い|
|VTD_IIR_DRAIN_WRITES |bit48 |IOTLB Invalidateの際に必ずwrite DMAをdrainする。CAPレジスタでDWDがクリアされている場合(未サポート)は無視。(0の場合、write DMAはdrainされるかもしれない)という扱い|

DMAのdrain

DMAのdrainについては、仕様書6.5.5「Draining of Requests to Memory」に記載がある。
仕様書によると、Drainingとは「Root-Complex内に留まっているデバイスからの要求をメモリに作用させる」ことをいう。
Writeの場合、書き込みを行うことによって、他のデバイスからも可視になることをいい、Readの場合は実際にメモリに対して要求したすべてのデータをフェッチ完了させる時点まで移行させることをいう。
つまり、未完了の要求があった場合は、それをすべて吐き出すということになる。

仕様書によると、

Hardware clears the IVT field to indicate the
invalidation request is complete.

とあるので、IOTLBレジスタのIVTビットがクリアされるまで待つ。

IOMMUをenableにする

IOMMUをenableにするには、vtd_translation_enable()を呼び出す。

static void
vtd_translation_enable(struct vtdmap *vtdmap)
{

	vtdmap->gcr = VTD_GCR_TE;
	while ((vtdmap->gsr & VTD_GSR_TES) == 0)
		;
}

GCRレジスタのTEビットを立てて、GSRレジスタのTESビットが1になるのを待つ。
1になれば、晴れてremapping H/Wがenableとなります。

これでやっとiommuの初期化は終わりました。
次はゲストOSがロードされるときのiommuの使われ方を見るか、それとも初期化処理の続きを追うか、はたまた別のことを書くか...。
少し考えます。

0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?