BitVisorの外部割り込み処理についてです。
参考資料: BitVisor Summit 7 https://bitvisor.connpass.com/event/100573/ 「BitVisor 2018年の主な変更点」
2.0まで
2.0までの外部割り込み処理は、ずいぶん妙な仕組みになっていました。
struct exint_func
- int_enabled: VM開始時、STIエミュレーション時に呼び出される処理で、exint_pendingをfalseにして、exint_passをconfig.vmm.no_intr_interceptに合わせて設定する
- exintfunc_default: 外部割り込みを生成する時に呼び出される処理で、generate_external_intを呼び出す
- hlt: interrupt windowの時にexint_pendingであれば呼び出される処理で、外部割り込みを取得してパススルーする(do_exint_pass)
exint_pending
これをtrueにすると割り込み生成できるようになった時(interrupt window)にVM exitするという仕掛けですが、AMD環境では機能していませんでした。
exint_pass
これをtrueにすると割り込みで#VMEXITせずパススルーになりますが、unrestricted guest機能のないIntel環境では、real-address modeの場合に限り、パススルーにならないという仕掛けが入っています。
外部割り込み#VMEXIT時の処理
do_exint_passを直接呼び出します。
do_exint_passの処理
割り込み許可フラグを見て、許可であれば外部割り込みを取得して生成、禁止であればexint_pendingをtrueにして、exint_passをtrueに設定します。
何が問題だったか
そもそもexint_funcの各関数が意味不明だった... というのもありますが、参考資料にあるように、割り込み許可でも割り込み生成できないケースというのに対応できていなかったというのが大きな問題でした。共通処理でblocking by STIなどの情報を扱うようにするとしても、意味不明なexint_funcを整理することは避けられなかったでしょう。
また、仮にパススルーでないVMを実現しようとすると、困ったことになるのがわかります。例えば、外部割り込み#VMEXIT時にdo_exint_passを直接呼び出すというのは、明らかにパススルーでないVMのことを想定していない実装です。外部割り込み#VMEXITの時にパススルーでないVMを実行していたなら、まずパススルーのVMに切り替えてから、割り込みを生成しなければならないはずです。exint_funcという名前からすれば、この関数はパススルーでないVMの割り込みも扱えるべきで、そういう意味でも整理が必要だったと言えます。
今
差分はこちらです: https://bitbucket.org/bitvisor/bitvisor/commits/63bb266c3359e2f2a5d49efa54a20c3a6933a5a6
current->pass_vm
新たにパススルーのVMを表す変数が追加されました。割り込みパススルーはIntel用・AMD用でそれぞれ個別の実装になっています。
struct exint_func
関数はackだけになりました。これは、CPUがINTR信号に対してacknowledgeすることを指していて、割り込み番号を得る関数になります。割り込みがなければ負の数を返します。
パススルーであれば、一時的に割り込み許可にして割り込み番号を取得して、さらにIntel NICのvirtio-net対応用の割り込み番号変換をして返します。パススルーでないVMなら、おそらく割り込みコントローラーのエミュレーションをすることになるでしょう。
exint_assert
exint_pendingから名前が変更されました。INTR信号をassertするかどうか、みたいな雰囲気の名前になっています。これをtrueにすると割り込み生成できるようになった時(interrupt window)にVM exitするという仕掛けです。パススルーでないVMであれば、割り込みコントローラーがINTR信号を出すために使用することになるはずです。
Intel環境では、割り込みVM exitはゲストオペレーティングシステムへ割り込み生成可能かどうかに関わらず(無駄に)発生するので、割り込み生成可能になるのを待つために、パススルー時でも使用されます。
AMD環境ではパススルー時は使用されません。現在、V_INTR_MASKINGというのをセットしていないために、ゲストオペレーティングシステムの割り込み許可等の設定がそっくりそのまま実際の割り込み受け付けに使用されていて、割り込み禁止の時は無駄な割り込み#VMEXITは発生しません。本機能はVINTR機能を使用して実装されていますが動作しません。VINTRを有効にするとvirtualなINTR信号を設定することができて、さらにVINTRのinterceptを設定することで、virtualなINTR信号を受け付けようとするタイミングで#VMEXITさせようという狙いですが、VINTRの#VMEXITの処理が抜けているので動きません。
なお、AMD環境で、Intel環境のようにゲストオペレーティングシステムの割り込み許可状態に関わらず割り込み#VMEXITさせるには、V_INTR_MASKINGをセットします。すると、VMRUN命令を実行する時の割り込み許可状態が、実際の割り込み受け付けに使用されるようになります。通常のLinux KVMのようなVMMでは、これをセットして割り込み許可状態でVMRUN命令が実行されます。BitVisorではunsafe nested virtualizationの時のみそれができるようになっています。V_INTR_MASKINGをセットするとTPRなどにも影響があり複雑さが増すのと、無駄な#VMEXITを増やす必要はないので、セットしていません。
exint_pass
これをtrueにすると割り込みで#VMEXITせずパススルーになりますが、unrestricted guest機能のないIntel環境では、real-address modeの場合に限り、パススルーにならないという仕掛けが入っています。これはvmctlから外され、Intel/AMD用実装内でのみ使用される機能になりました。
外部割り込み#VMEXIT時の処理
Intel環境では、パススルー時はexint_passをtrueに、exint_assertをtrueにします。ただし、条件が整っていればすぐにinterrput windowの処理を行います。
Interrupt windowでは、exint_passをconfig.vmm.no_intr_interceptに合わせ、exint_assertをfalseに戻して、ackを呼び出して割り込み生成をします。これはパススルーかどうかに関わらず共通です。
AMD環境では、パススルー時はさっさとexint_passをconfig.vmm.no_intr_interceptに合わせ、exint_assertをfalseに戻して、ackを呼び出して割り込み生成をします。VINTRの処理は抜けていますのでパススルーでないVMを実装するならそのあたりを追加する必要があります。