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

2026/04/09 4:23

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

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

USB デバイスの操作は、libusb を使用してユーザー空間だけで完全に処理できるため、カーネルレベルのドライバ開発は不要です。  
例として、Fastboot モード(VID 18d1 / PID 4ee0)にある Android フォンを挙げます。接続すると `lsusb` は「Google Inc. Nexus/Pixel Device (fastboot)」と表示し、カーネルドライバは付いていません。また、ベンダー固有クラスインターフェースが 2 つのバルクエンドポイントを公開します:コマンド送信用 OUT 0x02 とレスポンス受信用 IN 0x81。

libusb のホットプラグコールバックはこのデバイスの到着を検出し、Fastboot コマンドを自動的に発行できます。典型的な手順は次のとおりです:
1. `libusb_control_transfer` を使用して GET_STATUS リクエストを送信します。2 バイトの応答はデバイスがセルフパワーであり、リモートウェイクアップをサポートしないことを示します。
2. GET_DESCRIPTOR リクエストを送信して完全なデバイスディスクリプタ(ベンダー/プロダクト ID、USB バージョン等)を取得します。
3. バルク OUT 0x02 を介して Fastboot コマンドを発行します(例:「getvar:version」を 64 バイトにパディング)。  
   デバイスは IN 0x81 で 4 文字のステータス(「OKAY」または「FAIL」)と任意のペイロードを返します。

同じユーザー空間アプローチは、バルク転送に依存する他の USB プロトコルにも適用できます。主な作業はカーネルコードを書く代わりにプロトコルロジックを実装することです。これにより OEM 向けドライバ開発が簡素化され、ブートローダーのテストが迅速化し、カーネルモジュールなしでカスタム USB デバイスの高速プロトタイピングやデバッグが可能になり、組込み開発者と広範な USB エコシステムに恩恵をもたらします。

本文

はじめに

USBデバイスのドライバーを書きなさいという指示を受けたとします。
最初は大変そうに思えるかもしれません――ドライバーはカーネルコードで、低レベル、デバッグが難しい…しかし実際にはそうではありません。USBデバイス用のドライバーを書くことは、ソケットを使うアプリケーションを書くほど難しくありません。

この記事は、ハードウェアに触れた経験がなくても USB を利用したい人向けに、USB の概要をわかりやすく紹介します。
「USB in a Nutshell」など詳細なリソースもありますが、ハードウェアの背景知識がないと取り付きづらいものです。USB やソケットを使うために組み込みシステムエンジニアでもネットワークスペシャリストでもなくても構いません。


USB デバイス

ここでは Android スマートフォンのブートローダー(fastboot)モードを使います。理由は次の通りです。

  • 取得が簡単
  • プロトコルがよく文書化されていてシンプル
  • ドライバーが事前にインストールされていることがほとんどないため、OS が実験を妨げません

デバイスをブートローダーにする方法は機種ごとに異なります。一般的には電源投入時にボタンの組み合わせを押し続けます。私の場合は 音量ダウン を押したまま起動します。


手動でデバイスを列挙(Enumeration)

列挙とは、ホストがデバイス自身について情報を問い合わせることです。
USB デバイスをコンピュータに差し込むと、OS は以下の情報から適切なドライバーを決定します。

  • USB Device Class → 標準ドライバー
  • Vendor ID (VID)Product ID (PID) → ベンダー固有ドライバー

基本情報

ドライバーがなくても、スマートフォンを PC に差し込むと USB デバイスとして認識されます。
Linux では

lsusb
コマンドで確認できます。

