ラベル USB の投稿を表示しています。 すべての投稿を表示
ラベル USB の投稿を表示しています。 すべての投稿を表示

2009年6月22日月曜日

UNIX系OS用UQ wimaxドライバの作成(1)

MTM3が終わってから、今度は2週間ちょっとの出張がはいってしまってなかなかblogを書く時間をとれなかったが、いつまでも「MTM3参加中」のエントリがトップなのも寂しいので、最近のwimax関係のアップデートを書いてみる。


UQ Wimax解析の状況

GCT720X系のチップセットを使っているインターフェイス(UD01SSとかUD01OK)の解析はほぼ完了した。要素としては大きく分けると、
  • データ通信系(Ethernetフレームを交換する部分)
  • 認証系(EAPパケットを処理して通信に必要な鍵を生成する部分)
  • ネットワークエントリ系(認証系に加えてwimaxネットワークに参加する部分)
  • データストア系(加入者識別子、パスワード、デバイスのX509証明書の処理)
  • 管理系(通信状態把握、パラメータ設定)
くらいに分類される。通信に必要なのは最初のデータ通信系の機能だけだが、そこに至るために残りの部分を攻略しなければならかった。「データストア系」はネットワークに接続するために必須の部分であるのに関わらずまったく仕様が公開されていないため、比較的時間を費やしてしまった。

動作検証コード

解析結果はすべてOpenBSD上で検証コードを作成して、実際の動作を確認した。
未知のデバイスのドライバをカーネルドライバとしていきなり実装するのは開発効率が悪すぎる。今回は、ずいぶん前に「ugenでラピッドプロトタイピング(*BSDでUSB)」で書いたようにugen(4)を利用してユーザーランドで実装してみた。さらに、ethernetインターフェイスへのアクセスにもtuntap仮想インターフェイス(tun(4))を用いてユーザランドドライバとして実現した。
ugen(4)を用いてUSBバスをユーザランドドライバに見せたうえで、データフレームをtun(4)でカーネルに挿入するアプリケーションをかけば、実質上ユーザーランドだけでデバイスドライバを書くことができる。
この「ユーザランド」ドライバの構造(概略)を右図に示す。簡略化のために一個のプロセスで全部入りでつくったが、検証用には十分だろう。
機能としては、Wimaxのネットワークをスキャンして、UQ Wimax決めうちで接続処理を行う。デバイスから必要な情報を引きずり出して、認証機構がEAP-TTLS/MSCHAPv2でWimax-PKMv2を処理してログイン&鍵対の生成を行っている。
うまくつながれば、あとはugen(4)とtun(4)をブリッジしてEthernetインターフェイスとして動いてくれる。普通にDHCPでアドレスをもらって通信できるところまでは動作確認済みだ。
あ、あと、Ethernetブリッジしているのにtapじゃないのは、OpenBSDではtapがtun(4)に統合されているから。tunをLayer2モードで駆動している。

ユーザランドドライバの副次的な効果として「移植性の高さ」がある。たぶん、ほとんど変更しなくてもbsd系のOSでは動くだろうし、ugen(4)をlibusbかなにかに置き換えればOSXやlinuxでも動いちゃうんじゃないかなと予想している。だれかやってみないかな。
ソースは公開する予定だけど、作業の優先順位は要望によって変わります。

今後の予定

ちょっと落ち着いてきたので、解析結果のまとめをつらつらとblogに書きながら、(途中になっている)kernelドライバの作成をやってみようとおもっている。
変なNDAに縛られちゃう前に、知っていることは公知にしておくといいよね?

2009年5月14日木曜日

OpenBSDでSoftbank C01SWを使う

週末に海外の見知らぬ人から「(俺の)sierra wirelessのHSDPAモデムがopenbsdで使えないんだけど」というメールが届いた。ちょうど他の作業で煮詰まっていたので息抜きに何通かメールをやりとりして、OpenBSDのumsm(4)で動くパッチを作ってみた。先方曰く「ちゃんと動いた!」と報告してくれたが、手元に無いデバイスなので不安は残る。ふと思い立ってgoogleするとSoftBankモバイルのC01SWはSierra wirelessのOEM製品らしい。周りを少し見渡すと持っている人が見つかったので、実際にためしてみた。


Tru-install

C01SWはsierra wirelessの「Tru-install」という機能を搭載している。これは、他の3Gモデムでもよくある「普段はUSBマスストレージのデバイスとしてみえて、そこにデバイスドライバがはいってる。そのデバイスドライバをつかってモデムモードに切り替える」という機能だ。windows以外ではあんまり便利ではないし、自分でモードを切り替えてあげないといけない。

Tru-installのモード変更方法

もうこの手の話は何回もしているので、簡潔にモード変更方法を書いておく。
  • USB requestType = Write/Vendor/Device
  • USB request = 0x0b
  • USB request wValue, wIndex, wLength = 0x1, 0, 0
というコントロールリクエストをデバイスに送ると、デバイスがUSBバスから切り離されて再度認識されたときにはモデムデバイス+マスストレージデバイスの複合デバイスに変化している。上記に対応する関数を抜粋すると、こんな感じ。


#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);
}


umsm.cに対する変更の全体はCVSで参照できる。

あと、OpenBSDではsys/dev/usb/usbdevsにこのデバイス用のProduct IDを追加しなければならなった。これは追ってコミットしておく。

C01SW概略

アタッチしてみてちょっと驚いたのはインターフェイスが7本出現したことだ。一般に3Gモデムは複数(2−3本)のインターフェイスを用意しているが7本出てきたのは初めてだった。
USBディスクリプタを見ると4-7本目のインターフェイスはインタラプトエンドポイントを含んでいるのでこれらのどれかが通信用のポートのはずだ。上から試してみたところ、4本目が通信用のポートだった。5−7本目のポートはエコーバックされないがATコマンドに反応しているので何らかの管理ポートだと推測される(詳細は見ていない)。

C01SWで通信する

