MVPen (Pegasus Mobile NoteTaker) の解析 (10)
MVPen をLinux から使う試みシリーズ。libusb でモバイルノートデータを取得してみました。
※注意※ MVPen が故障する可能性があります
MVPen のローレベルインタフェース仕様
MVPen のローレベルなインタフェースを見てみます。
device: vendor = 0x0e20, product = 0x0101 config#0 interface#0 (altsetting#0) endpoint#0 address = 0x81 (EPin 1) attributes = 0x03 (interrupt transfer) interface#1 (altsetting#0) endpoint#0 address = 0x82 (EPin 2) attributes = 0x03 (interrupt transfer)
interface#1 はタブレットインタフェースである(と思われる)ため,今回は扱いません。
上記より,
- PC→デバイスには,コントロール転送が用いられる(MVPen の場合,8 バイト)
- デバイス→PCには,エンドポイント 0x81*1からのインタラプト転送が用いられる(MVPen の場合,64 バイト)
ことになります。なぁんだ,HID レイヤーをかますよりより単純でしたね。
コントロール転送やインタラプト転送,エンドポイントといった専門用語については,USB通信プログラミングテクニック - 電子工作の実験室のUSBの基本アーキテクチャが参考になります。
libusb の使い方
マルチプラットフォームな USB の抽象 API として libusb があるので,今回は Linux hiddev インタフェースではなくこちらを使ってみました。MacOS X でもうまく動くかも?
libusb の使い方としては http://libusb.sourceforge.net/doc/ というドキュメントがあるのですが,ほとんど内容がありません。
ヘッダ usb.h
や他のソース(libhid*2 など)を参考に,こういうことじゃないかなぁという解釈を書いておきます。
Output Report の送出
[http://libusb.sourceforge.net/doc/function.usbcontrolmsg.html:title]
を使います。
MVPen(というか HID デバイス)の場合,コントロール転送を使って,
int length = usb_control_msg( usb_dev_handle, USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, HID_REPORT_SET, (HID_RT_OUTPUT << 8) + report_id, interface_index, (char *) buffer, size, timeout);
のように書きます。と libhid のソースを読んで知りました。
本来3番目の引数は usb.h
で定義されている USB_REQ_*
という形式の定数を使うと思っていたのですが,それは2番目の引数が USB_TYPE_STANDARD
(=0) の場合のようです。上記のように USB_TYPE_CLASS
とするとデバイスクラス(今回の場合 HID クラス)に沿ったリクエストタイプ定数(今回の場合 HID_REPORT_SET
)を指定することになります。
interface_index
は MVPen の場合 0 となります(上記参照)。
Input Report の取得
MVPen の場合 Input Report はインタラプト転送で取得するので,より簡単です。
int length = usb_interrupt_read( usb_dev_handle, endpoint_address, (char *) buffer, size, timeout);
のようにします。endpoint_address
は MVPen の場合 0x81
となります(上記参照)。
ソースコード
以前の Linux hiddev インタフェースを使用したバージョンからかなり流用しています。が,細かい部分が違います。もっときちんと抽象化しておくべきでしたね。
/* mvpen.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <usb.h> extern int opt_verbose; #define HID_REPORT_GET 0x01 #define HID_REPORT_SET 0x09 #define HID_GET_IDLE 0x02 #define HID_SET_IDLE 0x0A #define HID_RT_INPUT 0x01 #define HID_RT_OUTPUT 0x02 #define HID_RT_FEATURE 0x03 static struct usb_device * _get_mvpen_device(struct usb_bus *busses, unsigned short vendor, unsigned short 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_mvpen_interface(usb_dev_handle *handle, int interface) { #ifdef LINUX if (! usb_claim_interface(handle, interface)) { if (opt_verbose) fputs("successfully claimed.\n", stderr); return 0; } if (opt_verbose) fprintf(stderr, "claim_interface(first try): %s\n", usb_strerror()); if (opt_verbose) fputs("detaching kernel driver\n", stderr); if (usb_detach_kernel_driver_np(handle, interface)) { fprintf(stderr, "detach_kernel_driver_np: %s\n", usb_strerror()); return -1; } #endif if (! usb_claim_interface(handle, interface)) { if (opt_verbose) fputs("successfully claimed.\n", stderr); return 0; } fprintf(stderr, "claim_interface: %s\n", usb_strerror()); return -1; } usb_dev_handle * mvpen_init(unsigned short vendor, unsigned short product) { usb_dev_handle *handle; struct usb_device *dev; usb_init(); usb_set_debug(1); usb_find_busses(); usb_find_devices(); dev = _get_mvpen_device(usb_get_busses(), vendor, product); if (! dev) return NULL; handle = usb_open(dev); if (! handle) { fprintf(stderr, "usb_open: %s\n", usb_strerror()); return NULL; } if (_claim_mvpen_interface(handle, 0 /* as interface */)) { return NULL; } return handle; } void mvpen_free(usb_dev_handle *handle) { usb_release_interface(handle, 0 /* as interface */); usb_reset(handle); usb_close(handle); } static char buffer[0x40]; int mvpen_recv_report(usb_dev_handle *handle, char *buf, int len, int timeout) { int got; got = usb_interrupt_read(handle, 0x81 /* as endpoint */, buffer, 64, timeout); if (got != 64) { if (opt_verbose) fprintf(stderr, "interrupt_read(%d): %s\n", got, usb_strerror()); return 0; } memcpy(buf, buffer, len); return len; } void mvpen_send_report(usb_dev_handle *handle, const char *buf) { int len; len = usb_control_msg( handle, USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, HID_REPORT_SET, (HID_RT_OUTPUT << 8) + 0 /* as report id */, 0 /* as interface */, (char *) buf, 8, 1000); if (len != 8) { fprintf(stderr, "control_msg: %s\n", usb_strerror()); exit(1); } } int mvpen_wait_ready(usb_dev_handle *handle) { 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(handle, cmd_status); if (mvpen_recv_report(handle, 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(usb_dev_handle *handle) { 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(handle, cmd_notes_size); if (mvpen_recv_report(handle, 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(usb_dev_handle *handle, FILE *fp) { static char cmd_get_notes[8] = { 0x02, 0x01, 0xb6, 0x00 }; char buf[0x40]; size_t notes_size; unsigned index = 0; mvpen_wait_ready(handle); notes_size = mvpen_get_notes_size(handle); mvpen_send_report(handle, cmd_get_notes); for (;;) { int rd; rd = mvpen_recv_report(handle, buf, 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(handle); notes_size = mvpen_get_notes_size(handle); mvpen_send_report(handle, cmd_get_notes); continue; } index = ((unsigned char) buf[0]) * 0x100 + (unsigned char) buf[1]; fprintf(stderr, "\rreading %u / %u", index, notes_size); if (0x40 - 2 != fwrite(buf + 2, 1, 0x40 - 2, fp)) { perror("fwrite"); return 1; } if (index >= notes_size) break; } fputs("\n", stderr); return index; } void mvpen_clear_notes(usb_dev_handle *handle) { static char cmd_clear_notes[8] = { 0x02, 0x01, 0xb0, 0x00 }; mvpen_send_report(handle, cmd_clear_notes); }
関数インタフェース仕様が異なるのでメインルーチンもやや書き直してあります。
/* main.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> /* for getopt() */ #include <usb.h> int opt_size = 0; int opt_retrieve = 0; int opt_clear = 0; int opt_verbose = 0; /* from mvpen.c */ usb_dev_handle * mvpen_init(unsigned short vendor, unsigned short product); void mvpen_free(usb_dev_handle *handle); int mvpen_wait_ready(usb_dev_handle *handle); int mvpen_get_notes_size(usb_dev_handle *handle); int mvpen_copy_notes_to_stream(usb_dev_handle *handle, FILE *fp); void mvpen_clear_notes(usb_dev_handle *handle); void usage() { fputs( "usage: mvpen [-s] [-r] [-c] [-v]\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[]) { usb_dev_handle *handle; 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); } } handle = mvpen_init(0x0e20, 0x0101); if (! handle) return 1; fprintf(stderr, "mode: %d\n", mvpen_wait_ready(handle)); if (opt_size) { notes_size = mvpen_get_notes_size(handle); fprintf(stderr, "size of notes: %d bytes\n", (0x40 - 2) * notes_size); } if (opt_retrieve) { if (! notes_size) notes_size = mvpen_get_notes_size(handle); if (notes_size <= 0) { fputs("There are no notes\n", stderr); } else { mvpen_copy_notes_to_stream(handle, stdout); mvpen_wait_ready(handle); } } if (opt_clear) { mvpen_wait_ready(handle); mvpen_clear_notes(handle); mvpen_wait_ready(handle); } mvpen_free(handle); return 0; }