$ lsusb
Bus 008 Device 014: ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)
  • Bus / Device – 実際の USB ポートを示す識別子
  • ID – VID(
    18d1
    )と PID(
    4ee0

VID は USB‑IF が割り当て、PID はメーカーが決めます。

クラスとドライバー情報

lsusb -t
でデバイスツリーを表示すると次のようになります。

$ lsusb -t
/:  Bus 008.Port 001: Dev 001, Class=root_hub, Driver=xhci_hcd/1p, 480M
   |__ Port 001: Dev 002, If 0, Class=Hub, Driver=hub/4p, 480M
   |__ Port 003: Dev 003, If 0, Class=Hub, Driver=hub/4p, 480M
   |__ Port 002: Dev 014, If 0, Class=Vendor Specific Class, Driver=[none], 480M

最下のデバイスが私たちのスマートフォン(Bus 008、Device 014)です。

Class=Vendor Specific Class
はカスタムプロトコルを使用していることを示し、
Driver=[none]
はドライバーがロードされていない状態です。これこそが私たちにとって良い兆候です。

Windows の場合

lsusb
が無いので、同じ情報はデバイスマネージャーや USB Device Tree Viewer などで確認できます。


libusb を使った列挙

カーネルコードを書く代わりに、libusb を利用したユーザースペースアプリケーションを書きます。libusb は汎用ドライバーとデバイスをクレームするための API を提供します。

#include <print>
#include <libusb-1.0/libusb.h>

auto hotplug_callback(libusb_context *ctx,
                      libusb_device *device,
                      libusb_hotplug_event event,
                      void *user_data) -> int {
    std::println("Device plugged in!\n");
    return 0;
}

auto main() -> int {
    libusb_context *context = nullptr;
    libusb_init(&context);

    libusb_hotplug_callback_handle hotplug_callback_handle;
    libusb_hotplug_register_callback(
        context,
        LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, // デバイス投入イベント
        LIBUSB_HOTPLUG_ENUMERATE,           // 既に接続されているデバイスも対象
        0x18d1, 0x4ee0,                     // VID / PID
        LIBUSB_HOTPLUG_MATCH_ANY,
        hotplug_callback, nullptr,
        &hotplug_callback_handle);

    while (true) {
        if (libusb_handle_events(context) < 0)
            break;
    }

    libusb_hotplug_deregister_callback(context, hotplug_callback_handle);
    libusb_exit(context);
}

プログラムを実行し、スマートフォンを差し込むと次のように表示されます。

$ ./libusb_enumerate
Device plugged in!

カーネルコードは触れていません。

Windows の場合
既にドライバーがロードされている場合は

libusb_detach_kernel_driver()
を使用します。そうでない場合は Zadig などで WinUSB.sys をインストールしてください。


デバイスとの通信

最も簡単な通信方法は Control エンドポイント(アドレス 0x00) を使うことです。このエンドポイントは標準プロトコルを使用します。ここでは

GET_STATUS
要求を送ります。

libusb_device_handle *handle = nullptr;
libusb_open(device, &handle);

std::vector<std::uint8_t> data(0xFF);
const auto result = libusb_control_transfer(
    handle,
    uint8_t(LIBUSB_ENDPOINT_IN) |   // デバイスからデータを取得
    LIBUSB_RECIPIENT_DEVICE |
    LIBUSB_REQUEST_TYPE_STANDARD,   // 標準要求
    LIBUSB_REQUEST_GET_STATUS,      // GET_STATUS
    0x00, 0x00,                     // wValue / wIndex
    data.data(), data.size(),
    1000);                          // タイムアウト

if (result >= 0)
    print_bytes(std::span(data).subspan(0, result));

libusb_close(handle);

出力例:

$ ./libusb_enumerate
Addr: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0000: 01 00

最初のバイトはデバイスがセルフパワーであること(

1
)を示し、2 番目はリモートウェイクアップをサポートしていないことを表します。


ディスクリプタの要求

ディスクリプタとはデバイスを記述する二進構造です。

GET_DESCRIPTOR
Device Descriptor を取得できます。

const auto result = libusb_control_transfer(
    handle,
    uint8_t(LIBUSB_ENDPOINT_IN) |   // IN エンドポイント
    LIBUSB_RECIPIENT_DEVICE |
    LIBUSB_REQUEST_TYPE_STANDARD,
    LIBUSB_REQUEST_GET_DESCRIPTOR,  // GET_DESCRIPTOR 要求
    (LIBUSB_DT_DEVICE << 8) | 0,     // 0 番目の Device Descriptor
    0x00,                           // 言語 ID
    data.data(), data.size(),
    1000);

出力例:

$ ./libusb_enumerate
Addr: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0000: 12 01 00 02 00 00 00 40 D1 18 E0 4E 99 99 01 02
0010: 00 01

USB 規格(第 9.6.1章)に従うと、以下の構造体になります。

struct DeviceDescriptor {
    u8  bLength;
    u8  bDescriptorType;
    u16 bcdUSB;
    u8  bDeviceClass;
    u8  bDeviceSubClass;
    u8  bDeviceProtocol;
    u8  bMaxPacketSize0;
    u16 idVendor;
    u16 idProduct;
    u8  iManufacturer;
    u8  iProduct;
    u8  iSerialNumber;
    u8  bNumConfigurations;
};

idVendor
idProduct
lsusb
の値と一致します。

他のディスクリプタ(Configuration、Interface、Endpoint、String)も同様に取得できます。

lsusb -v
を使うとすべてを確認できる便利な方法です。

$ lsusb -d 18d1:4ee0 -v
Bus 001 Device 012: ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)
...
Device Descriptor:
    bLength                18
    bDescriptorType         1
    bcdUSB               2.00
    bDeviceClass            0 [unknown]
    ...

