2008年1月28日月曜日

ugenでラピッドプロトタイピング(*BSDでUSB)

*BSDにはugen(usb generic device)という汎用のUSBデバイスドライバが実装されていて、USBデバイスを刺したときに適切なデバイスドライバが見つからなかったときには、ugenデバイスしてアタッチするようになっている。普通は「なんだugenかよ」と思って「サポートされてないじゃんか」となる(普通じゃない人は「しょうがないからデバイスドライバ書くか」となる)のだが、実はugenとして見えるのはそれはそれで大変意味があることだ。
というわけで、ugenを使ったrapid prototypingについて書いてみようと思う。

ugenが出来ることは、

  • /dev/ugenN.MMというスペシャルファイルを介したUSBデバイスとのエンドポイントとの通信
で、read(), write(), ioctl()をつかって、USBデバイスの概ねほとんどの機能にアクセスできる。USBは「ユニバーサルシリアルバス」なのでread(), write()できれば、適切なアクセス方法をプログラムできれば大体なんでもできるはずだ。
もちろん、ugenもデバイスドライバなので、あるデバイスに適切なデバイスドライバが実装されていてkernelにコンフィグされているときはugenとしてアクセスすることは出来ない。(kernelコンフィグによっては強制的にugenにすることもできる。man ugen参照のこと)。

ugenで実装することとkernelで実装することの利点・欠点をまとめると、

ugen利用の利点:
  • kernelで実装しなくてもいいので、開発が楽
  • kernelに実装しなくていいので、配布や利用が楽
  • APIがあまり変わらないので、kernelバージョンがあがっても追従しやすい
  • ugenっぽいデバイスドライバがあるOSなら移植しやすい(かも)
ugen利用の欠点:
  • 参考になる実装や文書が少ない
  • kernelに取り込まれることは無いので、マージはされない
  • kernelスペースとuserlandのやりとりが多いのでパフォーマンスは期待できないかも
  • kernel内のほかのフレームワークとの連携は難しい
という感じだと理解している。本質的にはkernelで書こうがugen経由で書こうがUSB的にはやることはほとんど変わらないので、kernelをかける人もプロトタイプツールとしてugenというのは検討しても良いんじゃないだろうか。

というわけで簡単な使い方から。基本的なステップは
  • usbctlなどのツールでUSB device descriptorを取得して、エンドポイントの構造をざっくりと理解する。D02HWみたいに何かしたら化けるデバイスもあるので、ugenの-Dオプションを使って全部のconfigurationをdumpしておくと便利。何回もみるので、ファイルに出しておいたり、印刷して脇に置いたりしても良い。usbctlとかusbgenコマンドはpkgsrcのusbutilを入れれば入るはず。
  • デバイスを刺したときにでたugenのデバイスのugenN.00をO_RDWRでopen()する。これがコントロールエンドポイント(EP0)になる。
  • 開いたEP0に対して、ioctlでUSB_GET_DEVICEINFOを送りつけると、device descriptorが取得できる。取得したusb_device_info構造体にvendor IDとproduct IDが入っているのでこれでデバイスの認識をする。複数のデバイスをサポートしていないなら、お目当てのデバイスがみつからなければexitしてしまえばよい。
  • お目当てのデバイスがみつかったら、最初に調べておいたエンドポイントを必要な数だけopen()する。openするファイル名は/dev/ugenN.MMのMMがendpoint番号。endpointがINだったらO_RDONLY、OUTだったらO_WRONLYにするのを忘れずに。IN/OUTだったらO_RDWRで良い。
  • 後は開いたファイルディスクリプタに対して、書き込んだり、ioctlを発行すれば、usbのバスに必要なトランザクションが生成される
と、こんな感じ。

デバイスを探索するコードのサンプルを書いてみると、

#include </dev/usb/usb.h>
int
XXX_match(int fd) {
struct usb_device_info udi;

/* get device information */
if (ioctl (fd, USB_GET_DEVICEINFO, &udi) < 0) {
perror("can't get device information");
return -1;
}
fprintf(stderr, "device info: VID:%#.4x PID:%#.4x\n",
udi.udi_vendorNo, udi.udi_productNo);
/* check the device is XXX */
if (udi.udi_vendorNo != XXX_VID || udi.udi_productNo != XXX_PID){
fprintf(stderr, "can't find any NM30 on %s\n", dc->dev);
return -1;
}
printf("==== Found a XXX ====\n");
return 0;
}

のようになる。

使ってみていて注意しないといけない点は、
  • 結構kernelの奥深くをさわっているらしく、お行儀が悪いとすぐにkernelパニックを引き起こす
ということ。openしたファイルディスクリプタの始末や、書き込むメッセージのサニタライズは最初からそれなりにお行儀よくしておいたほうがいい。

長くなったので続きは次回。

0 件のコメント: