Raspberry Pi Mouse の /dev/rtswitch の挙動について

## はじめに

Raspberry Pi Mouse というのはRaspberry Piを搭載した車輪型ロボットです。

[Raspberry Pi Mouse | 製品情報 | 株式会社ア－ルティ](https://www.rt-net.jp/products/raspimouse2)

Raspberry Pi Mouse に搭載されているセンサやアクチュエータは Linux ではデバイスファイルとして見えるようになっています。
`/dev/rtswitch{0,1,2}`はこのロボットの三つのタクトスイッチの状態へのアクセスを提供しているようです。

以下の記事で Raspberry Pi Mouse のタクトスイッチの読み取りで困っている人がいたので、この記事を書くことにしました。

[Pythonでデバイスをリアルタイムに読み書きをする方法？](https://qiita.com/ossyaritoori/items/f676f5f9a2932922aec2)

上掲の記事ではプログラムを Python で実装しています。 
この記事を書いた彼が悩んでいたことは、`/dev/rtswitch0` を `open` して一度 `read` するとそれ以降何も値が読み取れないことでした。
なぜこのようなことになっているのか探っていくことにしましょう。


## 公式マニュアルを読む

さて、問題のタクトスイッチの読み取りですが、まずは公式マニュアル
[RaspberryPi Mouse 取扱い説明書 2.0版](http://resources.rt-net.jp/products/RPiM/raspberryPiMouseManual_2016_06_15.pdf)
を見ていくことにしましょう。
マニュアルの6.5節（19ページ以降）にタクトスイッチについての記述があります。
図6-5（マニュアル20ページ）を見ると、`cat` コマンドを使ってタクトスイッチの状態を調べることができるようです。

公式マニュアルから引用すると、タクトスイッチが押されている状態では

```sh
$ cat /dev/rtswitch0
0
```
となって 0 が返ってきて、タクトスイッチが押されていない状態では

```sh
$ 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版](https://www.oreilly.co.jp/books/4873112532/)
を読んでください。

幸いにして Raspberry Pi Mouse のデバイスドライバの[ソースコード](https://github.com/rt-net/RaspberryPiMouse)が公開されているので、これを見ていくことにしましょう。
[`src/drivers/rtmouse.c`](https://github.com/rt-net/RaspberryPiMouse/blob/master/src/drivers/rtmouse.c) というファイルがそのソースコードになります。

このソースコードの1193行目から1196行目に `/dev/rtswitch` の定義があります。

```c:src/dev/drivers/rtmouse.c
/* /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`](https://github.com/rt-net/RaspberryPiMouse/blob/master/src/drivers/rtmouse.c)
の751行目から764行目にありますが、実質的な操作は何もしていません。

次に関数`sw_read`を見ていきます。この関数が本質的です。
この関数の宣言を見ると

```c:src/drivers/rtmouse.c
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`](https://github.com/rt-net/RaspberryPiMouse/blob/master/src/drivers/rtmouse.c)の524-525行目を見ると、

```c:src/drivers/rtmouse.c
if (*f_pos > 0)
	return 0; /* End of file */
```
となっているので、オフセットが 0 より大きい場合には End of File になっていてそれ以上データを読み出しません。
なので、`/dev/rtswitch` を一度読み出したらもうそれ以上は何も読み出せないというのは正しそうに思えます。

~~しかしながら、我々はファイルのオフセットを変更する手段を持っています。それは `lseek` システムコールです。ユーザー側から~~

```c
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`システムコールは[ソースコード](https://github.com/torvalds/linux/blob/master/fs/read_write.c)を読むと、`file_operations`構造体の`llseek`メンバがNULLである場合は、`no_llseek`という`-ESPIPE`を返すだけの関数を呼ぶようです。

## まとめ

以上で公式マニュアルとソースコードを読むことで `/dev/rtswitch`の挙動を理解することができました。