DomUでのI/Oポート直叩き

これもHVMとパラバーチャルで挙動が異なる.引用コードは例によってXen-3.1.0-src.tgz.

まず外部から見て.

パラバーチャルでは.create時のコンフィギュレーションで ioports を指定すればよい,らしい.

HVMでは,どうやら ioports の指定は効かない.エラーも出ない様子.不親切極まりない.

Xen/HVMのI/Oポート管理

AMD/SVMIntel/VMX で入り口が違う.違いを説明するだけの知識量が,今の私に無い.よって省略*1

最下層から浮き上がってくるのは,xen/arch/x86/hvm/intercept.c にある一連のコード群.

int register_io_handler(
    struct domain *d, unsigned long addr, unsigned long size,
    intercept_action_t action, int type)
{
    struct hvm_io_handler *handler = &d->arch.hvm_domain.io_handler;
    int num = handler->num_slot;

    BUG_ON(num >= MAX_IO_HANDLER);

    handler->hdl_list[num].addr = addr;
    handler->hdl_list[num].size = size;
    handler->hdl_list[num].action = action;
    handler->hdl_list[num].type = type;
    handler->num_slot++;

    return 1;
}

type は,mmio (メモリマップド), pio (ポートマップド)に加えて buffered (VGA?) がある.
type 毎に,それぞれ,wrapper のインライン関数など提供されているが,結局,ここにくる.
ここで action がコールバック関数へのエントリポイント.

コールバックの様子が分かりやすそうなのは,hvm_io_intercept().こんな感じ.

/*
 * Check if the request is handled inside xen
 * return value: 0 --not handled; 1 --handled
 */
int hvm_io_intercept(ioreq_t *p, int type)
{
    struct vcpu *v = current;
    struct hvm_io_handler *handler =
                           &(v->domain->arch.hvm_domain.io_handler);
    int i;
    unsigned long addr, size;

    for (i = 0; i < handler->num_slot; i++) {
        if( type != handler->hdl_list[i].type)
            continue;
        addr = handler->hdl_list[i].addr;
        size = handler->hdl_list[i].size;
        if (p->addr >= addr &&
            p->addr <  addr + size)
            return handler->hdl_list[i].action(p);
    }

「性能? そんなの関係ねぇ」と開き直る姿が目に浮かびそうなコードだ.

具体的な登録の仕方を知るには,arch/x86/hvm/i8254.c で,handle_speaker_io を grep するのがよいかも.

intercept の実行時処理

arch/x86/hvm/platform.c にある.

void send_pio_req(unsigned long port, unsigned long count, int size,
                  paddr_t value, int dir, int df, int value_is_ptr)
{
/* (中略) */
    p->data = value;

    if ( hvm_portio_intercept(p) )
    {
        p->state = STATE_IORESP_READY;
        hvm_io_assist();
        return;
    }   
               
    hvm_send_assist_req(v);
}

hvm_io_assist() の中身は,正直なところ,私もよく理解できていない.VMX RootからVMX Non-root への処理を善きにはからってくれているものだろう…たぶん.


intercept で拾ってくれるハンドラがない場合,hvm_send_assist_req()が呼ばれる.この関数は,arch/x86/hvm/hvm.c という,いかにも窓口っぽいファイルにある.やっていることは,下に引用する通り.Xenバスを経由してリクエストを投げる.受け取り先はqemu-dmを想定してまちがいなさそうだ.

    /*
     * Following happens /after/ blocking and setting up ioreq contents.
     * prepare_wait_on_xen_event_channel() is an implicit barrier.
     */
    p->state = STATE_IOREQ_READY;
    notify_via_xen_event_channel(v->arch.hvm_vcpu.xen_port);

一方,qemu-dm に,どんなデバイスがあるのかを指定するのはxmコマンド(のcreateオプション)だが,こいつに食わせるコンフィギュレーションファイルは,デバイスに関しては貧弱で,独自のI/Oポートはおろか,プリンタのような一般的なデバイスでさえも指定の余地がないっぽい.


ちょっとしたI/Oにプリンタポートを使う,なんていうのはIA32を使った中規模の組込みシステムでは良く使う手だ.しかしながら,現時点のHVMでは,ハイパバイザに手を入れずに,そういった芸当を実現する手段はなさそうだ.

そんなわけで,やっぱサーバ用ね.

タイマ周りでは割と好感触だったXenだが,I/O周りについては,やはりサーバ用ハイパバイザの域を脱していないと言わざるを得ないようだ.
PCIパススルーなど福音もあるにはあるが,レガシーI/Oもまだまだ現役なのが,組込み機器の世界.このままでは使えない.


ま,特殊なターゲットについては自分でhackしろよそれがオープンソースのメリットだろ,と書いている自分も思わなくもない.
arch/x86/hvm に手を入れる覚悟を持ってコードを見ると,結構整理されていて,読みやすい部類だ.
// リニアサーチ使う手抜きは勘弁して欲しいけれど.;-)

*1:こんな調子で[下ネタ]タグつけちゃダメ.