OpenBSDのkernel ppp実装のpppdを使って通信を試みたところ、さっくりとつながった。
設定は昔「NetBSDでEmobile D02HWを使う(設定編)」で書いた物とほとんど同じで、
  • ifconfig ppp0 create でpppインターフェイスを作る
  • /etc/ppp/peers/に対応する設定ファイル(たとえばsoftbank )を作ってttyU3を通信ポートに設定する
  • ソフトバンク用のユーザIDとパスワードを/etc/ppp/chap-secretに書いておく
だけだった。pppd call softbankだけでpppがつながって通信できた。


2009年5月10日日曜日

UQ WimaxのUD01SS解析 (4)

前回の続き。

一行まとめ「UD01SSの内部コンソールを取得する」話。

OKIネットワークスが出しているUD01OKの解析をしてみたところ、同じGCTセミコンダクタのGDM72XXを使っている製品なら、ほぼUD01SSと同様な構成になっているのではないかと予測を立てることができた。たぶんGCTセミコンダクタが提供するファームウェアおよびデバイスドライバのSDKがLinuxをベースとしたものになっているのだろう。ということで、今後もGDM72XXを使う製品が出るのなら今回の一連の解析も役に立つだろう、ということでよりディープなところもまとめてみたいと思う。

このエントリではUQのUD01SS(やUD01OKなどのGDM72XXベースのWimaxインターフェイス)内で動いている「Wimaxアプリケーション」の操作コンソールについて説明する。

UD01SSの内部ではLinuxが動いている、というのは(1)で 書いた。汎用OSであるLinuxをファームウェアとして利用している以上、カーネル内部だけではなくユーザランドでも何らかのアプリケーションが動いて いるのは当然だ。解析を進めている途中で、そのアプリケーションと対話的に通信していろいろな情報を取得できるようになった。右にWimaxアプリケーションのコマンド一覧(helpコマンドの出力)を載せる(長いので画面キャプチャ形式)


UD01SSの内部アプリケーション:/usr/wimax

UD01SSのユーザランドとしてWimax関連で動いているのは/usr/wimaxコマンドである。このコマンドがGDM72XXのいろいろな要素(内部の別プロセッサとかで動いているプロセスとか)と通信しながら、ホストインターフェイスを提供している。
ホストPC側にWimaxインターフェイスとしての機能をUSBバスを経由して見せているのもこのアプリケーションで、ホスト側からのコマンドはこのアプリケーションが処理している。さらに管理用の対話的コンソールも提供されている。対話的コンソールが受理するコマンド一覧を上の図に示した。使い方はまだわからないことも多いがUD01SS内部で処理しているWimax関連の機能の内部状態を見ることができるので大変便利だ。
右図の画面ダンプはparamコマンドの出力例(mac addressは隠してある)だ。paramコマンドはデバイスの設定パラメータをダンプしてくれる。Wimaxの物理層まで興味があれば役に立つこともあるかもしれない。
ヘルプ画面にあるshellコマンドは興味深い。/bin/shを立ち上げて(fork & exec)内部のlinuxに対するshellアクセスを提供してくれるはずだが、子プロセスとしてのshellはUSBバスに対する通信機構を持っていないのでこの方法でのアクセスでは使えない。大変残念だがUSB経由(wimaxアプリケーションを経由)のshell取得は原理上無理があるようだ。

Wimaxアプリケーションのコンソールでできること

できることは大体以下の通り。

  • 対話的にコマンドを発行してレスポンスを受け取る。たとえば「version」「param」「dump」コマンドはいろいろな内部状態を出力する。
  • 対話的にコマンドを発行して動作モードを変える。(詳細は不明)
  • メモリ状態などを読み込む(詳細は不明)。発行してもハングアップしてしまう場合が多い。
  • 「log on wimax」コマンドを発行すると、非同期メッセージを受け取れるようになる。たとえば、内部の状態遷移に付随して発生する様々なメッセージを受信できる。さらに「sc」「nds」コマンドでデバグメッセージを出力させることで、もっといろいろなことを教えてくれるようになる。
最後の機能は非常に強力だ。単純なUSBバス解析だとバイナリ列の{コマンド,レスポンス}の組を統計的に解析していって意味を抽出する作業が必要になるが、コマンドに対して人間が読める形のレスポンスや状態遷移のタイミングを取得できればシステマチックな解析が可能になる。

取得できる出力の例
2009/4月のファームウェアアップデート後のUD01SSのVersion

DM> version
version 0.3.8.0 (00030800)
DM> dump ver
FW : Rev=1.6.6.4, 2009/04/03 18:18:00 (build: 2009/04/06 16:58:33)
RF : srf72xx-081025(72050227,0), Default
CFG: PHY=0x602, SPEC=204, CPU=3, SD_AFC, UPT, DNT, ARQ2, IPC, FREQ2.5G, SAMSUNG
MAP: 2009-03-24-1613-WAVE2

ほかにも接続状況なども取得できるが、それらは今後対応した操作を説明するときに説明しようと思う。

アクセス方法(概略)

Wimaxアプリケーションのコンソールに対するアクセス方法の概略を載せておく。詳細はGDM72XX系のコマンド体系をまとめるときに補足する。

GDM72XXのコマンドは、大体
  • 2バイトのヘッダ(Command/Response Type)
  • 2バイトのペイロード長(データ部分だけ。ヘッダ部分は含まない)
  • データ
のTLV形式で定義されている。(USBなのでビッグエンディアン)
コンソールに対するデータ送信は、Type:0x030cで、送信したい文字列に"\n\0"文字をつけてデータとして送信する。
受信は非同期メッセージを扱う場合はちょっと複雑だが、普段はType:0x830dのデータの9バイト目から文字列として扱えばよい。継続行がある場合は文字列の最後が非NULL文字になるので、文字列の最後がNULL文字になるまで繰り返し読み込めば複数行のレスポンスも取得できる。

いまはとりあえずopenbsdのugenで書いたユーザランドアプリケーションで実験をしている。プログラムの長さで3-4KBくらい。もしUD01SSとかで遊びたい人がいたら適当に声をかけてほしい(一人だと飽きそう)。

次回

次回のエントリでは、GDM72XXのコマンド体系について説明しようとおもう。認証部分はそのあとになる予定。

2009年5月5日火曜日

