17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-12-24

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

本連載について

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

本記事に登場するソースコード全体

今回の内容

前回、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に保持します。

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に対して一般ユーザのアクセス権が付与されていないためです。

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

17
12
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?