MVPen (Pegasus Mobile NoteTaker) の解析 (4)

MVPenLinux から使う試みシリーズ。Linux で HID デバイスを操る方法について。

※注意※ MVPen が故障する可能性があります

Linux の USB (HID) インタフェース

Linux からアクセスできる USB (HID) まわりへのデバイスノードのパスには下記のようなものがあります。

  • /sys/bus/usb/devices/*
    • 詳細不明
  • /sys/class/usb*/*
    • 実質下記の /devバイスへのリンクと思われる
  • /dev/bus/usb/*/*
    • 一番ローレベルの USB としてのデバイスインタフェース
    • ioctl() インタフェース
  • /dev/usb/hiddev*
    • 既知の入力デバイス(キーボード,マウス,ゲームパッド等)ではない場合に生成される,汎用 HID インタフェース
    • ioctl(), read() インタフェース
  • /dev/input/*
    • 既知の入力デバイス(キーボード,マウス,ゲームパッド等)と関連付けられる抽象化されたインタフェース
    • ioctl(), read() インタフェース

sysfs 経由のインタフェースについて詳細がわからないので,/devバイスノードインタフェースについてそれぞれ簡単に説明します。

/dev/bus/usb/*/* インタフェース

/dev/bus/usb/*/* を操る ioctl() インタフェースの詳細についてドキュメントが見当たらなかったのですが,libusbLinux port はこのインタフェースを下位で使っています((なので libusb のソースを読むと /dev/bus/usb/*/*ioctl() プロトコルについて,ある程度わかります。))。素直にこれを使うべきでしょう。

さらに libusb を下位とする libhid なるライブラリもあります。つまりこいつは後述する /dev/usb/hiddev* インタフェースを使っているわけではありません。

/dev/usb/hiddev* インタフェース(hiddev インタフェース)

汎用 HID デバイス用のドライバで作成されるインタフェースです。詳しくは後述します。

Linux HID」で検索して最初のほうにでてくる http://www.frogmouth.net/hid-doco/linux-hid.html に一応の説明はありますが,ほとんど内容はありません。Linux カーネルドキュメントの usb/hiddev.txt とヘッダファイルである linux/hiddev.h を読み解く必要があります。それにしても情報量は少ないので,あとは試行錯誤をくりかえして理解することになります。

また,先達の Network UPS Toolsnut-2.0.5/drivers/hidups.c も参考になります*1

/dev/input/* インタフェース

マウス,キーボードなどの入力デバイス向けの抽象化された(といっても所詮カーネルインタフェースなので高度な API があるわけではありませんが)インタフェースです。

前述の http://www.frogmouth.net/hid-doco/linux-hid.html に説明があります。

MVPen ではこのインタフェースは使わないので省略します。

hiddev インタフェースを使ううえで理解しておくべきこと

ほんとうは最初 libhid を使って MVPen をあやつろうと思っていたのですが,うまくいかなかったので hiddev インタフェースを使うことにしました。libhid 側でなにかおかしなことになっているようですが,libusb 自身に問題はなさそうだったので,余力があれば libusb でコードを書きたいと思います。そのほうが portable になりますしね。


さてさて,hiddev インタフェースについて。なるべく単純な API にするべくこうなったのかもしれませんが,一般的な HID 用 API にはない概念があります。それが「field」と「usage index」です。

たとえば,下記のような Report Descriptor があるとします。

struct MousePointerReport {
    /*
        Report Size:   8
        Report Count:  1
        Usage: 0x38             (Wheel)
        Input
     */
    byte    usage38;    /* Wheel */

    /* UsagePage: Generic Desktop */
    /*
        Report Size:   16
        Report Count:  2
        Usage: 0x30             (X)
        Usage: 0x31             (Y)
        Input
     */
    word    usage30;    /* X */
    word    usage31;    /* Y */

    /* UsagePage: Button */
    /*
        Report Size:   1
        Report Count:  8
        Usage Minimum: 0x01     (Button1)
        Usage Maximum: 0x08     (Button8)
        Input
     */
    bit:1   usage01;    /* Button1 */
    bit:1   usage02;    /* Button2 */
    bit:1   usage03;    /* Button3 */
    bit:1   usage04;    /* Button4 */
    bit:1   usage05;    /* Button5 */
    bit:1   usage06;    /* Button6 */
    bit:1   usage07;    /* Button7 */
    bit:1   usage08;    /* Button8 */

    /* UsagePage: Vendor */
    /*
        Report Size:   8
        Report Count:  16
        Usage: 0x01             (Vendor#1)
        Input
     */
    byte    usage01[16]; /* Vendor#1 x 16 */
};