UQ WimaxのUD01SS解析 (3)

(2009/5/7 認証フローの図を追加)

前回の続き。
UQのUSB型mobile wimaxインターフェイスであるUD01SSの解析メモ(3)。
今回は「どうやってWimaxネットワークに接続するか?」を説明。

  • 「Linuxとか*BSDでUQ Wimaxを使うのはちょっと敷居が高いので、どう越えるか?」
のバックグラウンドになるかな。

Mobile Wimaxの概略

Mobile Wimaxは下の仕様がIEEE802.16eおよびIEEE802.16-2004として規定され、運用などのルールがWimax Forumによって規定されている中・広域の無線アクセス技術である。802.11シリーズの無線LANと同様にIPベースの通信を前提としたアーキテクチャを持っており、高いコストパフォーマンスと相互運用性といった特徴を持っている。

ユーザからみると、「PCに挿せば動く家の外でも使える無線LAN」として動くことが期待されているわけだが、802.11よりも「セキュリティ」や「認証」といった点で気を使わなければならない点も多い。UQ wimax (Wimax forum Wave2)ではそのあたりをどのようにクリアしているのかをちょっとだけ説明しておく。

Mobile Wimaxネットワークへのログイン

カギとなるのは「認証」である。UQのネットワーク(もしくはUQからMVNOしたキャリアのネットワーク)を利用するためには以下の2つの認証を越えなければならない。
  1. デバイス認証(およびネットワーク認証)
  2. ユーザ認証
前者はそのデバイスがWimaxフォーラムの認証を通っているデバイスなのかを検証し、接続してもよいデバイスかを判断するために利用される。それぞれのMobile WimaxデバイスはX.509の証明書を持っている。証明書はデバイスのMACアドレスをWimaxフォーラムの認証局(CA)でサインしたもので、MACアドレスの詐称を防いでいる。これはWimaxフォーラムで規定されており、EAP-TLSを用いてX.509証明書を相互に交換することで相互認証を行う。

後者は課金のために必要な認証。契約者のIDと認証情報を使って、ネットワークにログインするために必要になる。Wimaxフォーラムでは前者のデバイス認証の結果でネットワークへのログイン処理を行うことも許容しているが、UQではMVNOの一部でAAA(認証)サーバ自体も外部にある設定を仮定しているので、ユーザ認証として独立した認証が必要になる。UQではEAP-TTLSを用いた暗号通信路の上にMS-CHAPv2を使ったuser/passwd認証を行っている。

UQの専用アプリケーションの役割

UQではネットワークに接続する際に専用のユーティリティソフトウェアが必要となるが、このソフトウェアの役割の一つは後者の「ユーザ認証」手続きだ。EAP-TTLS/MS-CHAPv2自体は標準化されたプロトコルなので、UQ全体で一つのアプリケーションで済むんじゃないか?みたいな話を聞いたこともあるが、実はそれは難しい。なぜかというと、各デバイス毎に「ユーザ情報(ID/Pass)」やWimaxの基地局からのEAPトランザクションを扱う方法が異なっているからだ。デバイス依存の差異を吸収する仕組みが存在しない現在では各ベンダ毎が自分のデバイスを扱うユーティリティを提供するしかない、という判断なんだろうと理解している。

UD01SSの認証関連機能

UD01SSでは、Wimaxデバイス認証を外部アプリケーションの助けを借りずに行える。つまりデバイス単体でEAP-TLSの確立および証明書の交換、検証、承認が可能で、外からコマンドを送るだけで自動的に進めてくれる。

ユーザ認証部分はちょっと複雑だ。
UQでは利用者にIDやパスワードを管理させずに「Wimaxインターフェイスをさすだけで使える」運用方法を採用している。そのため、UQネットワーク にログインするためのユーザIDおよびパスワードはデバイス内部のNVRAM領域に保存され、ユーザには見えないように隠ぺいされている。これが複雑さの原因だ。
ユーザ認証機構はWimaxデバイスの外部の「ユーティリティアプリケーション」に存在している。そのためUD01SSではユーザ認証のためのEAPトランザクションをUSBバスを通してホストPCにリレーすることでWimaxの認証サーバ(AAAサーバ)とホスト側の「ユーティリティアプリケーション」間の通信を確立させる。ホスト側のユーティリティアプリケーションはリレーされたEAPトランザクションを解釈してEAP-TTLSトンネルを確立したあとに、「ユーザIDとパスワード」を用いて認証を進める。デバイス証明書やユーザIDなどの情報はデバイス毎に異なるため、UD01SSではNVRAM領域に記録してあるらしい。ユーティリティアプリケーションはNVRAMからデータを読み取って処理を進めている。

認証がうまくいった後

EAPでの認証が成功すると、Wimaxネットワークはそのデバイスが網につながることを許可してくれる。
EAPトンネルはこの時点で破棄してもかまわない。WimaxのPKMの残りの部分はUD01SSの内部で処理されてユーザ側にはその成功・失敗しか通知されない(この辺はまだちゃんと実際に試したわけではないのでぬけがあるかも)。
(2009/5/13補足):ユーティリティアプリケーションのEAPセッションからUD01SS内部のPKMの処理に引き継ぐためにMSK(マスターセッションキー)をデバイス側に引き渡す必要がある。

あとはUD01SSは比較的簡単なEthernetインターフェイスに見える。EthernetフレームがUSBバス上に見えるようになるので、DHCPを行ってIPアドレスを取得して通信を進めることになる。Wimax特有の部分は全部UD01SS側でやってくれるので、ホスト側でやることはほとんどない。この辺はデバイスドライバを書いてみようと思うときには非常に都合がよいところだ。

なんか嬉しくない話

が、いい話ばかりではなかった。
UD01SSでは証明書やユーザIDなどはNVRAM領域に記録されている。が、読んでもすぐにはわからない形式でひねってあり、USBバス上でもその状態でデータが交換されるため、専用のユーティリティアプリケーションでなければ内容を確認することはできない。
つまり
  • NVRAM領域の読み方がわからなければ、正規に提供されているOS以外でのUQ Wimaxネットワークの(マットウな方法での)利用は絶望的
