Mac用USBデバイス-68 実装とデバグ [Mac用USBデバイス工作]
この土日で一段落させたいIIDCカメラドライバ。設計段階のヘッダを考えているうちは、極端な話、外を歩きながらでもできる(はたから見ると変なジジイが気も漫ろで歩いているようにしか見えないけど)が、具体的な実装やデバグはエディタに向かって集中しないとできない。特に今回のようなハードウェアに近い抽象性の低いコーディングはジジイの散漫な集中力では、すぐ限界が来る。
しばらく全然進んでいなかったのはデバグが遅々として進まなかったため。その昔、古いMacOSでQuickTimeのSequenceGrabberを使ってカメラからの読み込みですごく苦労したのを思い出した。
カメラのレジスタの読み書きは面倒でつらかったけど(結局フィーチャなんかはIIDC仕様書から引き写した文字列の表を作って、それとは別にフィーチャごとの関数のプロトタイプ宣言や中身を出力するちいさなCのプログラムを書いて自動生成したが、コピペするのと手間はたいして変わらなかった。しかもあとから書き換えることになった。もう二度とやりたくない)、比較的簡単に動いた。
一方アイソクロナス転送のほうは、サンプルコードを修正している間は全然問題なく動いていたのに、Cocoa/Objective-Cとインターフェイスをとるためにまとめ直して書き下したものが全然動かない。
サンプルコードは手順がわかるように順番に書かれているけど、Cocoaアプリの下請けにしようとするとパーツに分解して整理する必要がある。
またサンプルコードはアイソクロナス転送を設定したらそのまま実行ループを自分で起動しているが、実際のアプリでは実行ループはCocoaアプリ本体のを使わなければならない。そしてDCLのコールバックは独立したスレッドに置け、という指示に素直に従うとシングルスレッドのサンプルコードをもとに改変する必要がある。
とりあえず、普通にCocoa/Objective-Cでやるようにすなおに実行ループをまわす直前でスレッドを生成してそこで転送をスタートして実行ループをまわすコードを書いた。微妙にサンプルコードと違うコードを書いたら、それが全く動かない。
原因不明の問題がいくつかあった。
これもなんでなのかわからないけど、DCLの組み方に大きな影響が出た。制限が多い。単なるループのDCLしか組めない。
このチャンネル番号はRemoteNodeで、転送したいチャンネル番号に対応するビットを立てることで設定することになっている。同じ値をカメラにも伝えてその番号をヘッダにつけさせる。
ところが、これもうまくいかなかった。全ビットを立てないとアイソクロナスデータを受けてくれない。よくわからない。
このため、全部セットアップしてからスレッドを生成してチャンネルをスタートして実行ループをまわすというやり方ができず、まずスレッドを作りそこでまずAddCallbackDispatcherToRunLoop()を呼んでからポートやチャンネルを作るというふうにしないといけない。そうするとスレッドをデタッチした時点ではチャンネルオブジェクトなどがアクセスできず、メインスレッドから転送開始を指示したい場合、アイソクロナス転送に携わるオブジェクトが別スレッドで作られるのを待って、つまりなんらかのスレッド同期をする必要がある。
しんどい。
実装、あるいはその苦労
とりあえず先週設計したヘッダに従ってさくっと実装を始めたが、さーてデバグがまた大変だった。おやぶん、てーへんだ、てーへんだあ。ボケにもならんわ。デバグは問題が出ると面倒なのでiMacではなく、G4 PowerBookでやった。従って動作確認はPowerPCの10.5.8のみで行っている。しばらく全然進んでいなかったのはデバグが遅々として進まなかったため。その昔、古いMacOSでQuickTimeのSequenceGrabberを使ってカメラからの読み込みですごく苦労したのを思い出した。
カメラのレジスタの読み書きは面倒でつらかったけど(結局フィーチャなんかはIIDC仕様書から引き写した文字列の表を作って、それとは別にフィーチャごとの関数のプロトタイプ宣言や中身を出力するちいさなCのプログラムを書いて自動生成したが、コピペするのと手間はたいして変わらなかった。しかもあとから書き換えることになった。もう二度とやりたくない)、比較的簡単に動いた。
一方アイソクロナス転送のほうは、サンプルコードを修正している間は全然問題なく動いていたのに、Cocoa/Objective-Cとインターフェイスをとるためにまとめ直して書き下したものが全然動かない。
サンプルコードは手順がわかるように順番に書かれているけど、Cocoaアプリの下請けにしようとするとパーツに分解して整理する必要がある。
またサンプルコードはアイソクロナス転送を設定したらそのまま実行ループを自分で起動しているが、実際のアプリでは実行ループはCocoaアプリ本体のを使わなければならない。そしてDCLのコールバックは独立したスレッドに置け、という指示に素直に従うとシングルスレッドのサンプルコードをもとに改変する必要がある。
とりあえず、普通にCocoa/Objective-Cでやるようにすなおに実行ループをまわす直前でスレッドを生成してそこで転送をスタートして実行ループをまわすコードを書いた。微妙にサンプルコードと違うコードを書いたら、それが全く動かない。
原因不明の問題がいくつかあった。
ReceivePacketStartに提供するバッファサイズ
DCLコマンドのReceivePacketStart(AllocateReceivePacketStartDCLで作る)にはバッファを渡す。そのバッファにDMA転送されるのでページ境界をまたがないためにvm_allocate()で作った領域を渡す。ところがこれがなぜか余分なサイズがないとlocalIsochPortを作るときに失敗する。かなり大きな(必要となる領域の倍)領域を確保しないとCreateLocalIsochPort()がNULLを返す。サンプルコードではその必要はなく、どこが問題なのかわからない。サイズ0のバッファ
DCLではUpdateDCLListコマンドやCallProcコマンドは勝手な位置に置くことはできず、必ずReceivePacketStart(あるいはSendPacketStart)コマンドのすぐ後にしか配置できない。テックノートではそのためにサイズ0のデータを受けるようなコマンドを一つダミーに置いてそのあとに書けとあるが、そうしてDCLを組んでCreateLocalIsochPort()に渡すとやっぱりNULLが返ってくる。これはサンプルコードでも同じ。これもなんでなのかわからないけど、DCLの組み方に大きな影響が出た。制限が多い。単なるループのDCLしか組めない。
アイソクロナスチャンネル番号
アイソクロナスチャンネル番号というのが定義されていて、アイソクロナス転送の送り元を特定するために使われる。ほんとはアドレスでいいんだけどFireWireアドレスは長くてパケットごとにつけていたら無駄が多いということで、4ビットあるいは6ビットの数字をヘッダに埋めてその値を送り元ごとに変えて識別に使う。重複しないようにするのはアイソクロナス転送を行う側の責任になっている。このチャンネル番号はRemoteNodeで、転送したいチャンネル番号に対応するビットを立てることで設定することになっている。同じ値をカメラにも伝えてその番号をヘッダにつけさせる。
ところが、これもうまくいかなかった。全ビットを立てないとアイソクロナスデータを受けてくれない。よくわからない。
AddCallbackDispatcherToRunLoopの位置
実行ループにDCLのコールバックを組み込むための関数であるAddCallbackDispatcherToRunLoop()は、もしコールバックを別スレッドに置きたければ当然そのスレッドの上で呼ばなければならない。ところがこの関数はポートやチャンネルを作る前に呼ばないといけないらしい。そうでないとコールバックが呼ばれないということが起こった。これにはまいった。まさかAddCallbackDispatcherToRunLoop()に呼び出し順があるとは思ってなかったので、なぜコールバックが呼ばれないのか全然わからなかった。かなり長時間悩んだ。このため、全部セットアップしてからスレッドを生成してチャンネルをスタートして実行ループをまわすというやり方ができず、まずスレッドを作りそこでまずAddCallbackDispatcherToRunLoop()を呼んでからポートやチャンネルを作るというふうにしないといけない。そうするとスレッドをデタッチした時点ではチャンネルオブジェクトなどがアクセスできず、メインスレッドから転送開始を指示したい場合、アイソクロナス転送に携わるオブジェクトが別スレッドで作られるのを待って、つまりなんらかのスレッド同期をする必要がある。
SDKのAppleFireWireGDB.kextなどが動作しない
SDKにはデバグ用のkextなどがいろいろ含まれている。ためしにAppleFireWireGDB.kextをインストールしたらIORegistryExplorerさえ動かなくなった。最初はIOFireWire surfaceが表示されず、終了してもう一度立ち上げたらOSを道連れにクラッシュした。kextを削除してExtensions.mkextなんかのキャッシュを消して回っても元に戻らず、結局OSをインストールし直すことになった。これもなぜかわからない。いつのまにかDCLのコールバックがすべて動作しなくなった
そうこうしているうちにそれまで動いていたサンプルコードを修正しただけのDCLまでコールバックが呼ばれなくなった。まったくソースをいじっていないのでkernelがどこかおかしくなったとしか考えられない。それに気がつくのにまたずいぶん時間を費やした。キャッシュを消したりリブートしたりで何となく元に戻ってしまった。これも何が問題だったのかよくわからない。2度目の転送が動作しない
カメラのフォーマットとモードを切り替えると少なくともIsochChannelは作り直さないといけない。そこでカメラの設定に変更があったら、ノードやポートを含めて全部(キューやその上のCocoaオブジェクトも含めて)破棄してスレッドも捨てて、もう一度全部作り直すことにした。これのほうがちょっとレスポンスが悪くなるが簡単でいい。ところがいったん捨ててもう一度同じことをしているのに2回目以降は転送されない。詳しく見ていくとどうやらまたDCLのコールバックが呼ばれていない。全く同じことを繰り返しているだけ(場合によってはオブジェクトのアドレスまで同じところにアロケートされている)なのに動かない。200Mbps以上のスピードが出ない
これもよくわからないまま。カメラもMacもサポートできるフレームレートの最大値を設定すると、またDCLのコールバックが呼ばれない。カメラ側に200Mbpsを超えないフレームレートを設定すると動作する。しかも100Mbpsを超えていても200Mbpsを超えなければなぜか動作する。不思議。kIOTerminatedNotificationを設定してもコールバックが呼ばれない
IOServiceAddMatchingNotification()でkIOFirstMatchNotificationではコールバックが呼ばれるのにkIOTerminatedNotificationを設定しても呼ばれない。全く同じコード(マッチング辞書の中身とコールバックのやってる仕事が違うだけ)なのにUSBはちゃんと呼ばれる。これはまったくわからない。しんどい。
2010-03-06 21:20
nice!(0)
コメント(0)
トラックバック(0)
コメント 0