このデバイスは、単一の Configuration を持ち、その中に Interface と 2 つの Bulk エンドポイントがあります。


エンドポイント

種類用途
Controlデバイスごとに 1 本。常にアドレス
0x00
。設定や問い合わせ用。ディスクリプタには表示されない。
Bulk高帯域幅だが低優先度。大容量でタイムセンシティブでないデータ(例:Mass Storage)。
Interrupt小さなデータを低レイテンシで送受信。ホストが頻繁にポーリング。
Isochronous音声/映像などの時間的制約が厳しいストリーミング。libusb では非同期で扱う。

エンドポイントには方向もあります。

  • IN – ホストがデバイスからデータを要求(ビット7=1)
  • OUT – ホストがデバイスへデータ送信(ビット7=0)

USB バス上に存在できるカスタムエンドポイントは、制御エンドポイントを除き 127 本までです。


Fastboot プロトコル

Fastboot は Android のブートローダーで使われるシンプルなプロトコルです。

Host → Client

getvar:version

Client → Host

OKAY0.4   // 4 桁のステータスコード + データ

以下は、コマンドを送信し応答を受け取るサンプルです。

libusb_device_handle *handle = nullptr;
libusb_open(device, &handle);
libusb_claim_interface(handle, 0);

std::vector<uint8_t> bytes(64);          // Full‑speed 用に 64 バイト
std::ranges::copy("getvar:version", bytes.begin());

int num_bytes_transferred = 0;
libusb_bulk_transfer(handle,
    LIBUSB_ENDPOINT_OUT | 0x02,   // OUT エンドポイント 0x02
    bytes.data(), bytes.size(),
    &num_bytes_transferred,
    1000);

std::println("Request: {}", std::string_view(reinterpret_cast<const char*>(bytes.data()), num_bytes_transferred));

std::ranges::fill(bytes, 0x00);
num_bytes_transferred = 0;

libusb_bulk_transfer(handle,
    LIBUSB_ENDPOINT_IN | 0x01,    // IN エンドポイント 0x81
    bytes.data(), bytes.size(),
    &num_bytes_transferred,
    1000);

std::println("Response: {}", std::string_view(reinterpret_cast<const char*>(bytes.data()), num_bytes_transferred));

libusb_release_interface(handle, 0);
libusb_close(handle);

実行すると次のように表示されます。

$ ./libusb_enumerate
Request: getvar:version
Response: OKAY0.4

最初の 4 バイト(

OKAY
)は成功を示し、残りが値
0.4
です。


おわりに

カーネルコードを触ることなく、ユーザースペースで完全な USB “ドライバー” を実装しました。
USB ドライバーの共通原則は「列挙 → ディスクリプタ取得 → エンドポイント設定 → プロトコル通信」です。
MTP などより複雑なプロトコルでも、基本的な仕組みは同じです。TCP 上でソケットを扱うのと比べて決して難しいものではありません。

