MVPen (Pegasus Mobile NoteTaker) の解析 (8)
MVPen をLinux から使う試みシリーズ。いよいよ MVPen からモバイルノートデータを取り出します。
※注意※ MVPen が故障する可能性があります
MVPen の Report 仕様のおさらい
MVPen では INPUT Report, OUTPUT Report ともに一種類しか定義されていません。また,各 Report に含まれる Usage は一つのみです。
以下に定義を示します(Field というのは Linux hiddev ドライバの用語なのですが,事実上 Usage / Field が一つのみなので無視して構いません)。
INPUT Report
- Report ID: 0
- Field[0]
- Usage Page: 0xffa0
- Usage: 0x0002
- signed char[64]
OUTPUT Report
- Report ID: 0
- Field[0]
- Usage Page: 0xffa0
- Usage: 0x0003
- signed char[8]
実際のコマンドコードやレスポンス等は Report 内のバイト列の内容で区別します。
以下退屈なコードが続きます。
hiddev.c
hiddev ドライバとのやりとりをする薄いラッパライブラリです。
#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 hiddev_init(const char *path) { int fd; int flag; fd = open(path, 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); } return fd; } void hiddev_free(int fd) { close(fd); } int hiddev_recv_report(int fd, size_t size, char *buf, int len, int timeout, unsigned *preport_id, unsigned *pusage_code) { static struct hiddev_usage_ref uref; static struct hiddev_usage_ref_multi urefs; for (;;) { fd_set fdset; struct timeval tv; int rd; FD_ZERO(&fdset); FD_SET(fd, &fdset); tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; rd = select(fd + 1, &fdset, NULL, NULL, &tv); if (rd > 0) { rd = read(fd, &uref, sizeof(uref)); if (rd < sizeof(uref)) { perror("read"); exit(1); } if (uref.usage_code == 0) { /* bad condition; unknown reason */ continue; } if (uref.usage_index >= size - 1) { break; } } else if (rd < 0) { perror("select"); exit(1); } else { /* timeout */ return 0; } } if (preport_id) *preport_id = uref.report_id; if (pusage_code) *pusage_code = uref.usage_code; if (buf && len > 0) { int i; memset(&urefs, 0, sizeof(urefs)); urefs.uref.report_type = uref.report_type; urefs.uref.report_id = uref.report_id; urefs.uref.field_index = 0; urefs.uref.usage_code = uref.usage_code; urefs.num_values = size; if (ioctl(fd, HIDIOCGUSAGES, &urefs) < 0) { perror("HIDIOCGUSAGES"); exit(1); } for (i = 0; i < len; i ++) { *buf ++ = urefs.values[i]; } } return size; } void hiddev_send_report(int fd, unsigned report_id, unsigned usage_code, size_t size, const char *buf) { static struct hiddev_usage_ref_multi urefs; static struct hiddev_report_info repo; int i; memset(&urefs, 0, sizeof(urefs)); urefs.uref.report_type = HID_REPORT_TYPE_OUTPUT; urefs.uref.report_id = report_id; urefs.uref.field_index = 0; urefs.uref.usage_code = usage_code; urefs.num_values = size; for (i = 0; i < size; i ++) { urefs.values[i] = *buf ++; } if (ioctl(fd, HIDIOCSUSAGES, &urefs) < 0) { perror("HIDIOCSUSAGES"); exit(1); } memset(&repo, 0, sizeof(repo)); repo.report_type = HID_REPORT_TYPE_OUTPUT; repo.report_id = 0; repo.num_fields = 1; /* unneccesary? */ if (ioctl(fd, HIDIOCSREPORT, &repo) < 0) { perror("HIDIOCSREPORT"); exit(1); } }
前回使った read
(2) ではタイムアウトを指定できないので((alarm
すればいいですけど)),select
(2) でタイムアウトつき監視しておいて,データがきたら read
(2) するようにしています。
以降のソースでもそうですが,不正が発生すると exit()
で抜けています。ほんとうはリソース管理などの都合上きちんとステータスを返すようにするべきでしょうが(C なので),まぁ今回の目的であれば問題ないでしょう。
あたかも汎用的なライブラリ然としていますが,MVPen のように Report 内の Field / Usage が単一の場合にしか使えません。とはいうもののシンプルな USB ドライバチップを使ったデバイスであれば,使えるかもしれません。
mvpen.c
さきほどの hiddev.c
の上にのっかるライブラリです。
#include <stdio.h> #include <stdlib.h> extern int opt_verbose; /* from hiddev.c */ int hiddev_recv_report(int fd, size_t size, char *buf, int len, int timeout, unsigned *preport_id, unsigned *pusage_code); void hiddev_send_report(int fd, unsigned report_id, unsigned usage_code, size_t size, const char *buf); static char buffer[0x40]; static int mvpen_recv_report(int fd, char *buf, int len, int timeout) { unsigned report_id, usage_code; if (hiddev_recv_report(fd, 0x40, buf, len, timeout, &report_id, &usage_code) > 0) { if (report_id == 0 && usage_code == 0xffa00002) return 0x40; } return 0; } static void mvpen_send_report(int fd, const char *buf) { hiddev_send_report(fd, 0, 0xffa00003, 8, buf); } int mvpen_wait_ready(int fd) { static char cmd_status[8] = { 0x02, 0x02, 0x95, 0x95 }; static char sig[2] = { 0x80, 0xa9 }; if (opt_verbose) fputs("waiting ready ..", stderr); for (;;) { mvpen_send_report(fd, cmd_status); if (mvpen_recv_report(fd, buffer, 0x10, 500) > 0) { if (! memcmp(buffer, sig, 2)) { if (opt_verbose) fputs(". done\n", stderr); return (unsigned char) buffer[10]; } } if (opt_verbose) fputs(".", stderr); } /* NOTREACHED */ } int mvpen_get_notes_size(int fd) { static char cmd_notes_size[8] = { 0x02, 0x01, 0xb5, 0x00 }; static char sig1[5] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa }; static char sig2[2] = { 0x55, 0x55 }; if (opt_verbose) fputs("waiting report_size ..", stderr); for (;;) { mvpen_send_report(fd, cmd_notes_size); if (mvpen_recv_report(fd, buffer, 0x10, 500) > 0) { if (! memcmp(buffer + 0, sig1, 5) && ! memcmp(buffer + 7, sig2, 2)) { if (opt_verbose) fputs(". done\n", stderr); return (unsigned char) buffer[5] * 0x100 + (unsigned char) buffer[6]; } } if (opt_verbose) fputs(".", stderr); } } int mvpen_copy_notes_to_stream(int fd, FILE *fp) { static char cmd_get_notes[8] = { 0x02, 0x01, 0xb6, 0x00 }; size_t notes_size; unsigned index = 0; mvpen_wait_ready(fd); notes_size = mvpen_get_notes_size(fd); mvpen_send_report(fd, cmd_get_notes); for (;;) { int rd; rd = mvpen_recv_report(fd, buffer, 0x40, 500); if (rd <= 0) { if (index >= notes_size) break; if (index > 0) { if (fseek(fp, SEEK_SET, 0)) { fputs("output stream is not seekable\n", stderr); exit(1); } } mvpen_wait_ready(fd); notes_size = mvpen_get_notes_size(fd); mvpen_send_report(fd, cmd_get_notes); continue; } index = ((unsigned char) buffer[0]) * 0x100 + (unsigned char) buffer[1]; fprintf(stderr, "\rreading %u / %u", index, notes_size); if (0x40 - 2 != fwrite(buffer + 2, 1, 0x40 - 2, fp)) { perror("fwrite"); return 1; } if (index >= notes_size) break; } fputs("\n", stderr); return index; } void mvpen_clear_notes(int fd) { static char cmd_clear_notes[8] = { 0x02, 0x01, 0xb0, 0x00 }; mvpen_send_report(fd, cmd_clear_notes); }
MVPen では Usage Code は固定なので mvpen_recv_report()
,mvpen_send_report()
で薄くラッピングしてあります。
ノートデータの取得の際,あらかじめノートデータ量の取得を行っていないとうまくいかないようです。
main.c
以上のライブラリを呼び出すメインルーチンが下記です。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> /* for getopt() */ int opt_verbose = 0; int opt_size = 0; int opt_retrieve = 0; int opt_clear = 0; /* from hiddev.c */ int hiddev_init(const char *path); void hiddev_free(int fd); int hiddev_recv_report(int fd, size_t size, char *buf, int len, int timeout, unsigned *preport_id, unsigned *pusage_code); void hiddev_send_report(int fd, unsigned report_id, unsigned usage_code, size_t size, const char *buf); /* from mvpen.c */ int mvpen_wait_ready(int fd); int mvpen_get_notes_size(int fd); int mvpen_copy_notes_to_stream(int fd, FILE *fp); void mvpen_clear_notes(int fd); void usage() { fputs( "usage: mvpen [-s] [-r] [-c] [-v] <hid device>\n" "\n" "\toptions:\n" "\t\t-s\treport size of notes\n" "\t\t-r\tretrieve notes and write to stdout\n" "\t\t-c\tclear all notes data\n" "\t\t\n" "\t\t-v\toutput messages verbosely\n" , stderr ); } int main(int argc, char *argv[]) { int fd; int notes_size = 0; int opt; while ((opt = getopt(argc, argv, "vsrc")) >= 0) { switch (opt) { case 'v': opt_verbose = 1; break; case 's': opt_size = 1; break; case 'r': opt_retrieve = 1; break; case 'c': opt_clear = 1; break; default: usage(); exit(1); } } if (argc - optind <= 0) { usage(); exit(1); } fd = hiddev_init(argv[optind]); if (fd < 0) return 1; fprintf(stderr, "mode: %d\n", mvpen_wait_ready(fd)); if (opt_size) { notes_size = mvpen_get_notes_size(fd); fprintf(stderr, "size of notes: %d bytes\n", (0x40 - 2) * notes_size); } if (opt_retrieve) { if (! notes_size) notes_size = mvpen_get_notes_size(fd); if (notes_size <= 0) { fputs("There are no notes\n", stderr); } else { mvpen_copy_notes_to_stream(fd, stdout); mvpen_wait_ready(fd); } } if (opt_clear) { mvpen_wait_ready(fd); mvpen_clear_notes(fd); mvpen_wait_ready(fd); } hiddev_free(fd); return 0; }
使い方は usage 参照です。
usage: mvpen [-s] [-r] [-c] [-v] <hid device> options: -s report size of notes -r retrieve notes and write to stdout -c clear all notes data -v output messages verbosely
標準出力にモバイルノートデータを出力するので,リダイレクトして取得してください。
取得できるモバイルノートデータはバイナリ形式です。このフォーマットは次回解説します。