世界を動かす技術を、日本語で。

ソフトウェア開発者のためのUSB: ユーザースペースUSBドライバの作成入門

概要

  • USBデバイスドライバ作成は思ったより難しくない
  • Android端末のBootloaderモードを例に解説
  • lsusbやlibusbを使ったデバイス情報取得方法
  • ユーザ空間アプリでUSB制御が可能
  • GET_STATUSやGET_DESCRIPTORリクエストによるデータ取得手順

USBデバイスドライバ入門

  • USBデバイス のドライバ作成は一見難しそうだが、 Kernelコード を書く必要はない場合が多い
  • ユーザ空間アプリケーション でUSB通信が可能、Socketsを使うのと似た感覚
  • ハードウェアの専門知識がなくても、 libusb 等のライブラリを使えば簡単に扱える
  • 参考資料として「 USB in a NutShell」などがあるが、初心者には難解な場合も

BootloaderモードのAndroid端末を例に

  • 実験用デバイスとして AndroidスマートフォンのBootloaderモード を利用
    • 入手が容易
    • プロトコルがシンプルかつドキュメントも豊富
    • OSに標準ドライバがなく、独自ドライバの開発がしやすい
  • Bootloaderモードへの移行方法は機種ごとに異なるが、 特定のボタンを押しながら起動 するのが一般的

USBデバイスの手動列挙(Enumeration)

  • 列挙(Enumeration)とは、 ホストがデバイスの情報を取得 するプロセス
  • lsusb コマンドでデバイス情報を確認可能
    • 例:lsusb出力のID 18d1:4ee0Vendor ID(VID): 18d1Product ID(PID): 4ee0
    • VID はUSB-IFが企業に割り当て、 PID は企業が製品ごとに割り当て
  • lsusb -tデバイスツリー とクラス・ドライバ情報を表示
    • Class=Vendor Specific Class はメーカー独自プロトコル
    • Driver=[none] ならOSがドライバを割り当てていない状態

Windowsの場合の注意

  • Windowsでは lsusb が使えないため、 デバイスマネージャUSB Device Tree Viewer 等で情報取得
  • VID/PID が主な識別情報
  • libusb を使えばユーザ空間でUSBデバイスにアクセス可能
    • Windowsでは Winusb.sys ドライバが必要な場合もあり、 Zadig ツールでドライバ置換が可能

libusbによるデバイス列挙

  • libusb を使い、 ホットプラグイベント でデバイス検出が可能
    • 指定した VID/PID のデバイスが接続されたときにコールバック実行
  • サンプルコードで libusb_hotplug_register_callback を利用
    • デバイス接続時に「 Device plugged in!」と表示
  • カーネルコード不要で、 ユーザ空間のみで検出・制御 が可能

デバイスとの通信:Control Endpoint

  • 最初の通信は Control Endpoint(ID 0x00) 経由で行う
    • OSもこのエンドポイントを使い、 VID/PID などを取得
  • libusb_control_transfer 関数で GET_STATUS リクエストを送信
    • 例:デバイスが 自己電源 か、 Remote Wakeup 対応かを取得
  • 取得データの解釈はUSB仕様書を参照

ディスクリプタ(Descriptor)の取得

  • ディスクリプタは デバイスの能力や情報を伝える構造体
    • GET_DESCRIPTOR リクエストで取得
  • 例:デバイスディスクリプタ(Device Descriptor)には bLength, bDescriptorType, idVendor, idProduct などが含まれる
  • 他にも Configuration, Interface, Endpoint, String 等のディスクリプタが存在
  • lsusb -v -d VID:PID で全ディスクリプタ情報を取得可能

まとめ

  • USBデバイスドライバ作成は ユーザ空間アプリ でも十分対応可能
  • libusb などのライブラリを使うことで、カーネルコード不要で デバイス検出・制御 ができる
  • VID/PIDディスクリプタ の取得・解析が基本的な流れ
  • OSやデバイスによって必要なドライバや手順が異なるため、 環境ごとの注意 が必要

Hackerたちの意見

でも、これってUSBドライバがアプリケーションコードでもあるってことだよね?ドライバというより、ライブラリとプログラムの組み合わせみたいな感じ。例えば、USBからイーサネットのデバイスがあるとしたら、これをイーサネットアダプタのサブシステムにどうやって接続するの?

Linuxだと、アプリケーションからtun/tapデバイスを作って、その上で送信されたデータをイーサネットアダプタへのリクエストに変換できるよ。もちろん、ユーザースペースでこれをやる場合、カーネルと通信する方法が必要だったり、他のサブシステムもユーザースペースにいる必要があるね。

比較的標準的なものは、良い汎用サポートを受ける傾向があるよね。例えば、イーサネットデバイスは一般的にUSB/CDC/ECMやRNDISになる。正しいディスクリプタがあれば、そのまま動くかも。ユーザランドのアプローチは、変わったデバイスやカスタムデバイスにはもっと役立つよ。特にWindowsでは、ドライバ署名を気にせずにこういうユーザースペースの「ドライバ」を作れるし、libusbを使えばポータブルにもなるよ。(仕事用に小さなUSB DFUベースのツールを維持してる)

