仙石浩明の日記

2008年5月12日

x86_64 な Linux カーネルで i386 プログラムを実行するときの注意点 ── ivtv ドライバの ioctl インタフェース

64bit Linux (x86_64 別名 amd64) は、 CONFIG_IA32_EMULATION を有効にしておくことにより、 32bit プログラム (i386 別名 ia32) を走らせることができる。 したがって 64bit へ移行する際は、 全プログラムを一度に 64bit 化する必要はなく、 まずカーネルだけ 64bit 化しておいて、 各プログラムは (バージョンアップの機会などに) 徐々に 64bit 化していけばよい。 ただし 32bit プログラムがカーネルの機能を呼び出す場合は、 各機能それぞれが 32bit プログラムからの呼び出しに対応していることが前提となる。

32bit プログラムからの呼び出しに対応するといっても、 基本的には引数の型を変換するだけである。 x86_64 の整数データモデルは LP64、 つまり long int 型とポインタ型が 64bit で (引数の型として多用される) int型は 32bit のままなので、 変換が不要なケースも多い。

例えば ioctl システムコールはファイル・ディスクリプタ (file descriptor, 以下 fd と略記) ごとに カーネルが実行すべき機能は変わってくるわけで、 その実装は各デバイス・ドライバに委ねられることが多い。 したがって 32bit プログラムからの ioctl 呼び出しに応えられるか否かは、 各ドライバが 32bit 対応しているか否かに依存する。 不幸にしてドライバが対応していない場合は、

ioctl32(tv:11028): Unknown cmd fd(5) cmd(40045613){t:'V';sz:4} arg(081ec8b4) on /dev/video0

などといったカーネル・メッセージ (dmesg) が出力される。 このメッセージは、 カーネル・ソース中 fs/compat_ioctl.c の compat_ioctl_error が出力している:

static void compat_ioctl_error(struct file *filp, unsigned int fd,
    unsigned int cmd, unsigned long arg)
{
    ...
    compat_printk("ioctl32(%s:%d): Unknown cmd fd(%d) "
            "cmd(%08x){t:%s;sz:%u} arg(%08x) on %s\n",
            current->comm, current->pid,
            (int)fd, (unsigned int)cmd, buf,
            (cmd >> _IOC_SIZESHIFT) & _IOC_SIZEMASK,
            (unsigned int)arg, fn);
    ...
}

fs/compat_ioctl.c は 32bit 版 ioctl システムコールを実装していて、 32bit プログラムが ioctl システムコールを呼び出すと、 この中の compat_sys_ioctl ルーチンが呼ばれる:

asmlinkage long compat_sys_ioctl(unsigned int fd, unsigned int cmd,
                unsigned long arg)
{
    ...
        if (filp->f_op && filp->f_op->compat_ioctl) {
            error = filp->f_op->compat_ioctl(filp, cmd, arg);
            if (error != -ENOIOCTLCMD)
                goto out_fput;
        }
    ...
            compat_ioctl_error(filp, fd, cmd, arg);
    ...
 out_fput:
    fput_light(filp, fput_needed);
 out:
    return error;
}

つまりドライバ側で file 構造体の compat_ioctl 関数ポインタ (filp->f_op->compat_ioctl) が定義されていればそれが呼ばれ、 未定義ならば上記のような「Unknown cmd」エラーが出力される。

ちなみにこのエラーメッセージの「tv:11028」は、 ioctl を呼び出した 32bit プロセスの名前 (コマンド名) とプロセスID であり、 fd(5), cmd(40045613), arg(081ec8b4) は、 それぞれ ioctl システムコールの第一 (つまり fd 番号)、 第二 (ioctl リクエスト番号)、第三引数 (ioctl リクエストの引数) であり、 最後の on /dev/video0 は (第一引数の) fd 番号に対応するファイルのパス名である。

そして、この tv コマンドは 「ビデオキャプチャ・カード GV-MVP/RX2W を使って Linux 2.6.24.4 でテレビ録画」で紹介した perl スクリプト であり、 その名称から推測できるとおりテレビ録画を行なうためのスクリプトである。

このスクリプトでは Video::ivtv モジュールを利用していて、 このモジュールが /dev/video0 つまり TV キャプチャ・デバイスに対して、 ioctl システムコールを呼び出している。 上記エラーはスクリプト中 $IvTV->stopEncoding($TunerFD); を実行したときに発生した。

