はじめに
Raspberry Pi Mouse というのはRaspberry Piを搭載した車輪型ロボットです。
Raspberry Pi Mouse | 製品情報 | 株式会社ア-ルティ
Raspberry Pi Mouse に搭載されているセンサやアクチュエータは Linux ではデバイスファイルとして見えるようになっています。
/dev/rtswitch{0,1,2}
はこのロボットの三つのタクトスイッチの状態へのアクセスを提供しているようです。
以下の記事で Raspberry Pi Mouse のタクトスイッチの読み取りで困っている人がいたので、この記事を書くことにしました。
上掲の記事ではプログラムを Python で実装しています。
この記事を書いた彼が悩んでいたことは、/dev/rtswitch0
を open
して一度 read
するとそれ以降何も値が読み取れないことでした。
なぜこのようなことになっているのか探っていくことにしましょう。
公式マニュアルを読む
さて、問題のタクトスイッチの読み取りですが、まずは公式マニュアル
RaspberryPi Mouse 取扱い説明書 2.0版
を見ていくことにしましょう。
マニュアルの6.5節(19ページ以降)にタクトスイッチについての記述があります。
図6-5(マニュアル20ページ)を見ると、cat
コマンドを使ってタクトスイッチの状態を調べることができるようです。
公式マニュアルから引用すると、タクトスイッチが押されている状態では
$ cat /dev/rtswitch0
0
となって 0 が返ってきて、タクトスイッチが押されていない状態では
$ cat /dev/rtswitch0
1
となって 1 が返ってくるようです。
ここで cat
の挙動を思い出して見ましょう。cat
コマンドは与えられたファイルから読み出せる限り読み出したデータを標準出力に書き出します。
すると上記の挙動からして /dev/rtswitch{0,1,2}
は一度状態を出力するともうそれ以上は何も出力しないと考えられます。
もし、読み出される度に、つまり read
システムコールが発行される度にタクトスイッチの状態を返すのだとすれば、cat
は無限に /dev/rtswitch{0,1,2}
を読み出し続けいつまでも止まらなくなってしまいます。
従って、/dev/rtstwitch{0,1,2}
はタクトスイッチの状態を読み出す度に open
して read
するのが想定されている使い方ということになります。
ソースコードを読む
ここからはLinuxのデバイスドライバについての知識が必要ですが、この記事は特にそのあたりの知識はなくても読めるかと思います。
詳しく知りたい場合は、オライリーから出版されている
Linuxデバイスドライバ 第3版
を読んでください。
幸いにして Raspberry Pi Mouse のデバイスドライバのソースコードが公開されているので、これを見ていくことにしましょう。
src/drivers/rtmouse.c
というファイルがそのソースコードになります。
このソースコードの1193行目から1196行目に /dev/rtswitch
の定義があります。
/* /dev/rtswitch */
static struct file_operations sw_fops = {
.open = dev_open, .read = sw_read, .release = dev_release,
};
file_operations
構造体は Linux のソースコード linux/fs.h
で定義されている構造体で、デバイスのファイル操作関数を表現します。
/dev/rtswitch
においては open
と read
と release
の三つの操作が定義されています。
まずは open
がどのように実装されているかを見ていきます。
関数dev_open
はsrc/drivers/rtmouse.c
の751行目から764行目にありますが、実質的な操作は何もしていません。
次に関数sw_read
を見ていきます。この関数が本質的です。
この関数の宣言を見ると
static ssize_t sw_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos)
となっています。
引数filep
の型はfile
構造体のポインタで、この構造体にはOSが管理するファイルについての情報が色々と入っています。
引数buf
はユーザーランドにあるメモリバッファです。ここに読み出したデータが書き込まれます。引数count
はシステムコールを呼び出したユーザーが何bytesのデータを欲しがっているかが入っています。
そして、引数f_pos
はファイルのオフセットへのポインタです。
src/drivers/rtmouse.c
の524-525行目を見ると、
if (*f_pos > 0)
return 0; /* End of file */
となっているので、オフセットが 0 より大きい場合には End of File になっていてそれ以上データを読み出しません。
なので、/dev/rtswitch
を一度読み出したらもうそれ以上は何も読み出せないというのは正しそうに思えます。
しかしながら、我々はファイルのオフセットを変更する手段を持っています。それは lseek
システムコールです。ユーザー側から
lseek(fd, 0, SEEK_SET)
とすればオフセットが0に戻って、/dev/rtswitch
からまたタクトスイッチの状態を読み出せるようになるはずだというのがわかります。
追記
実物が手元にないのでlseek
の部分は実際に上手くいくかわからない。
この部分はあまり信用しないでほしい。
ただ、このページ
https://www.oreilly.com/library/view/linux-device-drivers/0596000081/ch03s03.html
を読む限り、
The llseek method is used to change the current read/write position in a file, and the new position is returned as a (positive) return value. The loff_t is a “long offset” and is at least 64 bits wide even on 32-bit platforms. Errors are signaled by a negative return value. If the function is not specified for the driver, a seek relative to end-of-file fails, while other seeks succeed by modifying the position counter in the file structure (described in Section 3.4 later in this chapter).
とあって、file_operations
構造体でllseek
が指定されていない場合でもEOFからのシーク以外は可能らしいので多分大丈夫なはずです。
追記(2019/2/3)
lseek
のくだりは完全に私の勘違いで間違っています。
Linuxのlseek
システムコールはソースコードを読むと、file_operations
構造体のllseek
メンバがNULLである場合は、no_llseek
という-ESPIPE
を返すだけの関数を呼ぶようです。
まとめ
以上で公式マニュアルとソースコードを読むことで /dev/rtswitch
の挙動を理解することができました。