USB162 を動かしてみると、Windows に比べて Linux が圧倒的に遅い。どうもドライバの作り自体に問題がありそうだ。
ちょっと、Linux ドライバのソースを眺めてみた。
linux-2.4 ACM ドライバ
urb というデータ構造を使って、I/O を行っている。その urb は read/write に1つづつアロケートし、バッファのサイズは エンドポイントの パケットサイズ。
要するに、read/write ともに 32バイト単位で I/O を行い、それらの I/O が完了しないと、次の I/O ができない。
USB は、1ms 毎に処理を行うようだ。そのようなデータ構造だと、どうやっても 32KB/sec を超えられないように思える。
では、他のドライバはどうなっているのだろう?
linux-2.6 CDC-ACM ドライバ
Linux-2.6 ではどうなっているのか見てみた。
ACM_NRU,ACM_NWB という define があり、それぞれ 32,2 になっている。
write については、urb は 1つだが、ACM_NWB 数 の バッファを切り替えている。バッファのサイズは、エンドポイントの パケットサイズ。
よくわからないが、バッファが 2 つあるので 高速そうな感じだ。
後記:
バッファを用意して、write が終わったらすぐに次の write を出せるようにしているだけだった。多重に write を出さなければ、高速にはならないようだ。重に write を出すときは、flags にUSB_QUEUE_BULK を指定する必要がある。
read については、urb を ACM_NRB数 生成している。そして、バッファのサイズは、エンドポイントの パケットサイズ x 2 。--- パケットサイズ にあわせなくとも良いらしい。
2.4 カーネルと比べれば 2 倍のバッファサイズで 16倍の多重度ということになる。これなら全然性能が違うのではないか?
linux-2.6 FTDI ドライバ
枯れていると思われる FTDI のドライバはいったいどうなっているのか? ついでに見てみた。
write では、tx_outstanding_urbs という変数があり、その最大値が 42 ! になっている。バッファのサイズは、64 バイト - αのようだ。
read では、urb もバッファも 1 つしかないが、バッファのサイズは 512 バイト。
チューニングしてみる1
readsize を変更しても動かなくなることはなさそうなので、とりあえず readsize を 4倍、16倍にしてみた。

readsize を 4倍の 128バイトにすると 性能が数割あがった。16倍にしても、若干上がるだけでそれ以上の性能向上はないようだ。
...といっても基本となる性能は、Windows の 50kHz のレンジと比べれば 1/10 程度。
では、write もサイズを大きくしてみることができるのか? 他のドライバは、そういうコードになっていないので、無理という気がするが、まぁやってみることにする。

結果は、read と良く似たかんじ。4 倍にするだけでずいぶんと性能があがる。16 倍にしたからといって、大幅に性能があがるわけではない。
... それは良いのだが、16 倍は実はおかしい。1 回測定すると 2回目が動かない。抜き差しして初期化すると 1回の測定が通った。やはりなにかあるらしい。
さて、オリジナルの性能は、最高で 4KHz 程度。1 回に 4バイトなので、1ms 毎の平均パケットサイズは、16バイトにしか過ぎない。USB162 の方は、32バイト詰め込んでいるはずだ。... ということは 動いていないときがあるということだ。
サンプリングしたデータは、タイマ1 の値なので、どのように動いているのか見ることができる。

このグラフは、サンプリングした時刻とサンプリング数の関係。HOST がデータを read しない時間帯があれば、横ばいになる。20 ms の間に 6 回そういう時間帯があったということ。ちなみにこのデータは、readsize を 128 バイト、8KHz サンプリング(1ms あたり 32バイト)でのデータ。常に read しているわけではなく、ときどきとまっていると見てよいはずだ。
Linux-2.6.17 CDC-ACM ドライバの性能
Linux-2.6 のマシンは、玄箱/HG しかもっていない。これでどうなるか見てみた。

