組み込みLinuxデバイスドライバの作り方 (8)

# 8回目: debugfs用インタフェース

# 本連載について
組み込みLinuxのデバイスドライバをカーネルモジュールとして開発するためのHowTo記事です。本記事の内容は全てラズパイ(Raspberry Pi)上で動かせます。

- [1回目: ビルド環境準備と、簡単なカーネルモジュールの作成](https://qiita.com/take-iwiw/items/1fdd2e0faaaa868a2db2)
- [2回目: システムコールハンドラとドライバの登録(静的な方法)](https://qiita.com/take-iwiw/items/580ec7db2e88beeac3de)
- [3回目: システムコールハンドラとドライバの登録(動的な方法)](https://qiita.com/take-iwiw/items/6b02494a3668f79800e6)
- [4回目: read/writeの実装とメモリのお話](https://qiita.com/take-iwiw/items/26d5f7f4894ccc4ce227)
- [5回目: ラズパイ用のGPIOデバドラの実装](https://qiita.com/take-iwiw/items/cd1d7734c8911830386d)
- [6回目: ioctlの実装](https://qiita.com/take-iwiw/items/ade0a73d4c05fc7961d3)
- [7回目: procfs用インタフェース](https://qiita.com/take-iwiw/items/548444999d2dfdc06f46)
- __[8回目: debugfs用インタフェース](https://qiita.com/take-iwiw/items/1ef4b629f9b9bab4d222)  <--------------------- 今回の内容__ 
- [9回目: 他のカーネルモジュールの関数を呼ぶ / GPIO制御関数を使う](https://qiita.com/take-iwiw/items/e92a950a2cf57d1a3ded)
- [10回目: I2Cを使ったデバイスドライバを作る](https://qiita.com/take-iwiw/items/dbc544864f0e9873270a)
- [11回目: デバイスツリーにI2Cデバイスを追加する](https://qiita.com/take-iwiw/items/0d13142863d9ed064e41)
- [12回目: 作成したデバイスドライバを起動時にロードする](https://qiita.com/take-iwiw/items/b9dd02724e83e36dabc3)


### 本記事に登場するソースコード全体
https://github.com/take-iwiw/DeviceDriverLesson/tree/master/08_01

## 今回の内容
前回、procfsを使用してデバイスドライバのデバッグ用インタフェースを作りました。しかし、本来procfsはプロセスに関する情報を配置するところで、デバッグ用に使うことはよくありません。デバッグ用にはdebugfsを使用することが推奨されています。(@rarul さんよりご指摘いただきました。ありがとうございます。)

今回は、前回と同じことを、このdebugfsを使用して実装してみようと思います。具体的には、

`/sys/kernel/debug/MyDevice/prm1`

にアクセスすることで、デバッグ用のパラメータを読んだり書いたりできるようにします。なお、このパスは環境によって変わる可能性があります。




# debugfs用インターフェースの実装 
debugfsインターフェースの作り方は、大きく2種類あります。それぞれの方法について記載しようと思います。

## 方法1: file_operationsを使用する
debugfs用インターフェースを作るには、ドライバがロード(insmod)される所で、`debugfs_create_file()`という関数によって、ファイル名とread/write時に呼んでほしい関数を登録するだけです。登録するときには、`struct file_operations`テーブルにread/writeハンドラ関数を設定します。これは、前回のprocfs、および、通常のデバイスドライバのハンドラ登録と全く同じです。

しかし、これだとdebugfsアクセス用のファイルが、debugfsのルートディレクトリに作成されてしまいます。当然、そこには他のカーネルモジュールのファイルもあるので、ディレクトリで整理します。debugfs用にディレクトリを作るには、`debugfs_create_dir()`を使います。`debugfs_create_dir()`の戻り値(entry)を、先ほどの`debugfs_create_file()`の第3引数に入れてあげることで、作成したディレクトリの下にファイルが作られるようになります。

## 方法2: ヘルパー関数を使用する
デバッグ用にパラメータを読み書きしたいだけなのに、毎回read/write関数を定義するのが面倒な場合があります。read/write操作だけが必要な場合には、ヘルパー関数1つでdebugfsを作ることが出来ます。例えば、32-bitの変数にアクセスする場合には、`debugfs_create_u32()`を使用します(定義はunsigned型でしたが、一応負数の入出力もできました)。16進数で読み書きしたい場合には、`debugfs_create_x32()`にします。他にも、bool型や、レジスタセット(アドレスと数値)、バイナリデータに使えるblob用の関数も用意されています。今回はひとまず、32bitの数値だけ使ってみます。

## 終了処理
カーネルをアンロードするときに、作成したdebugfsファイルを削除する必要があります。削除するためには、`debugfs_remove()`を使用します。これで作成したファイルを削除できます。が、一つ一つ消していくのは面倒です。何より、作成した時のentry情報を覚えておくのが面倒です。そのために、`debugfs_remove_recursive`()が用意されています。この関数に作成したディレクトリのentry (`debugfs_create_dir()`の戻り値) を入れることで、その下のファイルも再帰的に削除してくれます。

## コード
コードは、以下のようになります。デバッグ用のパラメータとして、`debug_prm1`をstaticで持っています。debugfsルートディレクトリの下に作る自分用のディレクトリ名は"MyDevice"にし、entry情報を`debug_entry_dir`に保持します。

```c:myDeviceDriver.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/debugfs.h>

/*** このデバイスに関する情報 ***/
MODULE_LICENSE("Dual BSD/GPL");
#define DRIVER_NAME "MyDevice"				/* /proc/devices等で表示されるデバイス名 */

/* デバッグ用変数 */
struct dentry *debug_entry_dir;		/* debugfsディレクトリのエントリ */
static int debug_prm1;				/* デバッグ用パラメータ(テスト用) */
static int  debug_read_size = 0;	/* 1回のopenでreadするバイト数 */

/* /sys/kernel/debug/MyDevice/debug_prm1にアクセスしたときに呼ばれる関数 */
static int mydevice_debug_open(struct inode *inode, struct file *file)
{
	printk("mydevice_proc_open\n");
	debug_read_size = 4;	// 1回につき4Byte readする
	return 0;
}

/* /sys/kernel/debug/MyDevice/debug_prm1のread時に呼ばれる関数 */
static ssize_t mydevice_debug_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_proc_read\n");

	if (debug_read_size > 0) {
		/* まだ出力すべきデータがあるとき、*/
		/* 保持している整数型の数字(debug_prm1)を文字列で出力する */
		int len;
		len = sprintf(buf, "%d\n", debug_prm1);	// 本当はcopy_to_userすべき
		debug_read_size -= 4;
		return len;
	} else {
		return 0;
	}
}

/* /sys/kernel/debug/MyDevice/debug_prm1のwrite時に呼ばれる関数 */
static ssize_t mydevice_debug_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
	printk("mydevice_proc_write\n");

	/* 入力された文字列を整数型の数字(debug_prm1)として保持する */
	sscanf(buf, "%d", &debug_prm1);			// 本当はcopy_from_userすべき
	return count;	
}

/* debugfs用のハンドラテーブル */
static struct file_operations debug_debug_prm1_fops = {
	.owner = THIS_MODULE,
	.open  = mydevice_debug_open,
	.read  = mydevice_debug_read,
	.write = mydevice_debug_write,
};

/* ロード(insmod)時に呼ばれる関数 */
static int mydevice_init(void)
{
	printk("mydevice_init\n");

	/* debufs用ディレクトリを作成する */
	debug_entry_dir = debugfs_create_dir(DRIVER_NAME, NULL);
	if (debug_entry_dir == NULL) {
		printk(KERN_ERR "debugfs_create_dir\n");
		return -ENOMEM;
	}

	/* 方法1: ハンドラテーブル登録方式 */
	debugfs_create_file("prm1", S_IRUGO | S_IWUGO, debug_entry_dir, NULL, &debug_debug_prm1_fops);

	/* 方法2: ヘルパー関数方式 */
	debugfs_create_u32("_prm1", S_IRUGO | S_IWUGO, debug_entry_dir, &debug_prm1);
	debugfs_create_x32("_prm1_hex", S_IRUGO | S_IWUGO, debug_entry_dir, &debug_prm1);

	return 0;
}

/* アンロード(rmmod)時に呼ばれる関数 */
static void mydevice_exit(void)
{
	printk("mydevice_exit\n");

	/* debufs用を取り除く(子ファイルも自動的に削除される) */
	debugfs_remove_recursive(debug_entry_dir);
}

module_init(mydevice_init);
module_exit(mydevice_exit);
```

方法1だと、エントリ関数やハンドラテーブルを定義したりする必要がありましたが、方法2だと1行で終わります。当然簡単なのは方法2ですが、パラメータ書き換えと同時に何か処理する必要がある場合には方法1にする必要がありそうです。(例えば、i2cで接続されたデバイスのレジスタ(パラメータ)を変えてみる場合など。)


# debugfs経由でパラメータを読み書きしてみる
下記コマンドでビルド、ロードします。

```
make
sudo insmod  MyDeviceModule.ko
sudo ls /sys/kernel/debug/MyDevice
prm1  _prm1  _prm1_hex
```
すると、`/sys/kernel/debug/MyDevice`の下に、`prm1`、`_prm1`、`_prm1_hex`が出来ていることが分かります。`prm1`は方法1での実装で、`_prm1`と`_prm1_hex`は方法2で実装になります。全て同じ変数へのアクセスを行います。(名前はこのように実装したから、prm1となっただけで、特に意味はありません。)

```
sudo bash -c 'echo 12 >  /sys/kernel/debug/MyDevice/prm1'
sudo cat /sys/kernel/debug/MyDevice/prm1
12
sudo cat /sys/kernel/debug/MyDevice/_prm1
12
sudo cat /sys/kernel/debug/MyDevice/_prm1_hex
0x0000000c

sudo bash -c 'echo 13 >  /sys/kernel/debug/MyDevice/_prm1'
sudo bash -c 'echo 0x0f >  /sys/kernel/debug/MyDevice/_prm1_hex'
```

その後、catやechoで読み書きができます。全て同じ変数の値を入出力しているので結果は同じになります。16進数の時には自動的に0xがつくようです。

## アクセス権について
これは、ラズパイだけかもしれません。  
今回、各debugfsファイルを作るときに、アクセス権として`S_IRUGO | S_IWUGO`を指定しました。これによって全ユーザが読み書きできるはずです。しかし、実際には、実行するときに`sudo`をつける必要がありました。これは、親ディレクトリである、`/sys`及び、`/sys/kernel`、`/sys/kernel/debug`に対して一般ユーザのアクセス権が付与されていないためです。

これらのディレクトリのマウントオプションを変えることで、これは変更できると思います(おそらく起動時に実行されるスクリプトのどこかにある、と思う)。が、深くは追っていませんし、変えるべきではないと思われます。