MVPen (Pegasus Mobile NoteTaker) の解析 (8)

MVPenLinux から使う試みシリーズ。いよいよ 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

標準出力にモバイルノートデータを出力するので,リダイレクトして取得してください。

取得できるモバイルノートデータはバイナリ形式です。このフォーマットは次回解説します。