同じ日のほかのニュース

一覧に戻る →

2026/04/09 0:40

私、macOS XをNintendo Wiiにポート(移植)いたしました。

## Japanese Translation: --- ## 改良された要約 Mac OS X 10.0(Cheetah)は、Nintendo Wii 上でネイティブに動作するようにポートされ、コンソールをキーボード/マウス入力と GUI サポート付きの完全機能型デスクトップへ変貌させました。プロジェクトのコアは、*ppcskel* をベースに最初から書き直されたカスタムブートローダーです。このブートローダーは、Wii の PowerPC 750CL CPU を起動し、メモリレイアウトを設定し、最小限のデバイスツリー(root → cpus → PowerPC,750; memory)を作成します。SD カードから XNU カーネルをロードし、実行中にカーネルバイナリをパッチ(MEM1/MEM2 用の BAT 設定と USB Gecko へのコンソール出力)し、制御を XNU に渡します。 ブートローダーが提供する主要ドライバーは次の通りです: - **SD‑カードドライバー**:Starlet MINI IPC コマンド(IPC_SDMMC_SIZE, READ, WRITE)を介して IOBlockStorageDevice を実装し、XNU が SD カードからルートファイルシステムをマウントできるようにします。 - **フレームバッファドライバー**:0x01700000 に RGB フレームバッファ(640×480 @ 16 bpp)を提供し、Wii のアナログテレビ出力用に YUV へ変換して Mac OS X GUI を実現します。 - **USB サポート**:PCI デバイスのニブ(NintendoWiiHollywoodPCIDevice)を作成し、AppleUSBOHCI をパッチして受け入れさせ、OHCI ドライバーからバイトスワップ処理を除去することでリバースレトルエンディアンハードウェアに対応し、USB キーボード/マウス機能をフル実装します。 ブートローダーは Apple Partition Map を解析し、起動可能なパーティションを一覧表示し、/chosen/memory‑map ノード経由でカーネル拡張を直接メモリにロードできるようにするため、改変されていない Mac OS X インストーラーパーティションからのインストールも可能です。必要なカーネル変更は最小限(BAT 設定、“hollywood” I/O ベース取得、フレームバッファキャッシュ整合性修正)で済み、その他すべてのドライバーはブートローダーが提供します。 この成果は、歴史的にサポートされていなかったプラットフォーム――Nintendo Wii――でも Mac OS X Cheetah をエンドツーエンドで動作させることを示し、ホビイストに低コストのレトロコンソールとして機能するデスクトップコンピュータを提供します。

2026/04/08 17:53

**コードを読む前に実行しておくべき一般的な Git コマンド** - `git fetch --all` *リモートの全ブランチとタグを取得します。* - `git status` *現在のブランチと未コミットの変更点を確認します。* - `git checkout <branch>` *対象となる機能やバグ修正用ブランチに切り替えます。* - `git pull --rebase` *ローカルブランチを最新の upstream コミットで更新します。* - `git log --oneline --graph --decorate -5` *簡潔なコミット履歴を表示し、文脈を把握します。* - `git diff origin/<branch>..HEAD` *まだプッシュしていない変更点を確認します。* - `git rev-parse HEAD` *現在のコミットハッシュを取得(参照に便利)。* - `git tag --list` *利用可能なタグ一覧を表示し、バージョン管理に役立てます。* - `git show <commit>` *特定のコミットの詳細と差分を調べます。* これらのコマンドで、コードを掘り下げる前にリポジトリの状態を素早く把握できます。

