MVPen (Pegasus Mobile NoteTaker) の解析 (7)
MVPen をLinux から使う試みシリーズ。Linux の hiddev デバイスドライバでイベントを取得する方法。
※注意※ MVPen が故障する可能性があります
read (2)
によるイベントの捕捉
いままで /dev/usb/hiddev*
のデバイスデスクリプタハンドルにたいして ioctl (2)
を使ってやりとりをしてきました。このやり方だけではデバイスから PC への情報の送出をリアルタイムでとらえることができません(=とりこぼす可能性もある)。このとき,ハンドルを read (2)
すると,デバイスのステートが変わったときにイベントとして取得することができます。
read (2)
で取得するデータは,デフォルトでは下記の構造体になります。
struct hiddev_event { unsigned hid; signed int value; };
hid
フィールドは Usage Code,つまり Usage Page と Usage の合成値になります。MVPen の場合 INPUT Report として 0xffa00002
に固定されています。value
はその Usage で変化した値です。
イベントを捕まえるサンプルを書いてみます。
#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> static char send_status[8] = { 0x02, 0x02, 0x95, 0x95 }; int main(int argc, char *argv[]) { int fd; struct hiddev_report_info repo; struct hiddev_usage_ref_multi urefs; struct hiddev_event hidev; int flag; int i; fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open"); exit(1); } memset(&urefs, 0, sizeof(urefs)); urefs.uref.report_type = HID_REPORT_TYPE_OUTPUT; urefs.uref.report_id = 0; urefs.uref.field_index = 0; urefs.uref.usage_code = 0xff0a0003; urefs.num_values = 8; for (i = 0; i < 8; i ++) { urefs.values[i] = send_status[i]; } /* OUTPUT Report の Usage Values の設定 */ if (ioctl(fd, HIDIOCSUSAGES, &urefs) < 0) { perror("HIDIOCSUSAGES"); exit(1); } /* OUTPUT Report の送信 */ memset(&repo, 0, sizeof(repo)); repo.report_type = HID_REPORT_TYPE_OUTPUT; repo.report_id = 0; repo.num_fields = 1; if (ioctl(fd, HIDIOCSREPORT, &repo) < 0) { perror("HIDIOCSREPORT"); exit(1); } /* イベントを待つ */ for (;;) { if (read(fd, &hidev, sizeof(hidev)) < sizeof(hidev)) { perror("read"); exit(1); } fprintf(stderr, "#%u: %d\n", hidev.hid, hidev.value); } close(fd); return 0; }
ほんとうは read (2)
で struct hiddev_event
をジャストサイズで取得するとは限りません。厳密には自力でバッファリングする必要があるかとも思うのですが,経験上イベントの取得に関しては構造体のサイズで読み待ちをしても問題がないようです。
上記プログラムでは,MVPen のステータスを取得するための OUTPUT Report をあらかじめ送信しています。ですから,これを実行すると(ステータスの戻り)イベントが発生するはず,です。ところが,発生しません。
HIDDEV_FLAG_UREF
と HIDDEV_FLAG_REPORT
HIDDEV_FLAG_REPORT
を HIDIOCSFLAG
でデバイスデスクリプタハンドルに設定すると,デバイスが Report を送信したときに,read (2)
で捕まえることができます*1。なお,HIDDEV_FLAG_REPORT
を設定するためには,あわせて HIDDEV_FLAG_UREF
というフラグも設定する必要があります。これは read (2)
で取得するイベント構造体が struct hiddev_event
ではなく,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_id
やら field_index
やらも戻ってきますので便利です。
コード例は以下のようになります(ヘッダ等と OUTPUT Report の送信は省略しています)。
/* ...... snip snip snip ...... */ int main(int argc, char *argv[]) { int fd; struct hiddev_report_info repo; struct hiddev_usage_ref_multi urefs; struct hiddev_usage_ref uref; int flag; int i; fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open"); exit(1); } flag = HIDDEV_FLAG_UREF | HIDDEV_FLAG_REPORT; if (ioctl(fd, HIDIOCSFLAG, &flag) < 0) { perror("HIDIOCSFLAG"); exit(1); } /* ...... snip snip snip ...... */ for (;;) { if (read(fd, &uref, sizeof(uref)) < sizeof(uref)) { perror("read"); exit(1); } fprintf(stderr, "[#%u] field#%u, usage#%u (0x%08x): %d\n", uref.report_id, uref.field_index, uref.usage_index, uref.usage_code, uref.value); } close(fd); return 0; }
事前に HIDIOCSFLAG
でフラグをセットしているだけです。なお,デバイスを open (2)
するたびにフラグをセットする必要があるようです(open (2)
するたびにフラグは 0 にリセットされる)。
これを実行すると,
% sudo ./hidtest /dev/usb/hiddev1 # GET STATUS (0x02 0x02 0x95 0x95 ...) への返答 [#0] field#0, usage#63 (0xffa00002): 0 # 以下ボタンを押すごとに発生する Report [#0] field#0, usage#63 (0xffa00002): 0 [#0] field#0, usage#63 (0xffa00002): 0
無事イベントを捕まえることができるようになりました。
ところで,Usage Index が 63 になっているところが気になりますね。
わたしが実験したところ,タイミングにより,
[#0] field#0, usage#0 (0xffa00002): -128 [#0] field#0, usage#1 (0xffa00002): -75 [#0] field#0, usage#2 (0xffa00002): 0 ...... snip snip snip ...... [#0] field#0, usage#62 (0xffa00002): 0 [#0] field#0, usage#63 (0xffa00002): 0
のように Usage Index が 0〜63*2 まで舐める場合と,
[#0] field#0, usage#63 (0xffa00002): 0
のように最終インデックスのみ帰ってくる場合がありました。さらにいうと後者の最終インデックスが帰ってくる状況が圧倒的に多かったです。
もし前者のようにすべての Usage Index に対してイベントが発生することが保証されていれば,HIDIOCGREPORT
で改めてレポートを取得する必要はなくなるのですが……残念です。
よって,INPUT Report を取得するためには
HIDDEV_FLAG_UREF | HIDDEV_FLAG_REPORT
なフラグをHIDIOCSFLAG
で立てておくread (2)
で INPUT Report イベント待ち- Usage Index が最終インデックスであるイベントだったら下記に進む
HIDIOCGREPORT
で INPUT Report を取得するHIDIOCGUSAGES
やHIDIOCGUSAGE
で Report の内容を取得する
という流れで書くことになります。
イベントを待ってから INPUT Report を取得する
前回のサンプルでは,OUTPUT Report を送出してから sleep()
して,INPUT Report を取得していました。今回の話をもとに,sleep()
のかわりにイベントを待ってから INPUT 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> static char send_status[8] = { 0x02, 0x02, 0x95, 0x95 }; int main(int argc, char *argv[]) { int fd; struct hiddev_report_info repo; struct hiddev_usage_ref_multi urefs; struct hiddev_usage_ref uref; int flag; int i; fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open"); exit(1); } flag = HIDDEV_FLAG_UREF | HIDDEV_FLAG_REPORT; if (ioctl(fd, HIDIOCSFLAG, &flag) < 0) { perror("HIDIOCSFLAG"); exit(1); } /* OUTPUT Report の構成 */ memset(&urefs, 0, sizeof(urefs)); urefs.uref.report_type = HID_REPORT_TYPE_OUTPUT; urefs.uref.report_id = 0; urefs.uref.field_index = 0; urefs.uref.usage_code = 0xff0a0003; urefs.num_values = 8; for (i = 0; i < 8; i ++) { urefs.values[i] = send_status[i]; } if (ioctl(fd, HIDIOCSUSAGES, &urefs) < 0) { perror("HIDIOCSUSAGES"); exit(1); } /* OUTPUT Report の出力 */ memset(&repo, 0, sizeof(repo)); repo.report_type = HID_REPORT_TYPE_OUTPUT; repo.report_id = 0; repo.num_fields = 1; if (ioctl(fd, HIDIOCSREPORT, &repo) < 0) { perror("HIDIOCSREPORT"); exit(1); } for (;;) { /* イベント待ち */ for (;;) { if (read(fd, &uref, sizeof(uref)) < sizeof(uref)) { perror("read"); exit(1); } fprintf(stderr, "[#%u] field#%u, usage#%u (0x%08x): %d\n", uref.report_id, uref.field_index, uref.usage_index, uref.usage_code, uref.value); if (uref.usage_index == 63) break; } /* INPUT Report の取得 */ memset(&repo, 0, sizeof(repo)); repo.report_type = HID_REPORT_TYPE_INPUT; repo.report_id = 0; /* repo.num_fields = 1; */ if (ioctl(fd, HIDIOCGREPORT, &repo) < 0) { perror("HIDIOCGREPORT"); exit(1); } /* INPUT Report の Usage Values の取得 */ memset(&urefs, 0, sizeof(urefs)); urefs.uref.report_type = HID_REPORT_TYPE_INPUT; urefs.uref.report_id = 0; urefs.uref.field_index = 0; urefs.uref.usage_code = 0xff0a0003; urefs.num_values = 64; if (ioctl(fd, HIDIOCGUSAGES, &urefs) < 0) { perror("HIDIOCGUSAGES"); exit(1); } /* INPUT Report のダンプ */ for (i = 0; i < 64; i ++) { fprintf(stdout, "%02x ", (unsigned char) urefs.values[i]); if (i % 16 == 15) fputs("\n", stdout); } } close(fd); return 0; }
いままでのプログラムでもそうですが,close (2)
に到達しないので書いてある必要はないのですが,気持ち悪いので書いてあります。
これを実行すると,
% sudo ./hidtest /dev/usb/hiddev1 # GET STATUS (0x02 0x02 0x95 0x95 ...) への返答 [#0] field#0, usage#63 (0xffa00002): 0 80 a9 28 01 3e 01 09 7d 1c 0e 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 # 以下ボタンを押すごとに発生する Report [#0] field#0, usage#63 (0xffa00002): 0 80 b5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [#0] field#0, usage#63 (0xffa00002): 0 80 b5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
無駄なく INPUT Report を取得できるようになったことがわかります。