Linux の USB

このメモは、ある USB デバイス (BT ドングル) に不要なドライバ (usbfs) が bind されてしまう問題を調査する過程のメモだ。肝心の問題はまだ解決していない。udev の `kmod load $env{MODALIAS}` 以外でドライバが bind される事が分かったら続きを書く。

## lsusb による接続状態の表示

Linux から見ると USB デバイスは以下のような情報を持つらしい。

* Linux マシンは複数の Bus を持つ。
* 各 Bus は Port を持つ。
* 各 Port がさらに Port を持つ事もある。
* USB デバイスは各 Bus ごとに一意の Device を持つ。
* USB デバイスごとに vendor:product の形でデバイスを特定する ID を持つ。
* USB デバイスは複数の If を持つ。
* 各 If ごとにデバイスの機能を特定する Class がある。

Linux マシンに繋がった USB 機器の物理的な接続状態を見るには `lsusb -t' を使う。

```
# lsusb -t
/:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=uhci_hcd/2p, 12M
    |__ Port 1: Dev 2, If 0, Class=Human Interface Device, Driver=usbhid, 12M
    |__ Port 2: Dev 3, If 0, Class=Hub, Driver=hub/7p, 12M
        |__ Port 1: Dev 4, If 0, Class=Wireless, Driver=usbfs, 12M
        |__ Port 1: Dev 4, If 1, Class=Wireless, Driver=, 12M
        |__ Port 2: Dev 5, If 1, Class=Wireless, Driver=, 12M
        |__ Port 2: Dev 5, If 0, Class=Wireless, Driver=usbfs, 12M
/:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-pci/6p, 480M
    |__ Port 1: Dev 2, If 0, Class=Video, Driver=uvcvideo, 480M
    |__ Port 1: Dev 2, If 1, Class=Video, Driver=uvcvideo, 480M