これの number of fields はいくつでしょうか?

1 + 1+1 + 8 + 16 = 27 ?

こたえは,4 です。端的にいうと,Input(や Output)がある数だけ field があることになります。そしてその field の中の各 Usage Data を usage index で指定することになります。

各フィールドについて個別に見ていきましょうか。

    /*
        Report Size:   8
        Report Count:  1
        Usage: 0x38             (Wheel)
        Input
     */
    byte    usage38;    /* Wheel */

説明するまでもありませんね。8 bit×1つのデータ(Usage)を定義しています。これはこのレポートのなかで独立した1つのフィールドとして扱われます。

    /* UsagePage: Generic Desktop */
    /*
        Report Size:   16
        Report Count:  2
        Usage: 0x30             (X)
        Usage: 0x31             (Y)
        Input
     */
    word    usage30;    /* X */
    word    usage31;    /* Y */

Usage をいくつか指定したあとに Input がくると,各 Usage のデータを定義していることになります。これも1つのフィールドです。usage index = 0 が Usage#0x30(X),usage index = 1 のデータが Usage#0x31(Y)になります。

    /* UsagePage: Button */
    /*
        Report Size:   1
        Report Count:  8
        Usage Minimum: 0x01     (Button1)
        Usage Maximum: 0x08     (Button8)
        Input
     */
    bit:1   usage01;    /* Button1 */
    bit:1   usage02;    /* Button2 */
    bit:1   usage03;    /* Button3 */
    bit:1   usage04;    /* Button4 */
    bit:1   usage05;    /* Button5 */
    bit:1   usage06;    /* Button6 */
    bit:1   usage07;    /* Button7 */
    bit:1   usage08;    /* Button8 */

マウスのボタンやゲームパッドのボタンのように同じ用途のデータが数多くある場合,さきほどのように Usage をいくつも連ねていってもいいのですが,指定子が長くなってしまいますし面倒です。なので,「Usage Minimum」と「Usage Maximum」を指定すると,その範囲の Usage Type に対応する Usage Data が定義されるようになっています。このような Report Descriptor の場合でもフィールドとしては1つになります。

    /* UsagePage: Vendor */
    /*
        Report Size:   8
        Report Count:  16
        Usage: 0x01             (Vendor#1)
        Input
     */
    byte    usage01[16]; /* Vendor#1 x 16 */

あるいは単一の Usage に対応するデータが,上記のような「配列」のようになっている場合があります。この場合だと 8 bit×16個のデータです。この場合でもフィールドは1つになります。usage index は各配列のインデックスに対応します(これまでと異なり,usage code はすべて同じになります)。


これらの field や usage index という概念は,HIDIOCGUSAGEHIDIOCSUSAGE で使われる struct hiddev_usage_ref にでてきます。ヘッダファイルより抜粋します。

struct hiddev_usage_ref {
        __u32 report_type;
        __u32 report_id;
        __u32 field_index;
        __u32 usage_index;
        __u32 usage_code;
        __s32 value;
};

このように,各 (Usage) Data に対して,field_indexusage_index(と usage_code)が定義されていることがわかります。具体的な使い方は次回以降説明します。


本質的ではないことで長くなってしまいました。なんとかお盆休みまでには終えたいのですが。

*1:残念ながら?trunk バージョンでは libhid & libusb を使うように変わったぽいのですが