## 日本語訳: 以下の文章を日本語に翻訳してください。 ### 修正版要約 この記事は、ソースファイルを検査する前にコードベースの簡易監査が隠れた健康リスクを明らかにできる方法を示しています。これは5つの簡潔な Git コマンドを実行することで達成されます。 1. `git log --format=format: --name-only --since="1 year ago" | sort | uniq -c | sort -nr | head -20` 過去 1 年間で最も変更頻度が高い上位 20 ファイルを一覧表示し、潜在的な「ドラッグ」スポット(高い変更率)をフラグ付けします。 2. `git shortlog -sn --no-merges` コミット数で貢献者をランク付けします。単一人物が 70 % 超を占める場合はバスファクターが低く、過去 6 ヶ月にその貢献者がいない場合は危機的状況を示唆します。 3. `git log -i -E --grep="fix|bug|broken" --name-only --format='' | sort | uniq -c | sort -nr | head -20` バグ関連コミットが最も多いファイルを特定し、変更率データと照合して最高リスクコードをピンポイントします。 4. `git log --format='%ad' --date=format:'%Y-%m' | sort | uniq -c` 月ごとのコミット数を表示し、活動の加速または減退(例:半月間のドロップ)が重要人物の離脱を示す可能性があります。 5. `git log --oneline --since="1 year ago" | grep -iE 'revert|hotfix|emergency|rollback'` リバートとホットフィックスの数をカウントします。頻繁なリバートはデプロイ/テストが不安定であることを示し、ゼロの場合は安定性またはコミットメッセージ不足を意味する可能性があります。 これらの指標(変更ホットスポット、バスファクター問題、バグクラスタ、プロジェクトモーメンタム、火災対策頻度)は、コード複雑度測定だけよりも欠陥予測精度が高いと示されています(Microsoft Research 2005)。記事はスクワッシュマージワークフローが著者データを歪めることを警告しています。最初の監査に1時間を費やした後、筆者は特定されたリスクスポットに対して週単位で詳細調査を計画しています。関連研究としてはエンジニアリングチーム速度、Vim 使用、レガシー Rails 監査、Rails `default_scope` が引用されています。この手法は開発者に迅速なコミット履歴ベースの診断を提供し、高リスクファイルへの詳細コードレビューを集中させることでバグ削減、チームレジリエンス、およびリリース信頼性の向上を実現します。

2026/04/09 2:11

**カルマンフィルタを理解する:シンプルなレーダー例題** カルマンフィルタは、ノイズの多い測定値からシステム(位置・速度など)の状態を推定するアルゴリズムです。 この例では、レーダーセンサーを使って移動物体を追跡する方法を見てみます。 1. **システムモデル** - 状態ベクトル:\(\mathbf{x}_k = [x_k,\; y_k,\; \dot{x}_k,\; \dot{y}_k]^T\) - 変換行列(定常速度モデル): \[ \mathbf{F} = \begin{bmatrix} 1 & 0 & \Delta t & 0\\ 0 & 1 & 0 & \Delta t\\ 0 & 0 & 1 & 0\\ 0 & 0 & 0 & 1 \end{bmatrix} \] - プロセスノイズ共分散:\(\mathbf{Q}\)(加速度不確かさに依存) 2. **測定モデル** - レーダーは距離 \(r\) と方位角 \(\theta\) を返します。 - 測定ベクトル:\(\mathbf{z}_k = [r_k,\; \theta_k]^T\) - 観測行列: \[ \mathbf{H} = \begin{bmatrix} \dfrac{x}{\sqrt{x^2+y^2}} & \dfrac{y}{\sqrt{x^2+y^2}} & 0 & 0\\[4pt] -\dfrac{y}{x^2+y^2} & \dfrac{x}{x^2+y^2} & 0 & 0 \end{bmatrix} \] - 測定ノイズ共分散:\(\mathbf{R}\) 3. **カルマンフィルタの手順** - *予測* \[ \hat{\mathbf{x}}_{k|k-1}= \mathbf{F}\,\hat{\mathbf{x}}_{k-1|k-1} \] \[ \mathbf{P}_{k|k-1}= \mathbf{F}\,\mathbf{P}_{k-1|k-1}\,\mathbf{F}^T + \mathbf{Q} \] - *更新* イノベーション(残差)を計算: \[ \mathbf{y}_k = \mathbf{z}_k - h(\hat{\mathbf{x}}_{k|k-1}) \] イノベーション共分散: \[ \mathbf{S}_k = \mathbf{H}\,\mathbf{P}_{k|k-1}\,\mathbf{H}^T + \mathbf{R} \] カルマンゲイン: \[ \mathbf{K}_k = \mathbf{P}_{k|k-1}\,\mathbf{H}^T\,\mathbf{S}_k^{-1} \] 更新された状態と共分散: \[ \hat{\mathbf{x}}_{k|k}= \hat{\mathbf{x}}_{k|k-1}+ \mathbf{K}_k\,\mathbf{y}_k \] \[ \mathbf{P}_{k|k}= (\mathbf{I}-\mathbf{K}_k\,\mathbf{H})\,\mathbf{P}_{k|k-1} \] 4. **実装のヒント** - レーダー測定関数が非線形の場合は、線形化したモデル(拡張カルマンフィルタ)を使用します。 - 追跡が失われた際には、頻繁にフィルタを再初期化してください。 - \(\mathbf{Q}\) と \(\mathbf{R}\) を実際のセンサーノイズ特性に合わせて調整します。 5. **結果の解釈** - 追加測定が増えるにつれて、推定位置は真軌道に収束します。 - フィルタが十分なデータを蓄積し不確実性が減少すると、速度推定も改善されます。 これらの手順に従えば、シンプルなレーダー測定値でもカルマンフィルタで処理でき、物体運動の正確かつリアルタイムな追跡を実現できます。

