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

MVPenLinux から使う試みシリーズ。Linux の hiddev デバイスドライバで Report の形式を取得する方法。

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

今回は hiddev デバイスドライバのうち,Report の送受信にからむ部分のみ説明します。

HIDIOCGREPORTINFO で Report の定義情報を取得する

Report の種別(INPUT か OUTPUT か FEATURE か)と Report の ID がすでにわかっている場合,その Report についての情報を取得するには HIDIOCGREPORTINFO を使います。第3引数には struct hiddev_report_info のポインタを指定します。このうち report_type フィールドと report_id フィールドを埋めておく必要があります。

たとえば,こんな感じです。

struct hiddev_report_info repo;

repo.report_type = HID_REPORT_TYPE_INPUT;
repo.report_id   = 0;

ioctl(fd, HIDIOCGREPORTINFO, &repo);

printf("num_field = %u\n", repo.num_fields);

まあ,実際に取得できるのはその Report に存在する Field の数 num_fields だけですが。

struct hiddev_report_info の定義を示します。

struct hiddev_report_info {
    __u32 report_type;
    __u32 report_id;
    __u32 num_fields;
};

report_type は下記のように定義されています。

#define HID_REPORT_TYPE_INPUT   1
#define HID_REPORT_TYPE_OUTPUT  2
#define HID_REPORT_TYPE_FEATURE 3


といっても未知のデバイスの場合,Report ID があらかじめわかっている場合はまれです。このようなとき,HID_REPORT_ID_FIRSTHID_REPORT_ID_NEXT という疑似 ID を使うと列挙することができます。実例を示します。

int fd = open("/dev/usb/hiddev0", O_RDONLY);

struct hiddev_report_info repo;
int report_id;

report_id = HID_REPORT_ID_FIRST;

for (;;) {
    memset(&repo, 0, sizeof(repo));
    repo.report_type = HID_REPORT_TYPE_INPUT;
    repo.report_id   = report_id;

    if (ioctl(fd, HIDIOCGREPORTINFO, &repo) < 0)
        break;

    printf("report id = 0x%x, num_fields = %u\n",
           repo.report_id, repo.num_fields);

    report_id = repo.report_id | HID_REPORT_ID_NEXT;
}

INPUT Report の ID を列挙しています。report_id フィールドに HID_REPORT_ID_FIRSTHID_REPORT_ID_NEXT などの値を絡めて指定すると,ioctl() したあとでは実 ID にかわっています。もう Report ID がないよ,という場合には ioctl() が負の値を返します。

HIDIOCGFIELDINFO である Report のある Field についての定義情報を取得する

HIDIOCGFIELDINFOioctl() で使うと Field についての情報を取得することができます。第3引数には struct hiddev_field_info へのポインタを指定します。

report_type, report_id, field_index を埋めておく必要があります。前2つは先ほどの HIDIOCGREPORTINFO と同じです。field_index は取得したい Field 情報のインデックスで,0 から始まり,先ほどの hiddev_report_infonum_fields - 1 までです。

取得できる情報は struct hiddev_field_info の定義を見るほうが早いでしょう。

struct hiddev_field_info {
    __u32 report_type;
    __u32 report_id;
    __u32 field_index;
    __u32 maxusage;
    __u32 flags;
    __u32 physical;         /* physical usage for this field */
    __u32 logical;          /* logical usage for this field */
    __u32 application;      /* application usage for this field */
    __s32 logical_minimum;
    __s32 logical_maximum;
    __s32 physical_minimum;
    __s32 physical_maximum;
    __u32 unit_exponent;
    __u32 unit;
};

struct hiddev_report_info をすでに取得した場合の,全 Field の情報を取得するコードは下記のようになります。

struct hiddev_report_info repo;
struct hiddev_field_info finfo;
int field_index;

memset(&finfo, 0, sizeof(finfo));
finfo.report_type = repo.report_type;
finfo.report_id   = repo.report_id;

for (field_index = 0; field_index < repo.num_fields; field_index ++) {
    finfo.field_index = field_index;

    ioctl(fd, HIDIOCGFIELDINFO, &finfo);

    /* work with finfo (struct hiddev_field_info) */
}

HIDIOCGUCODE である Field 内の指定した Usage Index の Usage Code を取得する

HIDIOCGUCODEioctl() で使うと指定された Usage Index の Usage Code を取得することができます。第3引数には struct hiddev_usage_ref へのポインタを指定します。

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;
};

