これはなに?
LinuxでUSBのON/OFFを行います。
検証環境
- Ubuntu 21.10
- Kernel 5.16.1 (自前ビルド)
どうして必要なの?
フロントポートのUSBなら物理的に抜き差しも容易ですが裏側のポートは面倒。
たまに認識しなくなった際にまず再接続を行いたい。
方法
端的に言うと以下のコマンドラインで可能。
接続する
echo $BUS_ID | sudo tee /sys/bus/usb/drivers/usb/bind
切断する
echo $BUS_ID | sudo tee /sys/bus/usb/drivers/usb/unbind
BUS IDの確認方法
まず接続デバイスを確認します。
僕の環境では以下のようになっています。
lsusb
Bus 008 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 007 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 006 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 005 Device 003: ID 08bb:2904 Texas Instruments PCM2904 Audio Codec
Bus 005 Device 002: ID 0582:00a3 Roland Corp. EDIROL UA-4FX
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 003: ID 1bcf:2c99 Sunplus Innovation Technology Inc. MiraBox Video Capture
Bus 002 Device 002: ID 8564:4000 Transcend Information, Inc. microSD/SD/CF UHS-II Card Reader [RDF8, RDF9]
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 005: ID 1462:7c94 Micro Star International MYSTIC LIGHT
Bus 001 Device 003: ID 05e3:0608 Genesys Logic, Inc. Hub
Bus 001 Device 006: ID 046d:c069 Logitech, Inc. M-U0007 [Corded Mouse M500]
Bus 001 Device 004: ID 258a:000c HAILUCK CO.,LTD USB KEYBOARD
Bus 001 Device 002: ID 05e3:0608 Genesys Logic, Inc. Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
USB機器にはvendorIDとproductIDが振られており、 lsusb
を実行した際も十六進法で XXXX:XXXX
という表記で機器名の左に表示されています。
このvendorIDとproductIDを控えておき、 dmesg
のログからどう認識されたか確認します。
今回は 08bb:2904
を見てみましょう。
ちなみにこれはベースエフェクターのB2.1uです。
(Ubuntu) [sakura@x2 ~]$ sudo dmesg | grep 08bb | grep 2904
[ 1.389766] usb 5-2: New USB device found, idVendor=08bb, idProduct=2904, bcdDevice= 1.00
usb 5-2: 〜
となっており、この 5-2
の部分がBUS_IDとなります。
試してみる
まず現状を確認してみます。
B2.1uはサウンドデバイスとして認識されているので /proc/asound/
を覗きます。
(Ubuntu) [sakura@x2 ~]$ ll /proc/asound/B21U
lrwxrwxrwx 1 root root 5 1月 18 13:03 /proc/asound/B21U -> card5
デバイスが認識されているようです。
それでは外してみましょう。
(Ubuntu) [sakura@x2 ~]$ echo 5-2 | sudo tee /sys/bus/usb/drivers/usb/unbind
5-2
(Ubuntu) [sakura@x2 ~]$ ll /proc/asound/B21U
ls: '/proc/asound/B21U' にアクセスできません: そのようなファイルやディレクトリはありません
無事に切断されました。
B2.1uのLEDも消えております。
このまま再度繋いでみましょう。
(Ubuntu) [sakura@x2 ~]$ echo 5-2 | sudo tee /sys/bus/usb/drivers/usb/bind
5-2
(Ubuntu) [sakura@x2 ~]$ ll /proc/asound/B21U
lrwxrwxrwx 1 root root 5 1月 18 13:08 /proc/asound/B21U -> card5
再び認識されました。
これでベースの演奏を録音できます。
スクリプトにまとめる
import 'dart:io';
int main(List<String> arguments) {
// 引数なければlsusbの結果を表示する
if (arguments.length < 1) {
usage();
return 1;
}
final action = arguments[0];
if (action == 'help') {
help();
} else if (action == 'lsusb') {
printLsUsb();
} else if (action == 'bind' || action == 'unbind') {
if (arguments.length < 2) {
help();
return 1;
}
final subArgs = arguments.sublist(1);
if (action == 'bind') {
bind(subArgs);
} else if (action == 'unbind') {
unbind(subArgs);
}
}
return 0;
}
void usage() {
print('check the help. type the following');
print(' usbdevctl help');
print('');
printLsUsb();
}
void help() {
print('usage: usbdevctl COMMAND [ARGS1 ...]');
print('');
print('suppored command list');
print(' help: this message');
print(' lsusb: show results `lsusb`');
print(' bind: connects the device with the vendorID:productID specified in the arguments');
print(' usbdevctl bind XXXX:xxxx [YYYY:yyyy]');
print(' unbind: disconnects the device with the vendorID:productID specified in the arguments');
print(' usbdevctl unbind XXXX:xxxx [YYYY:yyyy]');
}
void printLsUsb() async {
print((await Process.run('lsusb', [])).stdout);
}
void bind(List<String> ids) async {
final busIds = await retrieveUsbBusId(ids);
if (busIds == null) {
print('device not found');
return;
}
for (var busId in busIds) {
bindCommand('bind', busId);
}
}
void unbind(List<String> ids) async {
final busIds = await retrieveUsbBusId(ids);
if (busIds == null) {
print('device not found');
return;
}
for (var busId in busIds) {
bindCommand('unbind', busId);
}
}
void bindCommand(String type, String busId) async {
// echo "$busId" | sudo tee /sys/bus/usb/drivers/usb/unbind
final echo = await Process.start('echo', [busId]);
final sudoTee = await Process.start('sudo', ['tee', '/sys/bus/usb/drivers/usb/${type}']);
echo.stdout.pipe(sudoTee.stdin);
}
Future<List<String>?> retrieveUsbBusId(List<String> ids) async {
var result = <String>[];
for (var id in ids) {
final vid_pid = id.split(':');
if (vid_pid.length != 2) {
print('invalid id string: ${id}');
continue;
}
final vid = vid_pid[0];
final pid = vid_pid[1];
final busId = await retrieveUsbBusIdFromVidAndPid(vid, pid);
if (busId == null) {
print('perhaps a dvice that is not plugged in: ${id}');
continue;
}
if (result.contains(busId)) {
continue;
}
result.add(busId);
}
if (result.length < 1) {
return null;
}
return result;
}
Future<String?> retrieveUsbBusIdFromVidAndPid(String vid, String pid) async {
final baseDir = '/sys/devices/';
final vidResult = await Process.run('grep', ['-lrs', vid, baseDir]);
if (vidResult.stdout == "") {
return null;
}
final exp = new RegExp(r'^.*/usb[0-9]+/([^/]+)/id(Vendor|Product)$');
// vendorIDにひっかかったBUS ID一覧を作る
var vidMatchBusId = <String>[];
for (var vidValue in vidResult.stdout.split('\n')) {
final match = exp.firstMatch(vidValue);
if (match == null) {
continue;
}
final vidBusId = match.group(1);
if (vidBusId == null || vidMatchBusId.contains(vidBusId)) {
continue;
}
vidMatchBusId.add(vidBusId);
}
final pidResult = await Process.run('grep', ["-lrs", pid, baseDir]);
if (pidResult.stdout == "") {
return null;
}
// productIDでひっかかったBUS IDがvendorIDでひっかかったBUS IDにもあれば指定したvendorID:productIDのデバイスが接続されているBUS IDなので返す
// 同じvendorID:productIDのデバイスを複数繋ぐ可能性はあるが現状そのような状況にならないので考えないでおく
for (var pidValue in pidResult.stdout.split('\n')) {
if (!exp.hasMatch(pidValue)) {
continue;
}
final match = exp.firstMatch(pidValue);
if (match == null) {
continue;
}
final pidBusId = match.group(1);
if (pidBusId == null) {
continue;
}
if (vidMatchBusId.contains(pidBusId)) {
return pidBusId;
}
}
return null;
}
大体サブプロセスでシェルコマンド実行してるだけやんけ!
使い方
(Ubuntu) [sakura@x2 ~]$ usbdevctl unbind 08bb:2904
(Ubuntu) [sakura@x2 ~]$ ll /proc/asound/B21U
ls: '/proc/asound/B21U' にアクセスできません: そのようなファイルやディレクトリはありません
(Ubuntu) [sakura@x2 ~]$ usbdevctl bind 08bb:2904
(Ubuntu) [sakura@x2 ~]$ ll /proc/asound/B21U
lrwxrwxrwx 1 root root 5 1月 18 13:36 /proc/asound/B21U -> card5
usbdevctlだけ打つとlsusbの結果を返すのでとりあえずvendorIDとproductIDは拾いやすいはず。
今後の検討課題
- リスト表示から番号選択してON/OFFするようなプログラムにしたい
それでは良いLinuxライフを。