*BSDでのUSBデバイスドライバーの実装

山城 潤<yamajun@{ofug.net, cr.ie.u-ryukyu.ac.jp}>

ここ半年ほど、個人的に行なってきた*BSDからUSB機器をいじる仕事について、 その成果と調査結果について発表する。
(これはあくまで私個人の試行錯誤の記録なので、 ちゃんとした情報は最後に示している参考資料などを見て下さい)

個人的に関わっている仕事

ifp-line(ifp-driverプロジェクト)

iFP写真

http://ifp-driver.sourceforge.net/

チェコのPavel Krizが、 韓国iRiver社 (日本法人)のMP3プレイヤー iFPシリーズをUNIX(*BSD,Linux)から制御するために開発したプログラム。 コマンドラインから操作する。

関連するプロジェクトには、 Stark ViktorとMichele Baldessariがifp-lineのコードをベースにして書いた ライブラリーlibiriver と、それを利用したGNOME VFSモジュール giriverがある。

私は、2003年4月の中ごろからメーリングリストに各種パッチを送り続け、6月にcvsコミット権を得た。 現在までの主な変更は以下の通り。

このプロジェクトでは、Windows版マネージャーとプレイヤー間の通信を 解析するために、Windows上で動作するUSBパケットスニッファー SnoopyProを使用した。

今後の課題として、Mac OS X対応があるが(公式GUIマネージャーは既に存在する)、 受信するデータのほとんどがデタラメになってしまう (エンディアン変換すればいいってものでもない)のが悩み。 ADCから ダウンロードしたUSB開発キットには、USBロガーらしきものがあるが、 まだ使いこなしていない。

追記(2003-11-15):
Mac OS X対応は完了した。OSXで動かなかった理由は、 libusbを利用するプログラムに必須なインターフェイスのclaim/release処理を 行なっていなかったせいである(*BSD, Linuxではそれでも動作していた)。 この問題は、Johan Andrenのパッチによって改善された。

*BSD用のUSB無線LANアダプター用のドライバー

WLI-USB-KS11G写真

最近、各社からUSBで接続する無線LANアダプターが出てきているが、 *BSDではまだ使えない。また、 無線LAN内蔵ノートパソコンには、 内部でUSB無線LANモジュールを持っているものもあるらしい (電波を飛ばしたくない時にスイッチ一つでハードウェア的に切り離すためと思われる。 似たようなものにUSB2.0と無線LANが一緒になったCardBusカード (OEM元?)もある)。

これらの機器を*BSDで使わない理由はない!(というか、ノートパソコンの 無線LANが使えないのはもったいないという人も...)ということで、 バッファローの無線LANアダプターWLI-USB-KS11Gを買ったのを機に、 *BSDでもUSB無線LANが使えるようにしようと思った。

開発は、NetBSD-currentをインストールしたIBM ThinkPad上で行なっている。

情報収集

WLI-USB-KS11Gについて情報収集した結果、以下の情報が得られた。

そこで、/sys/dev/ic/wi.cの中身を、USBの入出力API に合わせるように移植することでUSB無線LANドライバーを実装しようとしている。

現状

現在は、ヒマと興味がある時のみ開発を進めているので、wi.cの 多くの関数がまだ移植できていません。また、他のUSB Ethernetアダプターの ドライバーから見よう見まねで書いているので、本当にこれで良いのか という自信もあまりありません^^; (というか、wi.cとほとんど同じ役目をするコードを書いてしまうと、 同じようなコードを2つ保守しなければならなくなるという欠点がある)

また、FreeBSD PRESS No.18で、Warner Losh氏がやる気があるようなそぶりを 見せているので、そちらの方が先に出来上がるかも知れません。

ボクがずっとwiドライバに心血をそそいできたから、彼はprism2が載った USB無線デバイスをプレゼントしてくれたんだ。このプレゼントが ボクを楽しませてくれるのか、それともとんでもないフラストレーションを もたらすのか、そのうちわかるだろう。

