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

MVPenLinux から使う試みシリーズ。昨日までの内容がやや間違っていたので,補遺です。

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

HID とのやりとりについて概略を知るためには,PSoCのUSB(HID)をつかってみる 文中でリンクされている Silicon Labs の AN249 の Human Interface Device Tutorial(PDF)が(英語ながら)よくまとまっていて非常にわかりやすいです。さらに Report Descriptor について詳しく知るためには,Human Interface Devices (HID) Information | USB-IF にある「Device Class Definition for HID 1.11」と「HID Usage Tables 1.12」を参照する必要があります。


昨日も書いたように,HID と PC の間のデータのやりとりは「Report」という構造でやりとりされるように抽象化されています。この Report の構造を定義するのが Report Descriptor で,HID デバイスは Device Descriptor や Interface Descriptor だけでなく Report Descriptor も返す必要があります。

Report を構造体とみなすと,Report Descriptor とは構造体の定義になります。

たとえば,マウスの Report Descriptor を C 風に書き下ろすと以下のようになります。

struct ApplicationCollection as Mouse {
    struct PhysicalCollection as Pointer {
        // Input as Buttons
        unsigned bool button1: 1;
        unsigned bool button2: 1;

        // padding
        unsigned bool pad: 6;

        // Input as X
        signed short X;

        // Input as Y
        signed short Y;
    };
};


さて,Report には「Usage Page」と「Usage」という非常に重要な2つの概念が存在します。

おおまかにいうと,「Usage」とは,Report やデータの意味を示すものです。具体的な Usage が示す意味は「Usage Page」によって切り替えることができます。逆にいうと,Usage Page でおおまかな「用途」を示して,Usage で具体的な「用途」を指定する,ということです。

実例を示します。

  • Usage Page 0x01 - Generic Desktop Page
    • Usage 0x02 - Mouse (Collection Appliation)
    • Usage 0x30 - X 座標 (Dynamic Value)
  • Usage Page 0x05 - Game Controls Page
    • Usage 0x02 - Pinball Device (Collection Appliation)
    • Usage 0x30 - Gun Bolt (Dynamic Value)

このように,同じ Usage の値に対しても,Usage Page が異なると違う意味をもつことになります。また,末尾の Collection Application や Dynamic Value という言葉からわかるとおり,Usage というのは「値の用途」だけでなく「Collection*1の用途」についても指定することができます。このように「Usage」がいろんな局面で適用されるので,当初混乱していました。

上記の例で「as」と書いていますが,これは Usage(や Usage Page)で指定された「用途」です。

MVPen の Report Descriptor

ここまで理解できたところで,MVPen の Report Descriptor はどうなっているんだろうと思い,自力で取得して手で解析してみました。Report Descriptor を取得するためのプログラムは末尾に添付してあります。

Interface #0(制御用インタフェース)の Report Descriptor は,下記のような感じです。

06 a0 ff    Usage Page = 0xffa0 (Vendor Page)