その名称から推測できる通り、 stopEncoding メソッドはキャプチャ・デバイスに対して エンコーディングの停止を指示するためのもので、 内部で ioctl(fd, VIDIOC_STREAMOFF) などと ioctl 呼び出しを行なっている。 VIDIOC_STREAMOFF は videodev2.h にて、

#define VIDIOC_STREAMOFF    _IOW  ('V', 19, int)

と定義されていて、このマクロを展開すると 40045613 (16進) となり、 上記カーネル・メッセージ「cmd(40045613)」と一致する。

というわけで、(少なくとも Linux 2.6.24.7 に含まれる) ivtv ドライバは、 残念ながら 32bit 対応していないことが分かった。 もちろん x86_64 なカーネルではなく、 i386 カーネルを使えば 32bit プログラムから ivtv ドライバを使うことができるが、 x86_64 なカーネルでは、32bit プログラムからの ioctl システムコールを 64bit カーネルが受付けられる形に変換できないということだ。

とはいうものの、 32bit だろうが 64bit だろうが ioctl のインタフェースに大した変わりはないはずだ。 どうして ivtv ドライバは 32bit 呼び出しをサポートしていないのだろう? と 思いながらドライバのソースを眺めていると... drivers/media/video/compat_ioctl32.c を見つけた。 名前からしていかにも 32bit 版 ioctl のように見える。

compat_ioctl32.c の中の v4l_compat_ioctl32 ルーチンは、 32bit な ioctl 呼び出しを受付けて 引数を 64bit へ変換し (といっても int 型はどちらも 32bit だが)、 本来の (64bit な) ioctl を呼び出し直す仕組みになっている。 なぜ ivtv ドライバは、このルーチンを利用していないのだろうか。

static int do_video_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    ...
    /* First, convert the command. */
    switch(cmd) {
        ...
    case VIDIOC_STREAMOFF32: realcmd = cmd = VIDIOC_STREAMOFF; break;
    };

    switch(cmd) {
        ...
    case VIDIOC_STREAMOFF:
        err = get_user(karg.vx, (u32 __user *)up);
        compatible_arg = 1;
        break;
        ...
    };
    if(err)
        goto out;

    if(compatible_arg)
        err = native_ioctl(file, realcmd, (unsigned long)up);
    else {
        mm_segment_t old_fs = get_fs();

        set_fs(KERNEL_DS);
        err = native_ioctl(file, realcmd, (unsigned long) &karg);
        set_fs(old_fs);
    }
    ...
    return err;
}

long v4l_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
{
    ...
        ret = do_video_ioctl(file, cmd, arg);
        break;
    ...
    return ret;
}

ざっと見た感じ、 ivtv ドライバからこの v4l_compat_ioctl32 ルーチンを呼んでも 特に問題は無いように思われる。

そこで、ivtv ドライバの file 構造体 (の中の file_operations 構造体) の compat_ioctl 関数ポインタに、 v4l_compat_ioctl32 を設定してみた。

--- linux-2.6.24.5.org/drivers/media/video/ivtv/ivtv-streams.c	2008-01-25 07:58:37.000000000 +0900
+++ linux-2.6.24.5/drivers/media/video/ivtv/ivtv-streams.c	2008-05-04 09:10:07.581416212 +0900
@@ -49,6 +49,7 @@
       .write = ivtv_v4l2_write,
       .open = ivtv_v4l2_open,
       .ioctl = ivtv_v4l2_ioctl,
+      .compat_ioctl = v4l_compat_ioctl32,
       .release = ivtv_v4l2_close,
       .poll = ivtv_v4l2_enc_poll,
 };
@@ -59,6 +60,7 @@
       .write = ivtv_v4l2_write,
       .open = ivtv_v4l2_open,
       .ioctl = ivtv_v4l2_ioctl,
+      .compat_ioctl = v4l_compat_ioctl32,
       .release = ivtv_v4l2_close,
       .poll = ivtv_v4l2_dec_poll,
 };

このパッチをあてることにより、 x86_64 なカーネル上で i386 な Video::ivtv モジュールを使って、 ビデオキャプチャ・カード GV-MVP/RX2W を コントロールすることができるようになった。 一週間ほど使ってみた (多数の TV 番組を予約録画した) が、 今のところ問題は起きていない。

1 Comment »

  1. この類のものはLKMLにポストすれば速攻マージされると思うので、マージさせといたほうが将来の自分が楽になるような気がしますよん。

    Comment by kosaki — 2008年5月12日 @ 16:56

RSS feed for comments on this post.

Leave a comment