USBシリアルCOMポートが消える:再列挙、ドライババインド、ポート番号、CDCブリッジのデバッグ
USBシリアルCOMポートの消滅、ポート番号変更、再列挙、CDC ACMブリッジのリセット、ドライババインド失敗、古いハンドルを使い続けるアプリのトラブルシューティング方法を解説します。
USBシリアルデバイスはあらゆるところにあります。Arduinoボード、産業用コントローラ、モデム、GPS受信機、テスト治具、デバッグプローブ、PLCツール、バーコードデバイス、独自CDC ACMファームウェア。COMポートが消えると、USB serial COM port disappears、COM port missing Device Manager、USB serial re-enumerates、CDC ACM device disconnects、COM port changes after reconnect で検索される羽目に。アプリの側は「ポートを開けない」としか言わないのが普通です。
Bus Scopeが効くのは、COMポート消滅の原因レイヤーがまったく異なるからです。USB列挙/ディスクリプタ問題/ドライババインド/デバイスリセット/古いアプリハンドル/ラインコーディングリクエスト失敗/WindowsのCOM番号再採番——いずれも候補になります。
COMポートはUSBデバイスそのものではない
COMポートは、USBデバイスが列挙しシリアルドライバがバインドされた「後」に作られるOS抽象です。USBデバイスが一度も列挙しなければCOMポートは出ません。USBデバイスは列挙したのにCDCインターフェースが落ちればCOMポートは依然として現れないかもしれません。
レイヤーを分けて確認します。
- USBデバイスはアタッチしたか?
- ディスクリプタは正しく読まれたか?
- コンフィギュレーションは完了したか?
- CDCインターフェースは現れたか?
- ドライバはバインドしたか?
- OSはCOMポートを割り当てたか?
- アプリは正しい現在のポートを開いたか?
再列挙はポート番号を変える
Windowsは、シリアル番号/USBパス/VID/PID/インターフェース識別子が変わったデバイスには新しいCOM番号を割り当てることがあります。ブートローダーモードにリセットされたデバイスは、別COMポートになるかCOMポート自体を失うことがあります。
症状:
- デバイスがCOM8だったのに今はCOM11
- アプリが古いCOMポートを覚えている
- 別のUSBポートに差し替えると割り当てが変わる
- ブートローダーは別ポートを使う
- ファームウェア更新後にデバイスが不明デバイスとして現れる
バストレースはデバイス識別子が変わったかどうかを示してくれます。
CDC ACMコントロールリクエスト
CDCシリアルデバイスはクラスリクエストを受けることがあります。
SET_LINE_CODINGGET_LINE_CODINGSET_CONTROL_LINE_STATESEND_BREAK
ファームウェアがこれらをストール/誤処理すると、ポートは開いてもデータが流れない/ドライババインドが失敗する/ということが起きます。
接続時だけでなく「ポートを開いた後」をキャプチャしましょう。アプリがCOMポートを開いてドライバがラインコーディングやDTR/RTSを送る段階で多くが失敗します。
古いアプリハンドル
USBが正しく再列挙しても、アプリが古いハンドルやキャッシュ済みCOMポートリストを使い続けていることがあります。バス上は新デバイスが見えていますが、アプリはポートを解放も再取得もしていないため失敗します。
これはUSBバスの失敗ではなく、アプリ/デバイス管理挙動の問題です。
デバッグチェックリスト
次の流れで作業すると、原因に早くたどり着けます。
- 接続時からキャプチャする
- デバイスディスクリプタとコンフィギュレーションディスクリプタを確認する
- CDCインターフェースディスクリプタを確認する
- アプリがCOMポートを開く瞬間をキャプチャする
- CDCクラスリクエストを確認する
- ラインコーディング後にリセット/切断がないか確認する
- 再接続前後のデバイス識別子を比較する
- COM番号が変わったかを確認する
- アプリが古いポート名を使っていないか確認する
- USB証拠とOSポート割り当てメモの両方を保全する
最終的な診断
USBシリアルCOMポートの消滅は、列挙失敗/CDCディスクリプタ失敗/ドライババインド失敗/新識別子での再列挙/ラインコーディング失敗/負荷時リセット/アプリ側状態不整合のどれかであり得ます。
Bus ScopeはUSB側の物語を可視化するので、COMポート症状をアタッチ/ディスクリプタ/CDCリクエスト/リセット/実際のデバイス識別子に紐づけられます。