```

これで以下の事が分かる。

* このマシンには Bus 01 と Bus 02 の二つの USB Bus が接続されている。
    * 多分この Bus というのは USB バスコントローラに対応している(要調査?)
* Bus 02 は Port 1 と Port 2 を持つ。
    * 多分この Port というのは USB ポートに対応している(要調査?)
* Bus 02 の Port 2 は Port 1 と Port 2 を持つ。
* Bus 02 の Port 2 の Port 2 には Dev 5 が接続されている。
    * Dev というのはデバイスを抜き差しするたびに変わってゆく Device 番号。Bus ごとに一意に定まる。
* Bus 02 の Dev 5 は If 1 と If 0 を持つ。
* Bus 02 の Dev 5 の If 1 の Class は Wireless である。
    * 多分この Wireless はクラスデバイスの事。

また `lsusb` コマンドでもう少し整理された情報が出る。

```
# lsusb
Bus 002 Device 005: ID 0a12:0001 Cambridge Silicon Radio, Ltd Bluetooth Dongle (HCI mode)
Bus 001 Device 002: ID 0e0f:000b VMware, Inc. 
Bus 002 Device 004: ID 0e0f:0008 VMware, Inc. 
Bus 002 Device 003: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 002: ID 0e0f:0003 VMware, Inc. Virtual Mouse
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
```

`lsusb -t` では現れなかったがデバイスを特定する ID が vendor:product の形で表示される。

これらの Bus や Device を使って表示するデバイスを特定出来る。

    # lsusb -s 002:006 # Bus とDevice が当てはまる情報だけを表示
    # lsusb -D /dev/bus/usb/002/006 # デバイスファイル /dev/bus/usb/(Bus)/(Device) から詳しく表示
    # lsusb -d 0a5c:21e8 # vendor:product が当てはまる情報だけを表示

## sysfs による接続状態の表示

Linux では、sysfs というのでデバイスの状態をファイルシステム経由で見ることが出来る。

### USB デバイスの表示

USB デバイスは /sys/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/ のような感じのややこしい所に現れるが、/sys/bus/usb/devices からリンクされている。

```
# ls -l /sys/bus/usb/devices
total 0
lrwxrwxrwx 1 root root 0 Oct 24 12:21 1-0:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1/1-0:1.0
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-0:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-0:1.0
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-1:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1/2-1:1.0
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-2 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-2.1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-2.1:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-2.1:1.1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.1
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-2.2 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.2
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-2.2:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.2/2-2.2:1.0
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-2.2:1.1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.2/2-2.2:1.1
lrwxrwxrwx 1 root root 0 Oct 24 12:21 2-2:1.0 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2:1.0
lrwxrwxrwx 1 root root 0 Oct 24 12:21 usb1 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:03.0/usb1
lrwxrwxrwx 1 root root 0 Oct 24 12:21 usb2 -> ../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2
```

このうち `:` がついているリンクが実際のデバイスに対応している。書式は `bus-port.port:config.interface` のようになっているらしい。これであるデバイスを制御しているドライバが分かる。以下の例では 2-1:1.0 (Bus 2, Port 1, Config 1, Interface 0) を制御しているドライバは usbhid という事が分かる。

```
# ls -l /sys/bus/usb/devices/2-1\:1.0/driver
lrwxrwxrwx 1 root root 0 Oct 24 12:21 /sys/bus/usb/devices/2-1:1.0/driver -> ../../../../../../../bus/usb/drivers/usbhid
```

### USB ドライバの表示

USB ドライバは /sys/bus/usb/drivers に現れる。

```
# ls /sys/bus/usb/drivers
btusb  cp210x  ftdi_sio  hdm_usb  hub  k2l-mocca  pl2303  usb  usb-storage  usbfs  usbhid  usbserial  usbserial_generic
```

このディレクトリを見ると、あるドライバがどのデバイスを制御しているか分かる。この例では usbhid が `2-1:1.0` すなわち Bus 2, Port 1, Config 1, Interface 0 を制御している。

```
# ls -l /sys/bus/usb/drivers/usbhid/
total 0
lrwxrwxrwx 1 root root    0 Oct 24 12:24 2-1:1.0 -> ../../../../devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-1/2-1:1.0
--w------- 1 root root 4096 Oct 24 12:24 bind
lrwxrwxrwx 1 root root    0 Oct 24 12:24 module -> ../../../../module/usbhid
-rw-r--r-- 1 root root 4096 Oct 24 12:24 new_id
-rw-r--r-- 1 root root 4096 Oct 24 12:24 remove_id
--w------- 1 root root 4096 Oct 24 12:21 uevent
--w------- 1 root root 4096 Oct 24 12:24 unbind
```

## [udev](https://www.freedesktop.org/software/systemd/man/udev.html) によるドライバの自動登録

USB の接続を検知してドライバを割り当てるために udev という仕組みが使われている。udev はカーネルからデバイスイベントを受け取り以下を行う。

* ネットワークインタフェースの名前を変更する
* デバイスノードへの symlink を作成する
* あるプログラムを実行する

udev ルールは以下に従って作成する

* ルールの位置は優先順に以下のどこかに置く。同じ名前のルールがあると優先順序の高いルールだけが有効になる。
    * /etc/udev/rules.d
    * /run/udev/rules.d
    * /lib/udev/rules.d
* ルールのファイル名は必ず .rules で終わる。
* ルールは置かれたディレクトリに関わらず名前でソートされてから読み込む。
* \# はコメント
* 一行に , で区切って複数のマッチや代入を書く。
* マッチには `==` と `!=` がある。一行の全てのマッチが成功すると代入が実行される。
* 代入には `=`, `+=`, `-=`, `:=` がある。

### udev にどんなイベントが来るのかを調べる

`udevadm monitor` で実際にどんなデバイスイベントが来るのかを調べる事が出来る。以下の例では、BT ドングルを刺したイベントを観察している。

```
# udevadm monitor
KERNEL[1607.753652] add      /devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1 (usb)
KERNEL[1607.816654] add      /devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0 (usb)
KERNEL[1607.825478] add      /devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/bluetooth/hci0 (bluetooth)
KERNEL[1607.825506] add      /devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/bluetooth/hci0/rfkill6 (rfkill)
```

最初の行は以下のような意味

* `KERNEL`: KERNEL と UDEV の二種類がある
* `[1607.753652]`: 多分タイムスタンプ
* `add`: action を表す、add, remove, change がある
* `/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1`: devpath を表す。sysfs 中でデバイスを表すパス
* `(usb)`: subsystem を表す、usb, bluetooth など

ルールの中で使う変数は `udevadm monitor -p` で観察出来る。全部見ると量が多くて大変なので、subsystem 等でフィルタすれば良い。

```
# udevadm monitor -p -s bluetooth
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent

KERNEL[2265.779078] remove   /devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/bluetooth/hci0 (bluetooth)
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/bluetooth/hci0
DEVTYPE=host
SEQNUM=4437
SUBSYSTEM=bluetooth

UDEV  [2265.797035] remove   /devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/bluetooth/hci0 (bluetooth)
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.1/2-2.1:1.0/bluetooth/hci0
DEVTYPE=host
SEQNUM=4437
SUBSYSTEM=bluetooth
SYSTEMD_ALIAS=/sys/subsystem/bluetooth/devices/hci0
SYSTEMD_WANTS=bluetooth.target
TAGS=:systemd:
USEC_INITIALIZED=1607841223
```

### [Modalias](https://wiki.archlinux.org/index.php/Modalias)

udev ではデバイスの情報をコンパクトに表現するために、Modalias という物を使う。Modalias は sysfs 経由 で取得出来る。例えば 2-2.2:1.0 (USB Bus 2, Port 2, Port2, Config 1, Interface 0) の Modalias は以下で取得する。

```
# cat /sys/bus/usb/devices/2-2.2\:1.0/modalias 
usb:v0A12p0001d8891dcE0dsc01dp01icE0isc01ip01in00
```

まるで暗号のようだが、Modalias は英小文字を区切りになっている。[drivers/usb/core/sysfs.c](https://git.yoctoproject.org/cgit/cgit.cgi/linux-yocto-4.8/tree/drivers/usb/core/sysfs.c#n966) によると以下のように読める。

* usb: サブタイプを表す
* v0A12 (device vendor: Cambridge Silicon Radio, Ltd)
* p0001 (device product: Bluetooth Dongle (HCI mode)
* d8891 (bcc device:)
* dcE0 (device class: Wireless)
* dsc01 (device subclass: Radio Frequency)
* dp01 (device protocol: Bluetooth)
* icE0 (interface class: Wireless)
* isc01 (interface subclasss: Radio Frequency)
* ip01 (interface protocol: Bluetooth)
* in00 (interface)

この Modalias を人間でも読める情報に分解するために [hwdb](https://www.freedesktop.org/software/systemd/man/hwdb.html) データベースが使われる。hwdb の設定ファイルは例えば /lib/udev/hwdb.d/20-usb-classes.hwdb あたりに入っている。文法は読めば分かるがこの例だと `usb:v*p*d*dcE0dsc01dp01*` の行にマッチして

```
usb:v*p*d*dcE0dsc01dp01*
 ID_USB_PROTOCOL_FROM_DATABASE=Bluetooth