である。それはあんまり嬉しくない。
というわけで、そいつをどうやってやっつけようか?という話になるのだが、しばらくがんばってみたところなんとなく復元する方法を作り出すことに成功した。

次回は、その辺の解析の話につながる部分を書いてみる予定。

UQ WimaxのUD01SS解析 (2)

前回の続き。
UQのUSB型mobile wimaxインターフェイスであるUD01SSの解析メモ。

UD01SSのファームウェア(Linux)を覗く

UD01SSは前回も書いたようにLinux(正確にはuclinux)をファームウェアとして利用している。普通に使っていると「なんか認識まで時間がかかるなあ」と思うが、これも裏でlinuxがブートして頑張っていると思うとちょっとは許せてしまうかもしれない。

今回のエントリでは、FirmwareUpdateとして配布されているUD01SSの内部のファイルシステムを覗いてみる方法を紹介する。

Windowsの場合はProgram Files\UQ\UD01SS\FirmwareUpdate\というディレクトリの下にzImageとramdisk.jffs2が配置されている(OSXの場合はまた別の場所。適当に探してほしい)。zImageが圧縮されたlinuxカーネル本体で、ramdisk.jffs2がUD01SSのフラッシュメモリに書き込まれているルートファイルシステム以下のユーザランドファイルシステムだ。kernelの解析はまたにしておいて、わかりやすいramdisk.jffs2を見てみることにする。

jffs2はlinux(とくに組み込み系)で利用されているファイルシステム形式でフラッシメモリに対する書き込みイメージとして利用される。これを普通のPCで動いているLinuxでマウントして見るためには、以下のような手続きが必要だ。ここでは手元のlinuxはi386系で動いていることを仮定しておく。

問題はUD01SSがARMでファイルシステムのそれに合わせて構成されていることだ。エンディアンを変換してあげないとi386のPCではマウントできない。jffs2のイメージの操作にはmtd-toolsというユーティリティを使えばよい。適当に手順を書くと、


  1. linuxホストにramdisk.jffs2を持ってくる

  2. apt-get instlal mtd-toolsなどでmtd-toolsをインストールする

  3. インストールしたツールでエンディアンをリトルエンディアンに変換する。
    • jffs2dump -v -b -e ramdisk-le.jffs2 ramdisk.jffs2

  4. 必要なカーネルモジュールをロードする。
    • sudo modprobe jffs2
    • sudo modprobe mtdblock

  5. ループバックファイルシステムとしてマウントする。
    • sudo losetup /dev/loop0 ramdisk-le.jffs2

    • sudo modprobe block2mtd block2mtd=/dev/loop0,16384

    • sudo mount -t jffs2 -o ro /dev/mtdblock0 /mnt
これで/mntの下にUD01SSのファイルシステムが見えるようになる。

UD01SSのファイルシステム概略

細かいことまで見たい人はぜひ自分で覗いてみてほしいが、ざっくりとしたところをメモとして書いておく。
  • ユーザーはrootだけ(パスワードはちゃんと付いている)
  • ユーザランドバイナリはbusybox化されている。shもある。
  • メインアプリケーションは/usr/wimaxコマンドらしい。たぶんこれがもう一つのOSで動いているタスク群と通信している。


UD01SSのカーネルイメージ

本当はzImageもばらしたほうがいいが、だいぶめんどさいのでgzipで圧縮された場所を引っ張り出してstringsしたくらいで満足してしまった。だれか挑戦した人がいたら詳細を教えてくれるとうれしいかも。

次回のエントリではWimaxインターフェイスとしての基本動作を書いてみる予定。

2009年5月4日月曜日

UQ WimaxのUD01SS解析 (1)

すこし前だがUQ wimaxが関東圏で商業サービス開始を目前に利用モニターを募集していた。あいにく機材を含めた無料モニターには当たらなかったが、ちょっと興味があったので自前でwimaxインターフェイスを購入してしばらく遊んでみた。購入したのはUD01SSというUSBドングル型のインターフェイス。というわけで、その結果を備忘録としてまとめておこうと思う。

UD01SSの概略
UD01SSは韓国のMODACOM社の製品(日本ではシンセイコーポレーションがOEMで販売)で、韓国ではWibro用として販売されているもの(たぶんMW-U2500と一緒なんではないかと推測)だ。WimaxフォーラムのWave2に適応しているのでUQが提供するネットワークと互換性がある。中身はGCTセミコンダクタが開発した1チップのWimax SoCであるGDM7205が使われている。GDM7205はCPUとWimax Mac, PHYが集積されており1チップで必要な機能を全部提供できる、とのこと。

と、これくらいがWebで調べてみてわかることなんだが、あまり面白くない。わざわざ購入してまで調べてみようと思ったのはもう少し面白い特徴があったからだ。UD01SSで面白いと思ったのは、

  • GDM7205はARM926EJと補助CPU(アーキテクチャ不明)が搭載されている
  • UD01SSの内部OSはLinuxと補助CPUで動くリアルタイムOS(詳細不明)で動いている
  • ソフトウェアでUSBクライアントとして動いているのでUSBデバイスとしての構造は比較的簡単
といったあたりだ。とくに、「中身がLinuxで動いているUSBドングルタイプのARMのボード」が12800円っていうだけでなんかほしくなってしまったのでつい買ってしまった、というのが本音かもしれない。

UD01SSの動作モード

最近よくあるUSBのHSDPAモデムなどと同様に、UD01SSもデバイスドライバが存在しないOSでは自身をUSBマスストレージデバイス(CDROM)に見せかけるようになっている。専用のデバイスドライバがインストールされたあとには特別なコマンドを発行してデバイスのモードをWimaxインターフェイスに切り替える。
最初はUSBのVendor ID:0x1076, Product ID:0x7f40のデバイスとして見えているが、モードが変換されると、Vendor ID:0x1eb8, Product ID:0x1240に化ける。
モード変更コマンドは、右の図のようなUSBパケットを生成して送り込めばよい。デバイスがリセットされて新しいVID/PIDをもったデバイスとして再度バスに出現する。

UD01SSのLinuxファイルシステム