report_type, report_id, field_index, usage_index を埋めておく必要があります。前3つはこれまで説明したとおりです。usage_index は取得したい Usage のインデックス(0 オリジン)となります。

HIDIOCGUSAGEHIDIOCGUSAGE で Usage の値を取得・設定するためには usage_code が必要になります。もしあらかじめ usage_code がわかっていない場合,この API によってあらかじめ調べておく必要があります。

全 Report の全 Field を列挙するプログラム

ここまで説明してきた API をつかって,HID デバイスの Report の定義を表示するプログラムを作りました。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <asm/types.h>
#include <linux/hiddev.h>

int
main(int argc, char *argv[])
{
    int fd;
    struct hiddev_devinfo devinfo;
    struct hiddev_collection_info colinfo;
    int i;
    static char name[1024];

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }

    /*** Get Device Name ***/
    if (ioctl(fd, HIDIOCGNAME(sizeof(name)), name) < 0) {
        perror("HIDIOCGNAME");
        exit(1);
    }
    fprintf(stdout, "%s\n\n", name);

    /*** Get Device Information ***/
    if (ioctl(fd, HIDIOCGDEVINFO, &devinfo) < 0) {
        perror("HIDIOCGDEVINFO");
        exit(1);
    }
    fprintf(stdout,
            "[Device Information]\n"
            "vendor  id = 0x%04x\n"
            "product id = 0x%04x\n"
            "num_applications = %d\n\n",
            devinfo.vendor  & 0xffff,
            devinfo.product & 0xffff,
            devinfo.num_applications);

    /*** Enumerate Collections ***/
    for (i = 0; ; i ++) {
        int r;
        static const char *collection_type[] = {
            "Physical",
            "Application",
            "Logical",
            "Report",
            "Named Array",
            "Usage Switch",
            "Usage Modifier",
        };

        memset(&colinfo, 0, sizeof(colinfo));
        colinfo.index = i;
        r = ioctl(fd, HIDIOCGCOLLECTIONINFO, &colinfo);
        if (r < 0) {
            if (errno != EINVAL) {
                perror("HIDIOCGCOLLECTIONINFO");
                exit(1);
            }
            break;
        }
        fprintf(stdout,
                "[Collection #%d]\n"
                "collection type = %s (0x%02x)\n"
                "level = %d\n"
                "usage page = 0x%04x\n"
                "usage type = 0x%04x\n\n",
                i,
                collection_type[colinfo.type], colinfo.type,
                colinfo.level,
                colinfo.usage >> 16, colinfo.usage & 0xffff);
    }

    /*** Enumerate Report Definitions ***/
    for (i = HID_REPORT_TYPE_MIN; i <= HID_REPORT_TYPE_MAX; i ++) {
        int report_id;
        int r;

        report_id = HID_REPORT_ID_FIRST;

        for (;;) {
            struct hiddev_report_info repo;
            int fi;
            static const char *report_type[] = {
                "UNKNOWN",
                "INPUT",
                "OUTPUT",
                "FEATURE",
            };

            memset(&repo, 0, sizeof(repo));
            repo.report_type = i;
            repo.report_id   = report_id;

            r = ioctl(fd, HIDIOCGREPORTINFO, &repo);
            if (r < 0)
                break;

            fprintf(stdout,
                    "[%s Report #%d]\n"
                    "num_fields = %u\n\n",
                    report_type[repo.report_type],
                    repo.report_id, repo.num_fields);

            /* Enumrate Fields in Report */
            for (fi = 0; fi < repo.num_fields; fi ++) {
                struct hiddev_field_info finfo;
                int usage;

                memset(&finfo, 0, sizeof(finfo));
                finfo.report_type = repo.report_type;
                finfo.report_id   = repo.report_id;
                finfo.field_index = fi;

                r = ioctl(fd, HIDIOCGFIELDINFO, &finfo);
                if (r < 0) {
                    perror("IOCGFIELDINFO");
                    continue;
                }

                fprintf(stdout,
                        "[%s Report #%d: Field #%d]\n"
                        "application = 0x%08x\n"
                        "flags = 0x%x\n"
                        "maxusage = %u\n"
                        "phy_min = %d,\tlog_min = %d\n"
                        "phy_max = %d,\tlog_max = %d\n"
                        "unit_exp = %u,\tunit = %u\n\n",
                        report_type[repo.report_type], repo.report_id,
                        fi,
                        finfo.application,
                        finfo.flags,
                        finfo.maxusage,
                        finfo.physical_minimum, finfo.logical_minimum,
                        finfo.physical_maximum, finfo.logical_maximum,
                        finfo.unit_exponent, finfo.unit);

                /* Enumerate Usages in Field */
                for (usage = 0; usage < finfo.maxusage; usage ++) {
                    struct hiddev_usage_ref uref;

                    memset(&uref, 0, sizeof(uref));
                    uref.report_type = repo.report_type;
                    uref.report_id   = repo.report_id;
                    uref.field_index = fi;
                    uref.usage_index = usage;

                    r = ioctl(fd, HIDIOCGUCODE, &uref);
                    if (r < 0) {
                        perror("HIDIOCGUCODE");
                        break;
                    }
                    else {
                        fprintf(stdout,
                                "(%2u: 0x%x) ",
                                uref.usage_index,
                                uref.usage_code);
                        if (usage % 4 == 3)
                            fputs("\n", stdout);
                    }
                }
                fputs("\n", stdout);
            }

            report_id = repo.report_id | HID_REPORT_ID_NEXT;
        }
    }

    close(fd);

    return 0;
}

