MVPen (Pegasus Mobile NoteTaker) の解析 (6)
MVPen をLinux から使う試みシリーズ。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_FIRST
と HID_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_FIRST
や HID_REPORT_ID_NEXT
などの値を絡めて指定すると,ioctl()
したあとでは実 ID にかわっています。もう Report ID がないよ,という場合には ioctl()
が負の値を返します。
HIDIOCGFIELDINFO
である Report のある Field についての定義情報を取得する
HIDIOCGFIELDINFO
を ioctl()
で使うと Field についての情報を取得することができます。第3引数には struct hiddev_field_info
へのポインタを指定します。
report_type
, report_id
, field_index
を埋めておく必要があります。前2つは先ほどの HIDIOCGREPORTINFO
と同じです。field_index
は取得したい Field 情報のインデックスで,0 から始まり,先ほどの hiddev_report_info
の num_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 を取得する
HIDIOCGUCODE
を ioctl()
で使うと指定された 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 オリジン)となります。
HIDIOCGUSAGE
や HIDIOCGUSAGE
で 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 が同じなのでつまんないですけど。