USBRH driver for Linux を CentOS 5 で使う
いままで USB-RH を利用してきました*1が,ユーザランドのコマンド(http://www.dd.iij4u.or.jp/~briareos/soft/usbrh.html)をバックエンドとして使ってきました。
しかし,定期的に情報収集するのならカーネルモジュールである http://acapulco.dyndns.org/usbrh/ を使いたいものです。
デメリット
- カーネルをアップデートするたびにビルドする必要がある(と思う)
- もしコードに問題があるとシステム全体が落ちる
では,ということで CentOS でビルドしてインストールしてみましたが,うまく動きません。
トラブルシューティングの「USBRH ドライバではなく USB HID ドライバがロードされてしまっている」にバッチリあてはまっています。
dmesg の様子を見てみると,
...... snip snip snip ...... usbcore: registered new driver usbfs usbcore: registered new driver hub ...... snip snip snip ...... usbcore: registered new driver hiddev usbcore: registered new driver usbhid drivers/usb/input/hid-core.c: v2.6:USB HID core driver ...... snip snip snip ...... Initializing USB Mass Storage driver... ...... snip snip snip ...... usb 3-1: configuration #1 chosen from 1 choice hiddev96: USB HID v1.00 Device [Strawberry Linux Co.,Ltd. Hygrometer/Thermometer ] on usb-0000:00:1d.1-1 ...... snip snip snip ...... usbcore: registered new driver usb-storage USB Mass Storage support registered. ...... snip snip snip ...... usbcore: registered new driver usbrh ...... snip snip snip ......
このように,usbrh ドライバがロードされるのは,hiddev や usbhid などのドライバが登録されたあとです。なので USB-RH を usbhid が先に握ってしまい,usbrh ドライバまでまわってこないんですね。
上記トラブルシューティングで usbhid ドライバを一度アンロードしてからロードする,というのは,usb サブクラスドライバのドライバチェーンの順序を変えることで(usbrh のほうが usbhid より先に登録されるようにする)USB-RH が接続されている場合に usbrh ドライバが優先的にアタッチすることを期待している,のでしょう。
しかしながら,CentOS では usbhid ドライバは動的カーネルモジュールとしてではなく,静的にカーネルに組み込まれているため,このようなハックは実行しようがありません。
カーネルをリビルドするか……でもこれだけのためにするのもいやだな……と思いながらカーネルソースを覗いてみる*3と,usb 系のモジュールにはアンバインド・バインド処理を行う関数も登録されています。
これうまく使えるといいなぁ……と思いながらウェブで調べると,その答えとなる手法が載っているページがありました。⇒http://ubuntuforums.org/archive/index.php/t-123061.html
USB のデバイスは /sys/bus/usb/devices/
ディレクトリ以下に列挙されています。
$ ls /sys/bus/usb/devices/ 1-0:1.0 2-2 2-2.1:1.0 2-2:1.0 3-1 4-0:1.0 usb1 usb3 usb5 2-0:1.0 2-2.1 2-2.1:1.1 3-0:1.0 3-1:1.0 5-0:1.0 usb2 usb4
いっぽう,USB サブクラスドライバのインタフェースは /sys/bus/usb/drivers/
以下に存在します。
$ ls -F /sys/bus/usb/drivers/ hiddev/ hub/ usb/ usb-storage/ usbfs/ usbhid/ usbrh/
さらに奥底を覗いてみると……
$ ls -F /sys/bus/usb/drivers/usbhid/ 2-2.1:1.0@ 2-2.1:1.1@ bind new_id unbind
まさに,bind
や unbind
などのあやしげな名前のファイルが存在するではないですか。
で,先ほどのページの内容によると,デバイス番号をこれらの疑似ファイルに書き込んでやると,デバイスのドライバへのバインド・アンバインドができる,そうなのです。
じっさいにやってみましょう。とりあえず,USB-RH の USB デバイス番号が 3-1:1.0
だとします(デバイスを抜き差しして調べてみてください)。
root 権限で,まずは hiddev ドライバからアンバインドしてみます。
# echo -n "3-1:1.0" > /sys/bus/usb/drivers/hiddev/unbind echo: write error: no such device
いきなり怒られてしまいました。CentOS では hiddev ドライバは利用されていないのかな。
では,usbhid ドライバからデバイスをアンバインドしてみます。
# echo -n "3-1:1.0" > /sys/bus/usb/drivers/usbhid/unbind
おお,怒られませんでした。ということは usbhid ドライバから切り離せたのかな。
最後に,いよいよ usbrh ドライバにバインドしてみます。
# echo -n "3-1:1.0" > /sys/bus/usb/drivers/usbrh/bind
怒られなかったので,うまくいったと思いたいです。dmesg みてみます。
/home/dayflower/src/usbrh-0.0.7/src/usbrh.c: USBRH device now attached to /dev/usbrh123
おお,無事 usbrh ドライバが USB-RH デバイスを認識しました。
じゃあ proc インタフェースも存在してるかな……
$ ls -F /proc/usbrh/ 0/ $ ls -F /proc/usbrh/0/ heater humidity led status temperature
でてきてる!
$ cat /proc/usbrh/0/status t:27.67 h:63.30
温湿度もきちんととれてる!
さて,このステップをいかに自動化するか,ですが。
以前も使った udev のしくみを利用します。udev では,デバイスの追加時などに,コマンドを実行することができます。もともと USBRH Linux Driver も modprobe を udev の rules で(/etc/udev/rules.d/10-usbrh.rule
)おこなっていますが,これはまず削除しておきます。
んで,たとえば /etc/udev/rules.d/99-usbrh.rules
などのファイルに次のように書きます。
ACTION=="add", BUS=="usb", SYSFS{idVendor}=="1774", SYSFS{idProduct}=="1001", \ RUN+="usbrh.sh '%b:1.0'"
この RUN
という部分が,コマンドを実行する部分です。
引数で渡している %b:1.0
というのは %b
の部分は $id
と同義で,さきほどの 3-1
のようなもので置換されます。後半の :1.0
を決め打ちにしてしまってますが……ここはどうするのがいいんだろう。複数台 USB-RH を接続するとうまくいかないかもしれません。
そして usbrh.sh
というのが,さきほどのアンバインド・バインドをおこなうスクリプトで,以下の内容のものを /lib/udev/usbrh.sh
という名前*4で置きます。
#!/bin/sh DRIVER_PATH=/sys/bus/usb/drivers /sbin/modprobe -v -s usbrh echo -n "$1" > $DRIVER_PATH/hiddev/unbind 2>/dev/null echo -n "$1" > $DRIVER_PATH/usbhid/unbind 2>/dev/null echo -n "$1" > $DRIVER_PATH/usbrh/bind 2>/dev/null exit 0
これしきの内容であれば普通はわざわざ別スクリプトにする必要もないんですが,udev rules の RUN
で指定されたコマンドは,途中で fail するとその後の処理を断念してしまいます。なので,あえて別スクリプトにして戻り値を 0 (成功)にしているのです。
また,2>/dev/null
で標準出力を omit してますが,これをはずすとエラーが発生した場合に udev が syslog にエラーを吐きます。んで,実は,なぜか,これらの echo
コマンドはすべて,実際には write error: no such device で怒られているのです。ですが,怒られながらも実行すると,きちんと usbrh ドライバにバインドされます。不思議です。
ともかく,これらの仕掛けをして,udevcontrol reload_rules
で udev のルールの再読み込みをさせ,USB-RH を抜き差しすると,以後自動的に usbrh ドライバがバインドされるようになります。接続したまま再起動してもきちんと認識してくれました。
おまけの spec ファイル
# kmod-usbrh.spec Source10: kmodtool %define kmodtool sh %{SOURCE10} %{!?kversion: %define kversion %(uname -r)} # hint: this can be overridden with "--define kversion foo" on rpmbuild, e.g. # --define "kversion 2.6.18-128.el5" %define kmod_name usbrh %define kverrel %(%{kmodtool} verrel %{?kversion} 2>/dev/null) %define upvar "" %ifarch i686 x86_64 ia64 %define xenvar xen %endif %ifarch i686 %define paevar PAE %endif %{!?kvariants: %define kvariants %{?upvar} %{?xenvar} %{?paevar}} # hint: this can be overridden with "--define kvariants foo" on rpmbuild, e.g. # --define 'kvariants "" PAE' Name: %{kmod_name}-kmod Version: 0.0.7 Release: 1%{?dist} Summary: kernel module for USB hygrometer / thermometer USB-RH Group: System Environment/Kernel License: GPL URL: http://acapulco.dyndns.org/usbrh/ Source0: http://acapulco.dyndns.org/usbrh/usbrh-%{version}.tgz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: sed ExclusiveOS: linux ExclusiveArch: %{ix86} x86_64 %description USB-RH is a hygrometer / thermometer connected via USB. # magic hidden here: %{expand:%(%{kmodtool} rpmtemplate_kmp %{kmod_name} %{kverrel} %{kvariants} 2>/dev/null)} # additional files %{_sysconfdir}/udev/rules.d/99-kmod-usbrh.rules %attr(0755,root,root) /lib/udev/usbrh.sh %prep %setup -q -c -T -a 0 sed -i -e "/etc\/udev/d" %{kmod_name}-%{version}/Makefile for kvariant in %{kvariants}; do cp -a %{kmod_name}-%{version} _kmod_build_$kvariant done cd %{kmod_name}-%{version} %build [ -n $RPM_BUILD_ROOT -a "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT mkdir -p %{buildroot} for kvariant in %{kvariants}; do ksrc=%{_usrsrc}/kernels/%{kverrel}${kvariant:+-$kvariant}-%{_target_cpu} pushd _kmod_build_$kvariant make TOPDIR=${ksrc} all %{?_smp_mflags} popd done %install for kvariant in %{kvariants}; do ksrc=%{_usrsrc}/kernels/%{kverrel}${kvariant:+-$kvariant}-%{_target_cpu} pushd _kmod_build_$kvariant make TOPDIR=${ksrc} INSTALL_MOD_PATH=$RPM_BUILD_ROOT INSTALL_MOD_DIR=extra/%{kmod_name} install %{?_smp_mflags} popd done find $RPM_BUILD_ROOT -type f -name \*.ko -exec strip --strip-debug \{\} \; mkdir -p $RPM_BUILD_ROOT/etc/udev/rules.d/ rule=$RPM_BUILD_ROOT/etc/udev/rules.d/99-kmod-%{kmod_name}.rules echo -n 'ACTION=="add", BUS=="usb", ' >$rule echo -n 'SYSFS{idVendor}=="1774", SYSFS{idProduct}=="1001", ' >>$rule echo "RUN+=\"usbrh.sh '%b:1.0'\"" >>$rule mkdir -p $RPM_BUILD_ROOT/lib/udev/ usbrh_sh=$RPM_BUILD_ROOT/lib/udev/usbrh.sh echo '#!/bin/sh' >$usbrh_sh echo 'DRIVER_PATH=/sys/bus/usb/drivers' >>$usbrh_sh echo '/sbin/modprobe -v -s usbrh' >>$usbrh_sh echo 'echo -n "$1" > $DRIVER_PATH/hiddev/unbind 2>/dev/null' >>$usbrh_sh echo 'echo -n "$1" > $DRIVER_PATH/usbhid/unbind 2>/dev/null' >>$usbrh_sh echo 'echo -n "$1" > $DRIVER_PATH/usbrh/bind 2>/dev/null' >>$usbrh_sh echo 'exit 0' >>$usbrh_sh chmod 755 $usbrh_sh %clean [ -n $RPM_BUILD_ROOT -a "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %changelog * Fri Jul 24 2009 dayflower - 0.0.7-1 - Initial release
これ使うには /usr/lib/rpm/redhat/kmodtool
を SOURCES/
ディレクトリにコピーしておく必要があります(おのおの自分の SRPM に含めておく慣例みたい)。
んで,たとえば i686 なら,
$ rpmbuild -bb --target=i686 --define 'kvariants ""' SPEC/kmod-usbrh.spec
のようにするとカーネルモジュール RPM が生成されます。kvariants
の部分を指定しないと,PAE 用カーネルモジュールなどもビルドしようとするので注意。
*1:USB-RH で遊ぶ - daily dayflower, USB-RH で温湿度を収集しグラフ化(collectd & rrdtool) - daily dayflower
*2:じっさいには,USB プロトコルシーケンスのやりとりのほうが時間的に食うのでそんなに差はないかと思います。しかし,ユーザランドのコマンドであれば温湿度を計測するたびに libusb が USB-RH を見つけるなどの手間が必要になります。カーネルモジュールのほうは,一度アクセスするデバイスが probe 時に定まってしまえばその手間はなくなるので,やはり軽い可能性があります。
*3:LXR / The Linux Cross Reference にお世話になりました。すっごい便利!
*4:ここのディレクトリに置くと,絶対パスで書かなくてもアクセスできるのです。もちろん別の場所に置いて絶対パスで指定してもよいですが。