E-mobileの電話型端末H12HWを試す機会が会ったのでOpenBSDでのデータ通信を試してみた。H12HWは型番が示すようにHUAWEI社の製品で3.6MbpsのHSDPAに対応している。
- product HUAWEI Mobile 0x1008 HUAWEI Mobile
- {{ USB_VENDOR_HUAWEI, USB_PRODUCT_HUAWEI_Mobile}, DEV_HUAWEI},
hack, hack, hack, write and hack.
E-mobileの電話型端末H12HWを試す機会が会ったのでOpenBSDでのデータ通信を試してみた。H12HWは型番が示すようにHUAWEI社の製品で3.6MbpsのHSDPAに対応している。
週末に海外の見知らぬ人から「(俺の)sierra wirelessのHSDPAモデムがopenbsdで使えないんだけど」というメールが届いた。ちょうど他の作業で煮詰まっていたので息抜きに何通かメールをやりとりして、OpenBSDのumsm(4)で動くパッチを作ってみた。先方曰く「ちゃんと動いた!」と報告してくれたが、手元に無いデバイスなので不安は残る。ふと思い立ってgoogleするとSoftBankモバイルのC01SWはSierra wirelessのOEM製品らしい。周りを少し見渡すと持っている人が見つかったので、実際にためしてみた。
#define TRUINSTALL_CHANGEMODE_REQUEST 0x0b
usbd_status
umsm_truinstall_changemode(usbd_device_handle dev)
{
usb_device_request_t req;
usbd_status err;
req.bmRequestType = UT_WRITE_VENDOR_DEVICE;
req.bRequest = TRUINSTALL_CHANGEMODE_REQUEST;
USETW(req.wValue, 0x1);
USETW(req.wIndex, 0);
USETW(req.wLength, 0);
err = usbd_do_request(dev, &req, 0);
if (err)
return (EIO);
return (0);
}
E-mobileの最近のモデムを触ってみる機会があったのですこし調べてみた。結果を共有。
少し前の話だがE-mobileのD21LCをしばらく借りることができたので、少し解析してOpenBSDで使えるようにしてみた。その時の話をメモとして残しておくことにする。
D21LCは中国のLongcheer社製のHSDPAモデムだ。E-mobileでは最初はHuawei社のモデムを使っていたが、しばらく前から2社の製品を投入している。D21LCも他のE-mobileのモデムと同様に「ゼロインストール」機能が搭載されている。ゼロインストールとはドライバが入っていないOS(つまり初回挿入時)にはモデムをUSBマスストレージデバイスとして認識させて必要なドライバをモデムから直接インストールさせる仕組みである。ドライバが動き始めると、そのドライバがモデムのモードを変更してモデムとして動作するようになる。Windowsなどからは大変便利な機能であるが、ほかのOSで使うためにはちょっとおせっかいだ。
Huawei社のモデムはメモ:D02HW解析で昔書いたように特別なUSBコマンドを発行すると、USBマスストレージモードからモデムモードに動作モードが切り替わった。D21LCの場合はどうだろうか?
D21LC:モデムモードへの切り替えUSBアナライザでUSBトランザクションをモニタしてみると、あるタイミングでデバイスがリセットされるタイミングがあった。これがモデムモードへの切り替えコマンドだろうと推測してもう少し中身を眺めてみる。
D21LCのモード変更コマンドはUSBマスストレージデバイスに対するコマンドとして実装されていた。(コマンドパケットの先頭の0x55 0x53 0x42 0x43:USBCが規定のマジックナンバー)といっても、USBマスストレージデバイスの仕様には定義されていない独自コマンドを発行している。
さらにモデムの動作を眺めてみたが、それ以降は普通の3Gモデムと同様の動作をしているようだ。
というわけで、このシーケンスを発行するようにデバイスドライバを拡張してやれば、ほかのOSでもモデムとして利用できるはずだ。
シリアルポート
縁あってOpenBSDの開発者の人たちと一緒にhackする機会があったので、なにかコントリビューションできることはないかなと考えたところ、OpenBSDの3Gモデムのサポートを拡張することにした。
特にD02HWは、本当の名前はE220といって世界各地で使われているメジャーなモデムなので、これがまともに使えるようになるとうれしい人がそれなりにいるだろうから、ちょうど良い。
OpenBSDもNetBSDと同様に、FreeBSD由来のUSBサブシステムを採用しているので、usbデバイスドライバは大きな差異は存在しない。NetBSD用につくったuhmodem(4)をそのまま移植するという手もあったのだけど、OpenBSDにはHSDPAモデム用のドライバとしてumsm(4)がすでに存在していたので、それを拡張する形で実装してみることにした。
umsm(4)(拡張前)
dev/usb/umsm.c(rev.1.21)を眺めてみると、非常にシンプルなドライバになっていた。具体的に言うと、
struct ucom_methods umsm_methods = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
};
となっていた。struct ucom_methods は、上位ドライバとなるucom(4)のopen/closeなどの各処理におけるデバイス依存コードへの関数ポインタを渡すための構造体なので、この状態ではシリアルに関する処理はucomでのデフォルト動作だけで実行されることを意味している。
どうしてこれで他のドライバが動作しているのだろうか?と「???」と思いながらざっくりとコードを追ってみたところ、シリアルデバイスのinterrupt endpointに関する処理がざっくりと抜け落ちていることに気がついた。単なるin/out endpointを処理するだけなら、確かにucom(4)における特別な処理は必要ないが、モデムで通信を行う際には自分から始めるだけではなく割り込み処理が必要である。
近くにいたOpenBSD開発者に聞いてみたら「おれが使っているE220で昔だれかが作ったパッチで試した見たところ、たしかにPPPまでは張れることもあるがうまく動かなかった。つながるけど切れたり通信できなかったりしてね。それから使ってない」と言われた。さもありなん、ということでinterrupt endpointの処理を拡張するところから始めることにする。D02HW(E220)以外でも役にたつだろう。
interrupt endpoint処理の追加
endpointの割り当て処理はumsm_attach()の中で行われる。data用のbulk in/out endpointしか処理していなかった部分を、以下のように変更する。また、struct umsm_softcにもinterrupt endpoint周りの状態を保持するための変数を拡張しておく。
if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
UE_GET_XFERTYPE(ed->bmAttributes) == UE_INTERRUPT) {
sc->sc_intr_number = ed->bEndpointAddress;
sc->sc_isize = UGETW(ed->wMaxPacketSize);
DPRINTF(("%s: find interrupt endpoint for %s\n",
__func__, sc->sc_dev.dv_xname));
} else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
uca.bulkin = ed->bEndpointAddress;
else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK)
uca.bulkout = ed->bEndpointAddress;
これで、interrupt endpointを持っているinterfaceがあったら、interrupt endpointを正しく扱うための準備ができる。
次に、ucomをopenしたときに検出したinterrupt endpointをucom割り込みとして利用するためのコードを追加しなければならない。まずucomをopenしたときにusb的にinterrupt endpointを登録するコード(usbd_open_pipe_intr()を呼ぶ)と、closeしたときに後始末(usbd_about_pipe() & usbd_close_pipe()を呼ぶ)をするデバイス依存の関数を追加する。それぞれubsa(4)から持ってきて、umsm_open(), umsm_close()ということにしておく。ucomがopen/closeされるタイミングで呼ばれるデバイス依存関数は、前述のstruct ucom_methodsで指定しておけばよい。
さらに割り込み発生時に呼び出されるデバイス依存関数とその補助関数として、同じくubsa(4)からumsm_intr(), umsm_get_status()を持ってくる。同様にstruct ucom_methodsに指定する。
これで、interrupt endpoint周りの処理は完了。普通のHSDPAモデムのDoCoMo A2502をつなげて試してPPPセッションが正しく張れるようになっていることを確認。とりあえずここまでで半分くらい終了。
ここまでのコードの差分は、OpenBSD CVSwebで確認できる。
次回のエントリーでは、D02HWのモード遷移のための関数の統合について書く予定。