内部がLinuxということは別に隠されているわけではない(宣伝しているわけでもないようだけど)。UD01SSのアプリケーションがインストールされると、Windowsの場合はProgram Files\UQ\UD01SS\FirmwareUpdateというディレクトリが作成される。その中身は
  • ramdisk.jffs2
  • zImage
になっていて中身のカーネルとファイルシステムを覗くことができる。

次のエントリーでこれらを覗いてみる方法を書いてみる予定。

2009年4月26日日曜日

E-mobile D21LCをOpenBSDで使う

少し前の話だが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でもモデムとして利用できるはずだ。

シリアルポート


D21LCではシリアルポートが3本生成される。モデムポートは3本目(openbsdの場合はttyU2)だった。

OpenBSDのデバイスドライバの拡張

実はすでにOpenBSDのumsm(4): 3Gモデム用デバイスドライバでは、このようなUSBマスストレージ型のモード変更デバイスを扱うための機能をすでに実装してある( umsm_umass_changemode() @umsm.c)。 似たようなデバイスを扱うために実装しておいたものだが、今回はこの関数を少し拡張して利用することにした。
すでにOpenBSDのCVSへマージされている(差分)を参照すればわかるが、上でキャプチャしたコマンドをタイミングよく発行するだけだ。
コマンド発行部分の差分を以下に載せておく。いままで2種類の同様なコマンドがあったので、3種類目として拡張しておいた。










おまけ:D21LC雑感

実際に使ったのは短い期間だったが、D21LCを使って気がついたのは、「デバイスがサポートしていないコマンドを受理するとフリーズすることがある」ということだ。正式に使えるコマンドセットが公開されているわけでもないのでバグというのは言いすぎだが、ちょっとした動作確認でハングアップしてしまうことが多くて困ることが多かった。最初はデバイスドライバ側が悪いのかと思ったが、Windowsでも同じ操作で同じ状態に陥ることが確認されたのでデバイス側に問題があるんだと思っている。

2009年2月20日金曜日

USB-BLINKM

BLINKMはthingmが作成・販売しているインテリジェントフルカラーLEDだ。1cm角くらいの基板に、明るいRGB LEDとAVR Tiny45が載っていて、I2Cインターフェイスで外部に接続できる。I2Cはデイジーチェーンできるので、たくさんLEDを使うときには大変便利だ。Arduinoなどから簡単に扱えるので様々なメディア作品で利用されている。


先週からいきなり始めてみた「AVRを使ってみる」という活動だが、ふと机の横に転がっていたBLINKMを眺めるとAVR Tiny45が目についた。ちょうどAVR Tiny85で遊んでいたところだった(その話はまた今度)ので、「Blinkmを単純にLED付きのAVRとして使えるんじゃないか?」と思い立った。実際にやってみたのが今回のエントリの「USBに直接つながるBLINKM」だ。i2cのデイジーチェーンできるという利点は無くなってしまうけれど、USBならどんなPCにも大体ついているので、一個だけ使う場合には逆にお手軽になる(と期待している)。今回は、ハードウェア部分について書いてみることにする。








BLINKMの回路図

BLINKMを裏返して眺めてみると、実装されている部品はLEDと電流制限用の抵抗(x3)とAVR TINY45とデカップリング用のコンデンサくらいだ。とりあえずI2Cの接続用にでている4pinがどこにつながっているかと、LEDがどのIOにつながっているかがわかれば良いとおもって回路を追ってみた。というわけでわかったのが左の図に示す回路図。大変都合の良いことに、i2cのdata/clockの線がPB0/PB2につながっている。

あと実際のBLINKMには実装されていないけれど、ISCPに必要な信号線を出すためのパターンも実装されていて、reset/misoは簡単に外部に引き出せる。
AVRでソフトウェア的にUSBを実現するコードとしてobject development社が公開しているAVRUSBがあるが、USBの信号線の一本をINT0に接続することが推奨されている。PB0/PB2が出ているのがどうして都合が良いかというと、PB2はINT0だからだ。ちょうどTINY85でAVRUSBを試していたところだったので、すぐに「ほとんど外付け回路無しでUSB化できそうだ」という予想ができた。


BLINKM-USBブリッジ回路

というわけで、BLINKMの4pinコネクタからUSBのAコネクタに変換するための外部回路を書いてみる。基本的にAVRUSBのリファレンス回路のツェナーダイオードを使うやつと同じ。
USB1.1の仕様ではD-信号線を1.5Kオームでプルアップすると、low speed (1.5Mbps)デバイスだと認識する。そのためのプルアップ抵抗が一本、信号線の電圧をクリッピングするためのツェナーが2本、あと信号線のシリーズ抵抗(68オーム)が2本、だけの簡単な回路になる。実際にはVccラインに保護用に500mA以下(LEDだけだったら300mA程度で十分)のポリヒューズを一本と、0.1uF程度のセラミックコンデンサを実装しておけば良い。
ポリヒューズが無ければ直結するという方法もあるが、何か間違ったときにPC側のUSBポートやUSBホストコントローラを焼いちゃうかもしれないのでおすすめしない。デカップリング用コンデンサはなくてもいいから、過電流の保護はちゃんとやっておこう。

BLINKM-USBブリッジの実装

思い立ってから、最初の実験を含めて3つほど実装したけど、一番小さくなったやつを紹介しておく。USBもBLINKMも両方とも4pinのコネクタだったので、秋月の16穴スルーホール基板をつかって実装したくなった。10枚買えば一枚10円、100枚だと一枚5円というお手頃価格だし、基板を加工する手間も省ける。

右の図が実体配線図。部品面から見た図になっている。斜め配線&一つの穴に2本部品がはいっているというお行儀の悪い実装になっているけれどなんとか収まったので良しとしよう。


部品はだいたい秋葉原で揃う。3.6Vのツェナーダイオードは千石電商で100本入りをかった。これはどこでも売っているはず。68オームと2Kオームのチップ抵抗(1608)は鈴商で売っている。チップ抵抗なら千石でもあるはずなんだけど、68オームは置いていなかった。一番面倒なのが面実装のポリスイッチ(ポリヒューズ)だ。容量の小さいものなら秋月で扱っているらしいけど0.1Aは小さすぎる。探してもなかったのでチップワンストップで、Tyco Electronics Raychem PSR-27313-050を調達して使った。まあ、千石のB1で売っているポリスイッチを裏にがんばって貼り付けても大丈夫なのでそこまでしなくても平気だとはおもう。