read は、Windows 並の性能が出ている。それに対し write は、8k Hz (1ms に 32バイト) が上限になっている。ACM_NWB を 16にしてみたが、性能は変わらなかった。read はちゃんとできているが、write は、FTDI のような処理にしないと性能が出ないように思える。
おわりに
結論としては、Linux-2.4 の CDC-ACM ドライバの出来は悪い。2.6 になれば read は良くなっているが、write はさほどでもない。ftdi のように ちゃんとしたものしなければ、Windows 並の性能にならない。
最初に作ろうと思っている、ADC からデータを吸い上げる装置は 48kHz 16bit x 2 までなら Windows XP か Linux-2.6 を使えば、CDC でいけそうだが、次に作ろうと思っている、JTAG とか SPI のような write して read するアプリケーションの場合、Linux は遅いことになる。
どのようにドライバを改造すればよいか、少し判ってきたような気がするので、当面は CDC にこだわってみようと思う。
(続き)
チューニングしてみる 2
まずは、2.4 の CDC-ACM ドライバの write をFTDI のコードを参考にして、チューニングしてみる。
バッファのサイズはそのままで、多重に I/O をかける。そのための urb は 16 個で試してみる。
write の要求が来たら、urb がある限り usb_submit_urb するだけ。urb の管理が面倒だが、ロジックとしては簡単そうだ。

すこし変なところがあるが、Windows 並の 250 KB/sec ぐらい出るようになった。
read は、実はよくわからない。(多重に read をかけていない)オオリジナルがどうやっているか? ちょっと整理してみる。
○初期化(xx_open):
無条件に read をかける。
○read 完了(callback):
throttle == 0 の間、tty_insert_flip_char で
read したデータを tty の flipbuf ? に移動
(移動している間に throttle == 1 になる場合あり)
throttle == 1 の場合は、urb にデータを残して return
throttle == 0 の場合、全部を移動したので
次の read をかける。
○throttle = 0 の変化
xx_throttle callback 関数が call される。その中で
read 完了と同じ関数を call して 未処理の データを
tty の flipbuf に移動。
まあなんだか良くわからないがこんな感じだ。
filpbuf に移動しきれなかった場合、read がかけられないので、性能が低下しているように見える。
read を多重に出すというよりは、read 済みの データのバッファリングをなんとかすれば性能が出るのではないかと思えてきた。urb を多数もっていて、リングバッファのように使うコードにして、read 多重度 1 で制御するようなコードを作ればなんとかなるような気がしてきた。
多重度 1 ではダメだった。多重度 N で制御できるようにした上で、全体の URB 数 16 , 多重度 8 ぐらいにしたら、性能が出るようになった。
おわりに その2
いまさら、2.4カーネルの1ドライバのチューニングの話を書いてもしょうがないので、これで終わりにする。ただ、2.6 カーネルの CDC-ACM ドライバもイマイチなことは判ったし、チューニングの方法もだいぶわかった。いずれのドライバもちゃんとチューニングすれば、Windows と同等の性能になる。
目安としては、read/write/write+read とも 帯域はだいたい 250KB/sec ( write+read の片道の性能は 1/2)ぐらい。bps にすると 2M bps にすぎない。USB162 + CDC を使った場合、それ以上の性能を出すのは ホスト側にとって厳しい。
ADC の取り込み/DAC への出力なら、48KHz 16bit x 2 サンプリングが上限。...だが 取りこぼしが出そうなレンジ。44.1 kHz あたりが無難そうだ。
JTAG /SPI なら、1M bps での write+read が上限になりそうだ。
追記
一応動いているようなので、Linux-2.4 ドライバと 性能グラフ

ソースコード: acm.c
パッチ: acm-2.4.31.patch.gz
コメント:
○このソース は、2.4.31 - 2.4.35 で同一なので、2.4 最新版でも使える。
○USB162 でしか試していないので、AVR-CDC でも試す予定。