C++がめちゃくちゃに見えるね。矢印を打てるキーボードにはまだ出会ったことがないよ。

一部の開発者は、リガチャーベースのフォントが好きなんだ。2つの文字を1つのグリフにまとめるんだよね。

これは単なるプログラミングフォントのリガチャーだよ。コピー&ペーストすると実際の文字が見えるよ。例えば、auto main() -> int { (これもモダンC++のトレーリングリターンタイプだね。)

これはただの「->」だよ。リガチャーフォントがそれを一つの矢印として表示してるだけ。

どんなキーボードでも、コンポーズキーを設定すれば「→」が打てるよ :)

USBデバイスを渡されて、それにドライバを書けって言われたら、返して「OSがすでに仮想COMポートとして認識しているデバイスの一つでできないことを証明して」って頼むべきだね。

もしGolangでこれをやりたいなら、cgoがいらないライブラリを書いたよ: https://github.com/kevmo314/go-usb これを使って、UVCデバイスにcgoなしでアクセスしてる: https://github.com/kevmo314/go-uvc

Rustのためには、こちらをどうぞ:https://github.com/kevinmehall/nusb

タイミング最高!今、地元のギターセンターでMOTU MIDI Express XTを手に入れる予定なんだ。数週間前に届いた時にお金を払ったんだけど、中古機材には盗難防止のために待機期間があるんだよね。残念ながら、これってクラスコンプライアントのMIDI-over-USBじゃなくて、変な独自プロトコルを使ってるから、LinuxやOpenBSD、Haikuを使ってるPCからUSB経由で使えないんだ。今のところは、シンセモジュールとコントローラーの間をルーティングするだけだから問題ないけど、PC側も動かせたらいいなと思ってる。既存のLinuxドライバー¹があるみたいで、期待できそうなんだけど、どうやらMIDIポートをJACK用に露出させる最低限のことしかやってないみたい。安定性やXTのサポートについても不明だし(READMEにはカーネルパニックが修正されたって書いてあるけど、オープンな問題もあるし、XTがサポートされてるって書いてあるけど、そっちにもオープンな問題がある)。プロプライエタリなアプリみたいに新しいルーティングプリセットを作れたらいいし、余計なドライバーをカーネルに入れずに使えたら最高だな。OpenBSDやHaikuでも使えるようにしたいから、LibUSBのドキュメントを見てるんだ。ユーザースペースのUSBドライバーがあれば、関連するMIDIポートを表示して、必要に応じてMIDIポートをリルーティングするツールも作れるし、ちょうどいいスタート地点の記事だなと思った。 ---- ¹: https://github.com/vampirefrog/motu

そのドライバーをAURにパッケージしたよ、同じデバイスを持ってるからね。バイナリーブロブがうまく動かないんだ(正直、あんまり頑張ってないけど)し、優先順位も高くないから、ダムMIDIツールとして使うのは全然問題ないよ。

すごく役立つ紹介だね!低レベルのハードウェアAPIで作業するのは大変だけど、やりがいもある。現代のOSの抽象化レイヤーのおかげで楽にはなったけど、その下に何があるかを理解するのは本当に貴重だよね。

ずいぶん前にシンプルなUSBデバイスを作ろうとした時、どうやってやるかの情報が全然なかったんだよね。例えば、正しくディスクリプタを書く方法とか。典型的なアドバイスは、作りたいデバイスに似たものを見つけて、そのディスクリプタをコピーして、自分のデバイスに合わせて試行錯誤することだった。USBって素晴らしい規格だね。俺が間違ってる?

ちょっとバカな質問だけど、USBデバイスはDMAをサポートしてるの?ホストを通してやるの?それとも、USBデバイスは常にホストメモリにデータをプッシュ/プルしてるの?

USBデバイスはPCIeやFireWireのようにホストメモリを直接アドレス指定できないけど、XHCIコントローラーはホストメモリとのDMAを行ってるし、ほとんどのUSBデバイスコントローラーはUSBとデバイスのRAMの間で何らかのDMAを持ってるよ。

いい記事だね!最近、同じような技術を使ってMacbook M3のためにusbipシステムを作ってたんだ。ただ、新しいmacOSシステムではこのアプローチに制限があることは注意が必要だよ。macOSがサポートしているデバイスのためにlibusbの「ユーザースペースUSBドライバー」をビルドできないからね。セキュリティ機能を手動で無効にしないと、システムが認識しているUSBデバイスのドライバーを上書きできないんだ。

何年も前にこの記事を読めてたらよかったな。ノートパソコンの機能をリバースエンジニアリングするのがもっと簡単だったのに。キーボードのLEDプログラムは、今でもお気に入りのプロジェクトの一つだよ。