これを MVPen で実行すると下記のようになります。

Pegasus Technologies Ltd. NoteTaker FW Ver 3.03

[Device Information]
vendor  id = 0x0e20
product id = 0x0101
num_applications = 1

[Collection #0]
collection type = Application (0x01)
level = 0
usage page = 0xffa0
usage type = 0x0001

[INPUT Report #0]
num_fields = 1

[INPUT Report #0: Field #0]
application = 0xffa00001
flags = 0x2
maxusage = 64
phy_min = 0,	log_min = -128
phy_max = 0,	log_max = 127
unit_exp = 0,	unit = 0

( 0: 0xffa00002) ( 1: 0xffa00002) ( 2: 0xffa00002) ( 3: 0xffa00002) 
( 4: 0xffa00002) ( 5: 0xffa00002) ( 6: 0xffa00002) ( 7: 0xffa00002) 
( 8: 0xffa00002) ( 9: 0xffa00002) (10: 0xffa00002) (11: 0xffa00002) 
(12: 0xffa00002) (13: 0xffa00002) (14: 0xffa00002) (15: 0xffa00002) 
(16: 0xffa00002) (17: 0xffa00002) (18: 0xffa00002) (19: 0xffa00002) 
(20: 0xffa00002) (21: 0xffa00002) (22: 0xffa00002) (23: 0xffa00002) 
(24: 0xffa00002) (25: 0xffa00002) (26: 0xffa00002) (27: 0xffa00002) 
(28: 0xffa00002) (29: 0xffa00002) (30: 0xffa00002) (31: 0xffa00002) 
(32: 0xffa00002) (33: 0xffa00002) (34: 0xffa00002) (35: 0xffa00002) 
(36: 0xffa00002) (37: 0xffa00002) (38: 0xffa00002) (39: 0xffa00002) 
(40: 0xffa00002) (41: 0xffa00002) (42: 0xffa00002) (43: 0xffa00002) 
(44: 0xffa00002) (45: 0xffa00002) (46: 0xffa00002) (47: 0xffa00002) 
(48: 0xffa00002) (49: 0xffa00002) (50: 0xffa00002) (51: 0xffa00002) 
(52: 0xffa00002) (53: 0xffa00002) (54: 0xffa00002) (55: 0xffa00002) 
(56: 0xffa00002) (57: 0xffa00002) (58: 0xffa00002) (59: 0xffa00002) 
(60: 0xffa00002) (61: 0xffa00002) (62: 0xffa00002) (63: 0xffa00002) 

[OUTPUT Report #0]
num_fields = 1

[OUTPUT Report #0: Field #0]
application = 0xffa00001
flags = 0x8
maxusage = 8
phy_min = 0,	log_min = -128
phy_max = 255,	log_max = 127
unit_exp = 0,	unit = 0

( 0: 0xffa00003) ( 1: 0xffa00003) ( 2: 0xffa00003) ( 3: 0xffa00003) 
( 4: 0xffa00003) ( 5: 0xffa00003) ( 6: 0xffa00003) ( 7: 0xffa00003) 

Field 内の全 Usage の Usage Page / Usage Code が同じなのでつまんないですけど。