追記(2003-11-19):
OpenBSD方面で、USB wavelanフロントエンドがcommitされている!

*BSDでうはうはできるのも時間の問題か?

追記(2004-04-01):
FreeBSDでもドライバーが完成したようです。 5.1-RELEASE用で、カーネルソースにパッチを当てて利用します。 私の5.2環境ではコンパイルエラーが出てるけど...

ユーザーランドでのUSBプログラミング

*BSDでドライバーが用意されていない機器は/dev/ugen*として認識される。 これをユーザーランドから利用するソフトウェアを書くには2種類の方法がある。

/dev/ugen*を叩く

他のキャラクターデバイスと同様に、 open(2)/dev/ugen*デバイスファイルを開き、 read(2),write(2),ioctl(2)経由で操作する。

#include <dev/usb/usb.h>
...
/*
 * 長くなるのでデバイスの検索は省略する。
 * 利用可能なデバイスを探し、ベンダーIDとプロダクトIDが一致するかどうかを調べる
 * ここでは、devname = "/dev/ugen0" とする。
 */

sprintf(outname, "%s.1", devname);  // 出力用デバイス名の生成
sprintf(inname,  "%s.2", devname);  // 入力用デバイス名の生成

if (fd = open(devname, O_RDWR) == -1) {
    perror(devname);
    exit(1);
}
if ( (outfd = open(outdevname, O_WRONLY)) < 0) {
    perror(outdevname);
    close(fd);
    exit(1);
}
if ( (infd = open(indevname, O_RDONLY)) < 0) {
    perror(indevname);
    close(fd);
    close(outfd);
    exit(1);
}

ioctl(fd, USB_GET_HOGE, &data);
...
write(outfd, outbuf, sizeof(outbuf));
...
read(infd, buf, sizeof(buf));
...
close(fd);

libusbを利用する

http://libusb.sourceforge.net/

libusbは各種OSのUSB関係APIのラッパーなので、 *BSDだけではなく、LinuxやMacOS Xでも使える。 また、ports/pkgsrcでパッケージングされているので、 devel/libusbからインストールできる。

デバイスとの通信には、制御転送(メッセージ程度)を行なう usb_control_msg()と、バルク転送(大容量)を行なう usb_bulk_{read,write}()を使って行なう。

#include <usb.h>
...

struct usb_bus *bus;
struct usb_device *dev;
usb_dev_handle *dh;

usb_init();
usb_find_busses();

/* デバイスの検索 */
for (bus = usb_get_busses(); bus; bus = bus->next) {
    for (dev = bus->devices; dev; dev = dev->next) {
	if (dev->descriptor.idVendor == HOGE_VENDOR &&
	    dev->descriptor.idProduct == HOGE_PRODUCT) {
	    goto device_found;
	}
    }
}
/* デバイスが見つからなかった場合 */
fprintf(stderr, "Device not found.\n");
exit(1);

device_found:

dh = usb_open(dev);

// デバイス使用開始前に、インターフェイスを要求する。
usb_claim_interface(dh, dev->config->interface->altsetting->bInterfaceNumber);
...
usb_control_msg(dh, request_type, request, value, index, msg, sizeof(msg), timeout);
...
usb_bulk_read(dh, read_endpoint, readbuf, sizeof(readbuf), timeout);
...
usb_bulk_write(dh, write_endpoint, writebuf, sizeof(writebuf), timeout);
...
// デバイス使用開始後に、インターフェイスを解放する。
usb_release_interface(dh, dev->config->interface->altsetting->bInterfaceNumber);

usb_close(dh);

カーネルレベルでのUSBプログラミング

カーネルレベルでデバイスドライバーを実装しなければならない時(Ethernetなど)は、 /sys/dev/usb/*を参考にして行なう。

参考資料

USB機器関連のソフトウェア

USB関係の開発ツール

その他の資料