09 01         Usage = 0x01 (Vendor's 0x01)
a1 01         Collection (0x01: Application)

09 02           Usage = 0x02 (Vendor's 0x02)
15 80           Logical Minimum = -128 (0x80)
25 7f           Logical Maximum =  127 (0x7f)
75 08           Report Size  = 8 (bits)
95 40           Report Count = 64 (0x40)
81 02           Input (Data, Variable, Absolute)

09 03           Usage = 0x03 (Vendor's 0x03)
15 80           Logical Minimum  = -128 (0x80)
25 7f           Logical Maximum  =  127 (0x7f)
35 00           Physical Minimum =  0   (0x00)
45 ff           Physical Maximum =  255 (0xff)
75 08           Report Size  = 8 (bits)
95 08           Report Count = 8 (0x08)
91 08           Output (Data, Array, Absolute, Wrap)

c0            End Collection (of Application)

シンプルなので,見ていただけると理解できると思います。

  • Input は 8bit × 64
  • Output は 8bit × 8

です。

おまけ?で,Interface #1(ユーザ入力デバイスとしてのインタフェース)の Report Descriptor をあげておきます。

05 0d       Usage Page = 0x0d (Digitizers)

09 02         Usage = 0x02 (Pen)
a1 01         Collection (0x01: Application)
85 08           Report ID = 0x08

09 20           Usage = 0x20 (Stylus)
a1 00           Collection (0x00: Physical)

05 01             Usage Page = 0x01 (Generic Desktop)

09 30               Usage = 0x30 (X)
75 10               Report Size = 16 (bits)
95 01               Report Count = 1
55 0f               Unit Exponent = 15
65 11               Unit = 17
35 00               Physical Minimum = 0   (0x00)
46 a0 00            Physical Maximum = 160 (0xa0)
16 e8 03            Logical Minimum = 1000 (0x03e8)
26 28 23            Logical Maximum = 9000 (0x2328)
81 02               Input (Data, Variable, Absolute)

09 31               Usage = 0x31 (Y)
35 00               Physical Minimum = 0   (0x00)
46 78 00            Physical Maximum = 120 (0x78)
15 00               Logical Minimum = 0    (0x00)
26 70 17            Logical Maximum = 6000 (0x1770)
81 02               Input (Data, Variable, Absolute)

05 0d             Usage Page = 0x0d (Digitizers)

09 42               Usage = 0x42 (Vz)
15 00               Logical Minimum = 0 (0x00)
25 01               Logical Maximum = 1 (0x01)
75 01               Report Size = 1 (bits)
95 01               Report Count = 1
81 02               Input (Data, Variable, Absolute)

09 44               Usage = 0x44 (Vbry)
81 02               Input (Data, Variable, Absolute)

95 02               Report Count = 2
81 03               Input (Constant, Variable, Absolute)    /* padding */

09 32               Usage = 0x32 (Z)
95 01               Report Count = 1
81 02               Input (Data, Variable, Absolute)

95 03               Report Count = 3
81 03               Input (Constant, Variable, Absolute)    /* padding */

c0              End Collection (of Physical)

c0            End Collection (of Application)


05 01       Usage Page = 0x01 (General Desktop)

09 02         Usage = 0x02 (Mouse)
a1 01         Collection (0x01: Application)
85 09           Report ID = 0x09

09 01           Usage = 0x01 (Pointer)
a1 00           Collection (0x00: Physical)

05 09             Usage Page = 0x09 (Button)
19 01             Usage Minimum = 0x01
29 02             Usage Maximum = 0x02
15 00             Logical Minimum = 0 (0x00)
25 01             Logical Maximum = 1 (0x01)
75 01             Report Size = 1 (bits)
95 02             Report Count = 2
81 02             Input (Data, Variable, Absolute)

95 06             Report Count = 6
81 02             Input (Constant, Variable, Absolute)  /* padding */

05 01             Usage Page = 0x01 (General Desktop)
09 30             Usage = 0x30 (X)
09 31             Usage = 0x31 (Y)
15 81             Logical Minimum = -127 (0x81)
25 7f             Logical Maximum =  127 (0x7f)
75 08             Report Size = 8 (bits)
95 02             Report Count = 2
81 06             Input (Data, Array, Relative)

c0              End Collection (of Physical)

c0            End Collection (of Application)

こちらのほうが Usage の意味についてとらえやすいかと思います。

このように,Digitizer としてのレポート((なぜ Vbry があるのか不思議ですが))と,Mouse としてのレポートが定義されていることがわかります。

だから,Windows の場合,汎用のデジタイザ(タブレット?)ドライバが存在すれば,それ経由で入力を取得することが可能,かもしれません*2。「オンライン・ノートモード」に切り替えないとだめかもしれないけど。

HID Report Descriptor を取得するプログラム

libusb を使っています。通常 root 権限が必要かも。また,Linux の場合,カーネルの USB (HID) ドライバを detach する必要があるので(下記ソースをみるとわかりますが,libusb にそのための API が存在する)root 権限必須です。

/* getrepodesc.c */

#include <stdio.h>
#include <stdlib.h>
#include <usb.h>

#define LINUX 1

static struct usb_device *
get_usb_device(struct usb_bus *busses, unsigned vendor, unsigned product)
{
    struct usb_bus *bus;

    for (bus = busses; bus; bus = bus->next) {
        struct usb_device *dev;

        for (dev = bus->devices; dev; dev = dev->next) {
            if (dev->descriptor.idVendor  == vendor
             && dev->descriptor.idProduct == product) {
                return dev;
            }
        }
    }

    return NULL;
}

static int
claim_usb_interface(usb_dev_handle *handle, int interface)
{
#ifdef LINUX
    if (! usb_claim_interface(handle, interface)) {
        fprintf(stderr, "successfully claimed interface\n");
        return 0;
    }
    fprintf(stderr, "%s\n", usb_strerror());

    fprintf(stderr, "detaching kernel driver\n");
    if (usb_detach_kernel_driver_np(handle, interface)) {
        fprintf(stderr, "%s\n", usb_strerror());
        return -1;
    }
#endif

    if (! usb_claim_interface(handle, interface)) {
        fprintf(stderr, "successfully claimed interface\n");
        return 0;
    }

    fprintf(stderr, "%s\n", usb_strerror());

    return -1;
}

int
main(int argc, char *argv[])
{
    struct usb_device *dev;
    usb_dev_handle *handle;
    int len;
    unsigned vendor = 0x0e20, product = 0x0101;
    int interface = 0;
    static char buf[4096];

    if (argc > 1)
        interface = atoi(argv[1]);
    if (argc > 3) {
        vendor  = strtol(argv[2], NULL, 16);
        product = strtol(argv[3], NULL, 16);
    }

    usb_init();
    usb_set_debug(1);

    usb_find_busses();
    usb_find_devices();

    dev = get_usb_device(usb_get_busses(), vendor, product);
    if (! dev) {
        return 1;
    }

    handle = usb_open(dev);
    if (! handle) {
        fprintf(stderr, "%s\n", usb_strerror());
        return 1;
    }

    if (claim_usb_interface(handle, interface)) {
        return 1;
    }

    if (dev->descriptor.bNumConfigurations > 0) {
        if (usb_set_configuration(handle, 0)) {
            fprintf(stderr, "%s\n", usb_strerror());
            return 1;
        }
    }

    len = usb_control_msg(handle,
                          USB_ENDPOINT_IN+1,
                          USB_REQ_GET_DESCRIPTOR,
                          (USB_DT_REPORT << 8) + 0, interface,
                          buf, 4096, 1000);

    if (len > 0)
        fwrite(buf, 1, len, stdout);

    usb_close(handle);

    return 0;
}

2008-08-08 追記
簡易パーサもつけときますね。

#!/usr/bin/perl

use strict;
use warnings;
use IO::Handle;
use IO::File;

my %code = (
    0x80 => 'Input',
    0x90 => 'Output',
    0xb0 => 'Feature',
    0xa0 => 'Collection',
    0xc0 => 'EndCollection',

    0x04 => 'UsagePage',
    0x14 => 'LogicalMinimum',
    0x24 => 'LogicalMaximum',
    0x34 => 'PhysicalMinimum',
    0x44 => 'PhysicalMaximum',
    0x54 => 'UnitExponent',
    0x64 => 'Unit',
    0x74 => 'ReportSize',
    0x84 => 'ReportID',
    0x94 => 'ReportCount',
    0xa4 => 'Push',
    0xb4 => 'Pop',

    0x08 => 'Usage',
    0x18 => 'UsageMinimum',
    0x28 => 'UsageMaximum',
    0x38 => 'DesignatorIndex',
    0x48 => 'DesignatorMinimum',
    0x58 => 'DesignatorMaximum',
    0x78 => 'StringIndex',
    0x88 => 'StringMinimum',
    0x98 => 'StringMaximum',
    0xa8 => 'Delimiter',
);

my $f = @ARGV ? IO::File->new(shift @ARGV, 'r')
      :         IO::Handle->new_from_fd(fileno(\*STDIN), 'r');
die $! if ! $f;

while (! $f->eof) {
    my $buf;

    $f->read($buf, 1)  or die $!;

    my $c = ord $buf;
    printf {*STDOUT} '%02x ', $c;

    my $n = $c & 3;
    while ($n -- > 0) {
        $f->read($buf, 1)  or die $!;
        my $d = ord $buf;
        printf {*STDOUT} '%02x ', $d;
    }

    $c = $c & 0xfc;

    if (exists $code{$c}) {
        print {*STDOUT} "\t", $code{$c};
    }

    print {*STDOUT} "\n";
}

$f->close();

*1:とりあえず Report のようなものだと思ってください;まったく異なりますが。

*2:あー,でもデバイスクラスにもよるかー。