```
属性の一つとして `ID_USB_PROTOCOL_FROM_DATABASE=Bluetooth` が得られる。

[systemd-hwdb](https://www.freedesktop.org/software/systemd/man/systemd-hwdb.html) を使うと hwdb を検索して Modalias から 属性を得られる。

```
# systemd-hwdb query usb:v0A12p0001d8891dcE0dsc01dp01icE0isc01ip01in00
ID_USB_CLASS_FROM_DATABASE=Wireless
ID_USB_SUBCLASS_FROM_DATABASE=Radio Frequency
ID_USB_PROTOCOL_FROM_DATABASE=Bluetooth
ID_VENDOR_FROM_DATABASE=Cambridge Silicon Radio, Ltd
ID_MODEL_FROM_DATABASE=Bluetooth Dongle (HCI mode)
```

### udevadmin による udev のテスト

`udevadmin` を使って udev の動作をテスト出来る。

```
# udevadm test /sys/bus/usb/devices/2-2.2:1.0
...
IMPORT builtin 'hwdb' /lib/udev/rules.d/50-udev-default.rules:15
RUN 'kmod load $env{MODALIAS}' /lib/udev/rules.d/80-drivers.rules:5
created db file '/run/udev/data/+usb:2-2.2:1.0' for '/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.2/2-2.2:1.0'
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:11.0/0000:02:00.0/usb2/2-2/2-2.2/2-2.2:1.0
DEVTYPE=usb_interface
DRIVER=usbfs
ID_MODEL_FROM_DATABASE=Bluetooth Dongle (HCI mode)
ID_USB_CLASS_FROM_DATABASE=Wireless
ID_USB_PROTOCOL_FROM_DATABASE=Bluetooth
ID_USB_SUBCLASS_FROM_DATABASE=Radio Frequency
ID_VENDOR_FROM_DATABASE=Cambridge Silicon Radio, Ltd
INTERFACE=224/1/1
MODALIAS=usb:v0A12p0001d8891dcE0dsc01dp01icE0isc01ip01in00
PRODUCT=a12/1/8891
SUBSYSTEM=usb
TYPE=224/1/1
USEC_INITIALIZED=18792432
run: 'kmod load usb:v0A12p0001d8891dcE0dsc01dp01icE0isc01ip01in00'
Unload module index
Unloaded link configuration context.
```

udev builtin command のテストも出来る。

```
# udevadm test-builtin "kmod load usb:v0A12p0001d8891dcE0dsc01dp01icE0isc01ip01in00" /sys/bus/usb/devices/2-2.2:1.0
calling: test-builtin
=== trie on-disk ===
tool version:          230
file size:         6948766 bytes
header size             80 bytes
strings            1781678 bytes
nodes              5167008 bytes
Load module index
Found container virtualization none
timestamp of '/etc/systemd/network' changed
timestamp of '/lib/systemd/network' changed
Parsed configuration file /lib/systemd/network/99-default.link
Created link configuration context.
Execute 'load' 'usb:v0A12p0001d8891dcE0dsc01dp01icE0isc01ip01in00'
Inserted 'btusb'
Inserted 'btusb'
Unload module index
Unloaded link configuration context.
```

## ドライバの割り当て

ドライバの機能は usb_driver 構造体で定義されている。例えば [btusb.c](https://git.yoctoproject.org/cgit/cgit.cgi/linux-yocto-4.8/tree/drivers/bluetooth/btusb.c#n3170) の定義は以下のようになっている。

```
static struct usb_driver btusb_driver = {
	.name		= "btusb",
	.probe		= btusb_probe,
	.disconnect	= btusb_disconnect,
#ifdef CONFIG_PM
	.suspend	= btusb_suspend,
	.resume		= btusb_resume,
#endif
	.id_table	= btusb_table,
	.supports_autosuspend = 1,
	.disable_hub_initiated_lpm = 1,
};

module_usb_driver(btusb_driver);
```

定義した `usb_driver` は `module_usb_driver` によってシステムに登録する。

重要なメンバ

* name: ドライバの名前
* prove: カーネルがデバイスにドライバを割り当てる時に呼ぶコールバック。成功すれば 0 を返す。
* disconnect: カーネルが切断される時に呼ばれる。

## 参考

* [Q: What are the sysfs structures for Linux USB?](http://www.linux-usb.org/FAQ.html#i6)
* [13.4. Writing a USB Driver](http://www.makelinux.net/ldd3/chp-13-sect-4)
* [What callbacks will usbcore do?](https://www.kernel.org/doc/Documentation/usb/callbacks.txt)
* [the Linux USB Project](http://www.linux-usb.org/)
* [udev](https://www.freedesktop.org/software/systemd/man/udev.html)
* [Rules on how to access information in sysfs](https://www.kernel.org/doc/html/v4.13/admin-guide/sysfs-rules.html)
* [Modalias](https://wiki.archlinux.org/index.php/Modalias)
* [16 Dynamic Kernel Device Management with udev](https://doc.opensuse.org/documentation/leap/reference/html/book.opensuse.reference/cha.udev.html)