実際に制作したものは右の写真のとおり。BLINKMはUSBコネクタ側にLEDが向くように差し込む。USBコネクタを差し込むと、ちょっと余裕が出るくらいの長さになるので、これがこの方法でつなげる場合に一番短くなる実装方法だろう。





USB-BLINKMのソフトウェア

もちろん、今回つくった回路で電気的にUSBコネクタにさせるようになったとしても、AVRのソフトウェアを書かないと動かない。I2C-USBブリッジではなく、BLINKMにのっているTINY45に直接USBを処理してもらわないといけないからだ。

次回のエントリでは、AVRUSBについて説明してみる予定。


2008年5月13日火曜日

OpenBSDでEmobile D02HWを使う(1)

縁あって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のモード遷移のための関数の統合について書く予定。

2008年2月17日日曜日

(メモ)DUS01まとめ

忘れる前にワンセグチューナーDUS-01の挙動についてメモしておく。半分は自分のため、あとの半分は「24時間ワンセグコミュニティ(?)」のため。Linuxやwindowsで使うときにどうぞ。

ハードウェア

  • CPU: Silicon Laboratories C8051F342
  • チューナー: sharp VA1A5JZ9902A (参考リンク:ascii24)
  • アンテナ:ロッドアンテナ
  • インターフェイス: USB1.1
シャープの9902Aは1-2世代前のワンセグモジュール。最新版には及ばないが感度が-107dbmは優秀なんじゃないかと思う。googleで探したけど仕様書等は見つからなかった。残念。

USBデバイス情報
USBデバイスとしては、こんな感じ。ほかに何をかけばいいのかな。
  • Vendor ID:0x1bc8 , Product ID:0x0001
  • endpointは0,1,2,3の4本。
  • EP1は制御用のバルク転送エンドポイント(読み書き可能)。
  • EP3はMPEG2TS転送用のアイソクロナス転送エンドポイント(読み込み専用)
  • EP2は使っているのを見たことないけど、ファームウェアアップデートなどに使うのじゃないかと推測
  • 起動時にファームウェアロードなどは発生しない。電源を入れれば動く。

さらに詳しく知りたい人のためにNetBSDのusbgenの出力を載せておく。
CONFIGURATION descriptor index 0:
bLength=9 bDescriptorType=2 wTotalLength=46 bNumInterface=1
bConfigurationValue=1 iConfiguration=0 bmAttributes=80 bMaxPower=150 mA

INTERFACE descriptor index 0, alt index 0:
bLength=9 bDescriptorType=4 bInterfaceNumber=0 bAlternateSetting=0
bNumEndpoints=4 bInterfaceClass=0 bInterfaceSubClass=0
bInterfaceProtocol=0 iInterface=0

ENDPOINT descriptor index 0:
bLength=7 bDescriptorType=5 bEndpointAddress=1-out
bmAttributes=2 wMaxPacketSize=64 bInterval=5

ENDPOINT descriptor index 1:
bLength=7 bDescriptorType=5 bEndpointAddress=1-in
bmAttributes=2 wMaxPacketSize=64 bInterval=5

ENDPOINT descriptor index 2:
bLength=7 bDescriptorType=5 bEndpointAddress=2-out
bmAttributes=2 wMaxPacketSize=64 bInterval=5

ENDPOINT descriptor index 3:
bLength=7 bDescriptorType=5 bEndpointAddress=3-in
bmAttributes=1 wMaxPacketSize=188 bInterval=1


制御コマンド
制御コマンドはEP1をつかってやりとりする。今のところ分かっているコマンドはこれだけ。全部推測なので正しいかはわからないけど。
  • ファームウェアバージョンチェック (0x9f) - 現在のファームウェアのバージョン(1.0.0)が返ってくる。
  • チャネル設定 (0x10) - これに加えてKHz単位の周波数をくべるとチューナーが設定される。
  • 入力検出(0x11) - 電波が受信できていたら0x1、受信できなければ0x0が返ってくる。
  • ストリームモードチェック(0x12) - たぶんMPEG2TSストリームを出しているかしないかをチェック。
  • ストリームスタート (0x20) - EP3へのMPEG2TS出力を開始する。
  • ストリームストップ(0x21) - EP3へのMPEG2TS出力を停止する。
  • シリアル番号チェック(0x43) - デバイス固有のシリアル番号を返す。
(2008/2/18 1:30修正:miroさんありがとうございました。)

それぞれのコマンドは10byte長で、先頭にコマンドを、引数がないものは0x0で埋めて送る。
チャネルを変えるときのシーケンスは
  • ストリームが出ていたら止める
  • 止まったかモードをチェックする
  • チャネル変更リクエストを出す
  • ちょっと待つ
  • 入力検出リクエストを出す
  • 返り値をチェックして0x0だったら「ちょっと待つまで戻る」。0x1が出るまでがんばる。(もしくはタイムアウトする)
  • ストリームを出力する
って感じ。チャネル設定コマンドは、{0x10, 0x04, 0x00, A, B, C, 0x00, 0x00, 0x00, 0x00} というフォーマットで、A,B,CにKHz単位の周波数を指定する。たとえば、521.43MHz(チャンネル21)なら{0x07, 0xf3, 0xb7}を埋め込めば良い。
(2008/2/19修正:チャネル設定コマンドの例の先頭バイトをtypoしていたので修正)

出力ストリーム
EP3からはMPEG2TSもどきが出力される。先頭はMPEG2TSで規定された同期ビット列(0x47)でかつ、パケット長が188Byteというのは良いのだが、MPEG2TSとして解釈すると全部エラーになる、という変な状態。きっと、プログラムミスでこうなってしまっているのだろうとおもう。あまりにも不憫なので正しいストリームに治してあげよう。
「先頭の0x47を除いて、他のバイト列に対して0xb5を排他的論理和を取る。」
すると、普通のMPEG2TSになる。