## Japanese Translation: カルマンフィルタは、測定ノイズとプロセスノイズが存在する状況でシステムの状態を予測し修正する最適な再帰推定器です。単純な一次元レーダートラッキング例では、状態ベクトル \(x=[r;\;v]\) が距離と速度を表します。初期時刻 \(t_0\) での測定値(\(r=10\,000 \text{ m}, v=200 \text{ m/s}\))から、フィルタは一定速度モデルを用いて次状態を予測します(\(\Delta t = 5\,\text{s}\)): \(x_{1|0}=F x_{0|0}=[11\,000;\;200]\)。 予測された共分散行列は、遷移行列 \(F\) と必要に応じてプロセスノイズ \(Q=\sigma_a^2\begin{bmatrix}\frac{\Delta t^4}{4}&\frac{\Delta t^3}{2}\\ \frac{\Delta t^3}{2}&\Delta t^2\end{bmatrix} = \begin{bmatrix}6.25&2.5\\ 2.5&1\end{bmatrix}\) によって更新され、\(P_{1|0}=28.5,\;3.75;\;3.75,\;1.25\) となります。 時刻 \(t_1\) での第二測定値(\(r=11\,020 \text{ m}, v=202 \text{ m/s}\))、共分散行列 \(R_1=\begin{bmatrix}36&0\\0&2.25\end{bmatrix}\)は、カルマンゲイン \(K_1=P_{1|0}(P_{1|0}+R_1)^{-1}= \begin{bmatrix}0.4048&0.6377\\0.0399&0.3144\end{bmatrix}\) を通じて組み込まれます。 更新された状態は \(x_{1|1}=[11\,009.37;\;201.43]\)、共分散行列は \(P_{1|1}=(I-K_1)P_{1|0}= \begin{bmatrix}14.57&1.43\\1.43&0.71\end{bmatrix}\) となり、事前予測と測定の不確実性に比べて減少します。 フィルタは次に \(t_2\) に再び予測します:\(x_{2|1}=F x_{1|1}=[12\,016.5;\;201.43]\)、共分散行列 \(P_{2|1}=F P_{1|1} F^T + Q = \begin{bmatrix}52.86&7.47\\7.47&1.71\end{bmatrix}\)。 この簡潔な説明は、すべての重要な定量的詳細を保持し、予測‑更新サイクルを示し、カルマンフィルタが不確実性をどのように減少させるかを強調しています。基本的な線形代数と推定概念に精通した読者には明瞭であり、不必要な専門用語は避けています。

**ソフトウェア開発者のためのUSB:ユーザースペース USB ドライバー作成入門** | そっか~ニュース