USBバルク転送タイムアウトのデバッグ:High-Speed・Full-Speed・STALL・NAK・ファームウェア遅延
USBバルク転送のタイムアウト、遅い読み出し、書き込みストール、NAK挙動、エンドポイントHALT復旧、速度不一致、ファームウェア遅延を、USB証拠をもとに診断する方法を解説します。
USBバルク転送は、「タイミングの正確さ」よりも「正しさ」が大事な場面で活躍します。ストレージデバイス、シリアルアダプタ、デバッグプローブ、ファームウェア更新ツール、スキャナ、ベンダー固有デバイス、各種データ収集製品など、多くの機器がバルクエンドポイントを採用しています。失敗するときの検索ワードは USB bulk transfer timeout、bulk endpoint stalled、USB read timeout、USB write timeout、libusb bulk transfer failed、USB device stops responding during bulk transfer などです。
アプリケーション側に見えるのは、たいてい「タイムアウト」か「I/Oエラー」です。しかしバス側ではもっと豊かな物語が繰り広げられています。デバイスがNAKを返し続けた、エンドポイントがストールした、ホストが再送した、デバイスがリセットした、転送サイズが間違っていた、期待より速度が遅かった、ファームウェアがデータ準備中にブロックした——そんな可能性があります。
Bus Scopeが活きるのは、バルク転送の失敗には「エンドポイントレベルの証拠」が必要で、アプリのスタックトレースだけでは足りない場面が多いからです。
バルク転送の得意分野
バルク転送はUSBプロトコルレベルでは信頼性があります。利用可能な帯域を使い、再送も可能です。「遅延の厳密さ」よりも「データの正しさ」が大事な、大容量データ移動に向いています。
バルク転送を使う機器の例:
- USBマスストレージ
- CDCシリアルアダプタ
- ベンダー固有ファームウェアツール
- デバッグプローブ
- 計測機器
- プリンタ/スキャナ
- 一部のキャプチャデバイス
- FPGA/マイコンのデータパイプ
バルク転送は余ったバス帯域を使うため、性能は他のUSBトラフィックやホストのスケジューリングに左右されます。
タイムアウト ≠ パケットロスとは限らない
バルク転送のタイムアウトは、たいてい「アプリ側のタイムアウト時間内に、ホスト側のリクエストが完了しなかった」ことを意味します。USBバスが合法的に振る舞っている場合でも発生します。
原因はさまざまです:
- デバイス側にデータがなく、NAKを返し続けている
- ファームウェアが忙しく、応答が遅れている
- STALL後にエンドポイントがHALTしている
- ホストが誤ったエンドポイントにリクエストを送っている
- 転送サイズがプロトコル上の期待と合っていない
- デバイスがリセットされた/切断された
- ドライバが転送を正しくサブミットしていない
- Full-Speed経路では期待スループットが出ない
- 別のデバイスがバス帯域を消費している
- アプリ側のタイムアウトが厳しすぎる
トレースを見れば、このうちどれが妥当か切り分けられます。
NAKの挙動
USBデバイスはNAKを返すことで「今は一時的に準備できていない」と示せます。NAKは必ずしもエラーではなく、フロー制御のシグナルです。
バルクINエンドポイントでNAKが繰り返されるなら、デバイス側にまだデータがないということです。バルクOUTエンドポイントでNAKが続くなら、デバイスがこれ以上データを受けられないということです。
問題になるのは「継続時間」と「文脈」です。数回のNAKは正常範囲。アプリタイムアウトまでNAKが続くなら、デバイスがいつまでも準備できないか、ホストが時期尚早にデータを期待しているかのどちらかです。
STALLとエンドポイントHALT
STALLはNAKとは別物です。多くの場合、エンドポイントが停止したか、その文脈ではサポート外のリクエストが来た、という意味です。復旧には次のリクエストが要ることが多いです。
CLEAR_FEATURE(ENDPOINT_HALT)
ホストがHALTをクリアしないと、その後の転送も失敗し続けます。クリア直後に再度ストールするなら、ファームウェア側のコマンドシーケンスが拒否されている可能性があります。
確認すべきポイント:
- タイムアウト前の最初のSTALL
CLEAR_FEATURE(ENDPOINT_HALT)の有無- クリア後に転送が再開するか
- 毎回同じコマンドでSTALLするか
- 繰り返すSTALLのあとでリセットが起きているか
High-Speed vs Full-Speedの現実的な期待値
USB速度によって「現実的なスループット」は変わります。Full-Speedで動いているデバイスは、High-Speedの性能を出せません。High-Speed対応デバイスでも、ケーブル・ハブ・ポート・信号品質・デバイスネゴシエーション次第でフォールバックすることがあります。
アプリがHigh-Speedの性能を見積もっていても、デバイスがFull-Speedで列挙されていれば、大容量転送でタイムアウトが現れます。
ディスクリプタ・ネゴシエートされた速度・エンドポイントの最大パケットサイズ・実際の転送ペースを確認しましょう。コネクタ形状やマーケラベルから速度を推測しないでください。
ファームウェアのコマンドプロトコル
多くのバルクデバイスは、USBの上に独自のコマンド/レスポンスプロトコルを持ちます。ホストがバルクOUTにコマンドを書き、バルクINのデータを待ちます。
タイムアウトは次のような場面で起きます:
- コマンドフォーマットが間違っている
- バルク転送の前にコントロールリクエストが必要
- ステータスを別エンドポイントで返している
- ホストが早く読みすぎている
- ホストが多く読みすぎている
- ファームウェアがコマンド処理中にブロックしている
- デバイスがZero-Length Packet境界を要求している
- 過去のエラー状態がクリアされていない
パケットの証拠を見れば、「デバイスがコマンドを無視した」「ストールした」「受け取ったが応答しない」「別エンドポイントで応答した」のどれかが見えます。
バルク転送サイズとショートパケット
USBバルクのプロトコルでは、ショートパケットが「転送の終わり」を示すことがあります。ホストが固定長を期待しているのに、デバイスがショートパケットを送ると、アプリ側で結果を誤認する可能性があります。デバイスがすでに転送を終えたあとでホストが追加データを待ち続けると、アプリ層でタイムアウトが発生します。
確認ポイント:
- 要求された転送長
- 実際に返ってきた長さ
- ショートパケット
- Zero-Length Packet
- USBの上位にあるプロトコルフレーミング
カスタムファームウェアやlibusbベースのツールでは特に重要です。
デバッグチェックリスト
次の流れで作業すると、原因に早くたどり着けます。
- 列挙とエンドポイントディスクリプタをキャプチャする
- デバイス速度とエンドポイントの最大パケットサイズを確認する
- バルクIN/バルクOUTエンドポイントを識別する
- タイムアウトするコマンドまたは転送をキャプチャする
- エンドポイントがNAK・STALL・データ・切断のどれを返すかを確認する
- STALL発生時は
CLEAR_FEATURE(ENDPOINT_HALT)による復旧を観察する - 要求長と実長を比較する
- デバイスがショートパケット/Zero-Length Packetを送るかを確認する
- ポート直結とハブ経由、High-SpeedとFull-Speedの経路を比較する
- 可能ならファームウェアログと突き合わせる
最終的な診断
USBバルク転送のタイムアウトは、单一のバグではありません。正常なNAKがアプリタイムアウトを超えたケース、エンドポイントSTALLが復旧されなかったケース、ファームウェアが応答しなかったケース、想定より速度が遅かったケース、プロトコルフレーミングが間違っていたケース、デバイスがリセットされたケース——いずれも可能性があります。
Bus Scopeはエンドポイントレベルのシーケンスを可視化するので、「汎用I/O失敗」ではなく「診断可能なUSBの証拠」としてタイムアウトを扱えるようになります。