まとめ
USB的には、「USBデバイスとしてのJ200への不満」、はほぼ解決されているようなデバイスだった。最初はストリームが見えるか少しどきどきしたが、まあすぐにわかってよかった。
まだ入手性もよいみたいだし、ここにある情報を使えば他にも対応ソフトを作れそうな人がいると思うので、いけているデバイスじゃないかと思う。明日もう少し補充を(自分用に)買っておこう。。

2008年2月7日木曜日

NetBSDでSONY GPS-CS1Kを使う

SONYはGPS-CS1KというGPSロガーを発売している。ずいぶん前(発売直後)に買ってあったのだけど、実はあまり使わずに棚の上のオブジェと化していた。一緒にblogをかいているCUEICHIがGoogle Mapと位置情報で遊んでいたのをみてGPSを持っていることを思い出しGPSを使おうと盛り上がった。
が、なぜかNetBSDでは動かない(OSXでも動かない。。)ので原因を調べてみた。
kerenlからのメッセージと、USBバスアナライザの出力を見る限り

  • UR_BBB_GET_MAX_LUNリクエスト受け取ったあとデバイスの挙動がどうも怪しい
  • しばらくリトライすると復帰する
ようだ。リトライのコードを入れてもいいのだが、他のデバイスへの影響が大きそう。あまり美しくはないがNetBSDのumassの例外コード(umass_quirks)を拡張してUR_BBB_GET_MAX_LUNを発行せずに決め打ちでLUNを指定する、というロジックを導入してみることにした。

その結果、sd0 on umass0として認識されるようになって、mount -t msdos /dev/sd0a /mntでディスクとしてマウントできるようになった。とりあえずpatchはsend-prしてある(kern/37948)。

NetBSDで動くようにはなったが、GPSロガーをどんなふうに使うかはまだ思案中。

こんなデバイスをみるたびに、「Windowsで動けばいい、みたいなデバイスの作り方はしないでほしいなー」と思う。で、同時に「Windowsのホストドライバってなにかあってもあきらめずに頑張るなー」とこっそり感心する。ここは純粋に評価してもいいね。

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したファイルディスクリプタの始末や、書き込むメッセージのサニタライズは最初からそれなりにお行儀よくしておいたほうがいい。

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

2008年1月3日木曜日

NetBSDでEmobile D02HWを使う(スループット改善)(1)

このエントリーの一行まとめ:HTTP/TCPで29KB/sしかでなかったけど440KB/s(3.4Mbps)まで改善した。うれしい。


最近のエントリーに結構話題にしたように、いろいろとごにょごにょとして、イーモバイルのD02HW(E220)をNetBSDでも普通に使えるようになった。使えないデバイスが使えるようにする作業をしていると、「使えるようになった!」時点でだいぶ達成感がでてしまってそこでゴールに着いてしまった気になりがちだ。今回もデバイスとして認識できない段階から、まがりなりにもPPPでセッションを張って通信できるようになったわけだから、まあそれでいいや、考えてしまっていた。
ちょっと間をおいて、実際に使ってみようとおもうと、どうも変な感じがする。windowsのNote PCにつなげて使っているときはすぐに見えるwebサイトなどが見えるのがだいぶ遅い。やっぱりスループットを計測してみるか、と重い腰を上げてやってみるた。
wgetコマンドをつかってHTTP/TCPで、ファイルを転送するときの速度を測ってみると………。(プロトコルオーバヘッドがあるので下部層ではあと10%くらい速いはず)。
  • Windows  (Panasonic Let's Note R5): 1.8Mbps-2.5MBps (230KB/s-320KB/s)くらい
  • NetBSD (ALIX 3C): コンスタントに0.225Mbps (29KB/s)
うーん、軽く1/10? と少しショックを感じながら改善作業に取り組むことにした。

2つの計測系で同一なのは「同じD02HWを使っている」「同じ場所で計測」「ほとんど同じ時間に計測」というところ。あとの計算機環境やソフトウェアは全部違っている。本当はできるだけそろえるべきなんだけど、Let's noteにNetBSDを入れるのもめんどくさいので実作業にうつることにした。とりあえず疑うべきは
  • D02HWの設定
  • D02HW用のデバイスドライバ
  • OSのUSBサブシステム
  • PPPの設定
  • PCの性能
あたり。ターゲットとしているALIX3は小さな組み込みボードといえども、500MHz駆動のAMD LX800で動いている。主記憶も256MBある。経験上これくらいの性能でたかだか数Mbpsのトラフィックを裁けないわけがない。USBホストのチップの問題もなさそう。というわけで、5つ目の「PCの性能」は「ほとんどなさそう」だ。

簡単なところが疑っていこうとおもって、次は「D02HW」の設定を追ってみることにした。といってもモデムとして見えるATコマンドでは無くUSBレベルでの話。USBバスアナライザでトランザクションを眺めていると、ベンダ独自のコマンドを使って設定用のレジスタをさわっているようにみえる。仕様書が無いのでこれが何を意味しているかはわからないが、設定しているということは意味があるのかもしれない?とおもってNetBSDからも同じ操作をする関数を実装してみた(どんな風にするかは後日別のエントリーで書く予定)。うーん?別に変わらない様子。

次は「デバイスドライバ」と「OSのUSBサブシステム」。一緒にみた方が良さそうなので同時に始末することにする。たぶん今回の問題の肝は「いつも同じ29KB/sである」ということだろう。windowsで見ていると、結構短い間隔でスループットが大きく変動しているのを観測できるが、NetBSDだとずっときっちりと29KB/sしかでない。つまりある層までは十分な性能があるのにかかわらずどこかに律速(ボトルネック)となる部分があってそれが原因でこんな状況になっている、と推測できる。こんなときにどこをさわればいいか?というのは、そのデータがデバイスからOSの上(tty)までどうやって流れてくるのかを理解して調べていかないといけない。幸いにも今回はD02HW対応でしばらくUSBのデバイスドライバをさわっていたので、ずいぶん勘がはたらくようになっていた。じーっとコードを眺めていると、USBのデータ処理単位とデバイスの単一のパケットサイズを同じ大きさ扱うようになっていた。たとえばUSB_ATTACH()のなかのエンドポイントディスクリプタを読んで上位のucomデバイスの送受信バッファサイズを設定するところ。


if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN &&
UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
uca.bulkin = ed->bEndpointAddress;
uca.ibufsize = UGETW(ed->wMaxPacketSize);
} else if (UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_OUT &&
UE_GET_XFERTYPE(ed->bmAttributes) == UE_BULK) {
uca.bulkout = ed->bEndpointAddress;
uca.obufsize = UGETW(ed->wMaxPacketSize);
}

