MVPen (Pegasus Mobile NoteTaker) の解析 (5)
MVPen をLinux から使う試みシリーズ。Linux の hiddev デバイスドライバの使い方。
※注意※ MVPen が故障する可能性があります
今回は hiddev デバイスドライバのうち,Report の送受信にからむ部分のみ説明します。
HIDIOCGREPORT
, HIDIOCSREPORT
デバイスと Report をやりとりするには,HIDIOCGREPORT
, HIDIOCSREPORT
を ioctl()
で使います。
struct hiddev_report_info repo; int result = ioctl(handle, HIDIOCGREPORT, &repo);
こんな感じで。もちろん repo
にはあらかじめそれなりの内容を設定しておく必要がありますけど。handle
は /dev/usb/hiddev*
を open (2)
したファイルハンドルです。
3番目の引数で指定している struct hiddev_report_info
の定義は以下のような感じです。
struct hiddev_report_info {
__u32 report_type;
__u32 report_id;
__u32 num_fields;
};
report_type
には Report の種別が Input Report か Output Report か Feature Report かを指定します。linux/hiddev.h
では次のような定義があります。
#define HID_REPORT_TYPE_INPUT 1 #define HID_REPORT_TYPE_OUTPUT 2 #define HID_REPORT_TYPE_FEATURE 3
report_id
は Report の ID*1を指定します。num_fields
には前回説明したフィールド数を指定します(MVPen の場合,INPUT でも OUTPUT でも 1 になります)。
あれ?実際に送受信する Report の内容はどこで指定するのでしょうか?これは hiddev がちょっとかわったインタフェースになっていることを説明しなくてはなりません。
hiddev ドライバのマナー
hiddev インタフェースでは,各 Report Type, Report ID ごとに Field の値をドライバ内部に保持しています。これを事後・事前に HIDIOCGUSAGE
, HIDIOCSUSAGE
で取得・設定する必要があります。スクラッチパッド RAM のようなイメージです。
Report を送信するためには,
HIDIOCSUSAGE
で各 Field, 各 Usage ごとに値を設定するHIDIOCSREPORT
でデバイスに Report を送信する
という手順をふむことになります。
逆に Report を受信するためには,
HIDIOCGREPORT
でデバイスから Report を受信するHIDIOGSUSAGE
で各 Field, 各 Usage ごとの値を取得する
という手順をふむことになります。
HIDIOCGUSAGE
, HIDIOCSUSAGE
今説明した Report の各 Field, 各 Usage ごとの値を取得・設定するのが,HIDIOCGUSAGE
, HIDIOCSUSAGE
という ioctl()
コマンドです。
ioctl()
の3番目のパラメータとして 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
は先ほどの struct hiddev_report_info
と同じです。
field_index
は Field のインデックスになります。usage_index
はその Field の中での Usage のインデックスを示します。usage_code
は上位16 bitが Usage Page,下位16 bitが Usage (Type) になります。たとえば Generic Desktop Usage Page (= 0x01) の X (= 0x30) の usage_code
は 0x00010030
になります。
value
というのが指定した Field Index, Usage Index の値になります。注意しなくてはいけないのは,signed long
になっているところです。
MVPen の場合,Report Descriptor の指示にしたがうと,各 Usage の値は Logical には -128〜127 の範囲の値を指定することになります。ですので,
uref.value = 0xff;
というのは期待どおりの結果とはなりません。Logical には -1 となるので,
uref.value = -1; /* or 0xffffffff */
となります。実際にはバイト列として char
型を利用することが多いので,
char code = 0xff; uref.value = code;
のように実質書くことになってこの違いが見えづらいのですが,unsigned char
とか使っていた場合にはハマりますのでご注意を。以後の例では char
を使っているのでこの符号付き値拡張変換が暗黙的になされることを利用したコードになっています。
Output Report を送信する code snipet
MVPen に Report を送信するコードを書いてみます。
MVPen の場合,Output Report は Report ID = 0 しか定義されていません。
- Field の数は 1 つ
- この Field の唯一の Usage は
- Usage Page = 0xff0a
- Usage (Type) = 0x0003
- この Usage の Report Size = 8 bit,Report Count = 8
- つまり 1 byte × 8 の Usage
実直に書くと以下のような感じになります。
char code[8] = { ... }; struct hiddev_usage_ref uref; memset(&uref, 0, sizeof(uref)); uref.report_type = HID_REPORT_TYPE_OUTPUT; uref.report_id = 0; uref.field_index = 0; uref.usage_code = 0xff0a0003; uref.usage_index = 0; uref.value = code[0]; ioctl(fd, HIDIOCSUSAGE, &uref); uref.usage_index = 1; uref.value = code[1]; ioctl(fd, HIDIOCSUSAGE, &uref); /* ... snip snip snip ... */ struct hiddev_report_info repo; memset(&repo, 0, sizeof(repo)); repo.report_type = HID_REPORT_TYPE_OUTPUT; repo.report_id = 0; repo.num_fields = 1; ioctl(fd, HIDIOCSREPORT, &repo);
HIDIOCSUSAGE
で各 Usage のスクラッチパッドに値を設定しておき,最後に HIDIOCSREPORT
で作り上げた Report を送信します。
本当はエラーチェックをしたりする必要があるのですが,それは以降の実例を参照,ということで。
MVPen のステータスを取得してみる
以前書いたように,MVPen のステータスを取得するための Output Report のバイト列は下記のようになります。
02 02 95 95 00 00 00 00
これを実際に送信して,ステータスを取得してみましょう。
コードは下記のようになります。
#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 msg_status[8] = { 0x02, 0x02, 0x95, 0x95 }; int main(int argc, char *argv[]) { int fd; struct hiddev_report_info repo; struct hiddev_usage_ref uref; int i; fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open"); exit(1); } /* send a report */ memset(&uref, 0, sizeof(uref)); uref.report_type = HID_REPORT_TYPE_OUTPUT; uref.report_id = 0; uref.field_index = 0; uref.usage_code = 0xff0a0003; /* set each of usages */ for (i = 0; i < 8; i ++) { uref.usage_index = i; uref.value = msg_status[i]; if (ioctl(fd, HIDIOCSUSAGE, &uref) < 0) { perror("HIDIOCSUSAGE"); exit(1); } } /* send */ 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); } /* wait, wait, wait ... */ sleep(1); /* receive a report */ /* receive */ 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); } memset(&uref, 0, sizeof(uref)); uref.report_type = HID_REPORT_TYPE_INPUT; uref.report_id = 0; uref.field_index = 0; uref.usage_code = 0xff0a0003; /* get each of usages */ for (i = 0; i < 64; i ++) { uref.usage_index = i; if (ioctl(fd, HIDIOCGUSAGE, &uref) < 0) { perror("HIDIOCGUSAGE"); exit(1); } fprintf(stdout, "%02x ", (unsigned char) uref.value); if (i % 16 == 15) fputs("\n", stdout); } close(fd); return 0; }
ほんとうは,select (2)
, read (2)
インタフェースを使うと,Input Report を receive したことを検出できるのですが,そのへんは後日説明するとして,便宜上 sleep()
で Report を receive するまで待っています。
通常 hiddev デバイスを操作するには root 権限が必要なので((/etc/udev/
にそれなりの設定をすると一般ユーザでも操作できるようになる,と思います。)),sudo
を使って実行すると,
% sudo ./hidtest /dev/usb/hiddev1 [sudo] password for dayflower: ******** 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
無事取得できました。
HIDIOCGUSAGES
, HIDIOCSUSAGES
をつかって Usage をバルク取得,バルク設定
このように1つ1つの Usage を HIDIOCSUSAGES
でちまちま設定するのは面倒だよ,ということで,まとめて設定するための API もあります。それが HIDIOCGUSAGES
, HIDIOCSUSAGES
です。
カーネルドキュメントには説明がないのですが,ヘッダファイルに含まれていました。使い方は下記の実例を参照するほうが早いでしょう。
2008-08-19 追記: 下記では values
を任意の量で用意してますけど,これだとときどきうまく動かなくなりました。素直に struct hiddev_usage_ref_multi
(これだと 1024 個ぶん!)をそのまま使う必要があるようです。
#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 msg_status[8] = { 0x02, 0x02, 0x95, 0x95 }; struct hiddev_usage_ref_multi8 { struct hiddev_usage_ref uref; __u32 num_values; __s32 values[8]; }; struct hiddev_usage_ref_multi64 { struct hiddev_usage_ref uref; __u32 num_values; __s32 values[64]; }; int main(int argc, char *argv[]) { int fd; struct hiddev_report_info repo; struct hiddev_usage_ref_multi8 uref8; struct hiddev_usage_ref_multi64 uref64; int i; fd = open(argv[1], O_RDONLY); if (fd < 0) { perror("open"); exit(1); } /* send a report */ memset(&uref8, 0, sizeof(uref8)); uref8.uref.report_type = HID_REPORT_TYPE_OUTPUT; uref8.uref.report_id = 0; uref8.uref.field_index = 0; uref8.uref.usage_code = 0xff0a0003; uref8.num_values = 8; for (i = 0; i < 8; i ++) { uref8.values[i] = msg_status[i]; } /* set bulk usages */ if (ioctl(fd, HIDIOCSUSAGES, &uref8) < 0) { perror("HIDIOCSUSAGES"); exit(1); } /* send */ 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); } /* wait, wait, wait ... */ sleep(1); /* receive a report */ /* receive */ 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); } memset(&uref64, 0, sizeof(uref64)); uref64.uref.report_type = HID_REPORT_TYPE_INPUT; uref64.uref.report_id = 0; uref64.uref.field_index = 0; uref64.uref.usage_code = 0xff0a0003; uref64.num_values = 64; /* get bulk usages */ if (ioctl(fd, HIDIOCGUSAGES, &uref64) < 0) { perror("HIDIOCGUSAGES"); exit(1); } for (i = 0; i < 64; i ++) { fprintf(stdout, "%02x ", (unsigned char) uref64.values[i]); if (i % 16 == 15) fputs("\n", stdout); } close(fd); return 0; }
各 usage_index
で usage_code
が異なる場合*2にどうすればいいのかはわからないのですが,MVPen の場合は単一の usage_code
で済みますのでこれでうまくいきました。