単なるシリアルラインならともなく、D02HWは7.2Mbpsの高速シリアルデバイスなのでこれじゃだめだろうという気がする。というわけでその辺のコードをさわる。具体的には、uca(ucom attach arg構造体)のuca.ibufsize / uca.obufsizeをD02HWのパケットサイズ(64Byte)とは独立した大きさにする。試行錯誤の結果、4096Byteくらいがちょうど良い大きさだと判断した。uca構造体を設定する場所で決め打ちで4096を設定しておく。ucomとか関連する関数をチェックしたがそれで問題は無い。

ここまでの作業で、29KB/sec -> 12oKB/secくらいに劇的に改善した。1Mbpsくらいでている計算になる。でもwindowsの1/3くらいでしかないので更に解析と作業を進める。

(続く)

2007年12月26日水曜日

USBアナライザとD02HW

「NetBSDでD02HWをつかう」では比較的(というかほぼ全部)試行錯誤でデバイスの設定に必要な条件を推測してデバイスドライバへのパッチを作成した。ハードウェアを触るときによく感じることは「見えないものはわからない」ということだ。当たり前のことだが、電気信号は眼にはみえないので、推測するしかなくそれはいつまでも推測のままで推移してしまう。電圧を測るのにテスターが必要で、時間ドメインの波形をみるのにオシロスコープが必要なように、扱う事象を見るための専用の「アナライザ」が必要になることは多い。実はとある目的のためにUSBバスアナライザが手元にあったので、D02HWのデバイスドライバ作成作業を検証してみることにした。

手元にあるのはellisysUSB Explorer 200だ。これは、USB2.0のHighSpeed(480Mbps)に対応したアナライザで、Windowsソフトウェアと組み合わせることでだいたいすべてのUSBイベントを表示してくれる、という便利な逸品である。ソフトウェアは誰でもダウンロードできるようになっていて、USBバスのキャプチャファイルを入手したときに誰でも表示できるのがうれしい(ちなみにキャプチャファイルの拡張子はUFOだ)。
写真でわかるように、アナライザを解析ソフトが入っているPCにつなげて、デバイスとデバイスをつなげるホストの間にはさむようにアナライザを配置する。これで、デバイスとホスト間のデータをインターセプトするという仕組みになっている。
まず、Windowsで動かしてみたときにUSBバスにどんなトランザクションが発生しているかを見てみた。
NetBSDでEmobile D02HWを使う(3)」にあるUSBのインターフェイスとエンドポイントの情報を見ながら、どのエンドポイントとホストがどんな通信をしているかを眺めてみる。アナライザは、

たとえばこんな情報が表示されて、トランザクションのタイプと中身のデコード結果をだしてくれるので、それとUSB2.0の仕様書(このへんにある)を眺めながら追っていく。赤裸々にどんなトランザクションをやりとりしているかを見ていてわかったことは以下の3つ。

  • NetBSDにつくったパッチは正しい。
  • ATコマンドいろいろ
  • D02HWは実はさらに別のインターフェイスをもっている。
最初のは、結局あのシーケンスを送らないとデバイスは言うことをきいてくれないみたいだ、ということを追認できたということだ。ちゃんとUSB2.0の仕様書を読み込むと、あのメッセージは
  • デバイスへのスタンダードのリクエスト(SET_FEATURE)でHost→Device
  • リクエストタイプはDEVICE_REMOTE_WAKEUP
  • wIndexの0x2はendpoint2を意味する
ということだった。アナライザでの結果をみるとendpoint2はモデムの主通信用のパイプなので、これで意味がわかってすっきりした。
2つめのATコマンドいろいろはたぶんWindowsとかのモデム定義ファイルをみればわかる情報なので大した追加情報ではない。問題は3つめで、確かに「NetBSDでEmobile D02HWを使う(3)」では、モデムとして認識されるようになると3つのインターフェイスディスクリプタが見えている。ひとつはモデムでひとつはUSBストレージデバイスだ、ということもわかっていたが、実はなぜもうひとつみえるのか、は謎のまま残っていた。まだ実際にコードを書いて叩いてみたわけではないのだが、USBのBUSトランザクションを見る限り、

INTERFACE descriptor 1:
bLength=9 bDescriptorType=interface(4) bInterfaceNumber=1 bAlternateSetting=0
bNumEndpoints=2 bInterfaceClass=255 bInterfaceSubClass=255
bInterfaceProtocol=255 iInterface=3(Data Interface)

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=5-in
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

ENDPOINT descriptor:
bLength=7 bDescriptorType=endpoint(5) bEndpointAddress=5-out
bmAttributes=bulk wMaxPacketSize=64 bInterval=0

のendpoint5はもう一本のモデム制御用のパイプらしい。まだ憶測だが、endpoint2でデータ通信しながらでもモデムが管理しているHSDPA由来の制御情報などを並列してとれるのではないかと予想している。これを扱うデバイスドライバかくのはどうしたもんか、と悩みがふえてしまった(しばらくはきっとやらないけどね)。

(12/28追記:実験してみたらモデムモードに入った後は3本ともシリアルらしい。2本目をはやすハックをしてみた、あとで詳しく書く予定)
(12/30追記:やっぱり3本目はusb mass strageだとおもうなあ。コードかいてみるか。。)
(1/4追記:usb mass strageでした。)

というわけで、アナライザを使うとみたいことも見えるけど、まったく気が付いていなかったようなことも見えてしまう、ってことで。今回はおしまい。