第11章 新規デバイスドライバの追加方法

この章では、ターゲットボードで動作するLinuxカーネルにデバイスドライバを新しく作成する方法と、作成したデバイスドライバをatmark-dist内に追加する方法を説明します。

11.1. Out of Tree コンパイル

Out of Treeコンパイルは、atmark-distに変更を加えることなく手軽に開発できる方法です。atmark-distのビルドシステムを使うため、複雑なMakefileを書く必要もありません。atmark-distのディレクトリ構造を木に見たて、そのディレクトリ外でコンパイルするためにこう呼ばれています。

以下に、仮想の(キャラクタ)デバイスドライバを例に作成方法を説明します。

なお、以下の例では、2.6系のカーネルを使用しています。

11.1.1. 準備

Out of Treeコンパイルでは、atmark-distに含まれるビルドシステムやライブラリ群を使うため、一度ビルドされているatmark-distが必要です。まず、atmark-distがターゲットボード用にコンフィギュレーションかつビルドされていることを確認してください。

11.1.2. ソースコードの用意

次に、開発するデバイスドライバ用のディレクトリをatmark-distのディレクトリ構造の外に用意します。この中には、Makefileと必要なCのソースコードやヘッダファイルを配置します。

[PC ~]$ ls
atmark-dist-[version]
[PC ~]$ mkdir message
[PC ~]$ ls
atmark-dist-[version] message
[PC ~]$ ls message
Makefile  message.c

message.cは、以下を使用します。

/**
 * Character Device Driver Sample:
 * file name: message.c
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>

#define LICENSE     "GPL v2"

#define MSG_LEN     (32)

static int driver_major_no = 0;
static char msg[MSG_LEN] = "Hello, everyone.";
static struct cdev char_dev;

module_param_string(msg, msg, MSG_LEN, 0);

/* デバイスファイルオープン時に実行 */
static int message_open(struct inode *inode, struct file *filp)
{
	pr_debug("message_open\n");
	return 0;
}

/* デバイスファイル読み取り時に実行 */
static int message_read(struct file *filp, char *buff, size_t count, loff_t
		 *pos)
{
	size_t read_size;

	if (count < strlen(msg) - *pos)
		read_size = count;
	else
		read_size = strlen(msg) - *pos;

	pr_debug("message_read: size = %d\n", read_size);

	if (read_size) {
		copy_to_user(buff, &msg[*pos], read_size);

		*pos += read_size;
	}

	return read_size;
}
/* デバイスファイルクローズ時に実行 */
static int message_release(struct inode *inode, struct file *filp)
{
	pr_debug("message_release\n");

	return 0;
}

/* ファイル操作定義構造体 */
static struct file_operations driver_fops = {
	.read = message_read,
	.open = message_open,
	.release = message_release,
};

/* インストール時に実行 */
int init_module(void)
{
	int ret;
	dev_t dev = MKDEV(driver_major_no, 0);

	pr_debug("message: init_module: msg = %s\n", msg);
	/* キャラクタ型ドライバ管理テーブルへ登録 */
	cdev_init(&char_dev, &driver_fops);
	char_dev.owner = THIS_MODULE;
	ret = cdev_add(&char_dev, dev, 1);

	/* 登録エラー */
	if (ret < 0) {
		pr_debug("message: Major no. cannot be assigned.\n");
			return ret;
	}

	/* 最初に登録する場合 */
	if (driver_major_no == 0) { 
		driver_major_no = ret;
		printk("message: Major no. is assigned to %d.\n", ret);
	}
	return 0;
}

/* アンインストール時に実行 */
void cleanup_module(void)
{
	pr_debug("message: cleanup_module\n");

	/* キャラクタ型ドライバ管理テーブルから削除 */
	cdev_del(&char_dev);
}

MODULE_LICENSE(LICENSE);

Makfileは、以下を使用します。

MODULES = message.o                                1

ifneq ($(KERNELRELEASE), )

obj-m := $(MODULES)
#CFLAGS_MODULE += -DDEBUG

else

ROOTDIR ?= ../atmark-dist-[version]                2

ROMFSDIR = $(ROOTDIR)/romfs

include $(ROOTDIR)/.config
include $(ROOTDIR)/config.arch

MAKEARCH = $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE)

LINUXDIR = $(CONFIG_LINUXDIR)
KERNELRELEASE = ${shell make -sC $(ROOTDIR)/$(LINUXDIR) kernelrelease}

all: modules romfs

modules: 
	$(MAKEARCH) -C $(ROOTDIR)/$(LINUXDIR) M=${shell pwd} modules

romfs:
	make -C $(ROOTDIR) INSTALL_MOD_DIR=kernel/drivers/char \
	M=${shell pwd} modules_install
	$(ROOTDIR)/user/busybox/examples/depmod.pl -b \ 
	$(ROMFSDIR)/lib/modules/$(KERNELRELEASE) &> /dev/null

clean:
	-rm -f *.[oas] *.ko *.mod.c .*.d .*.tmp .*.cmd *.symvers
	-rm -rf .tmp_versions

distclean: clean
	-rm -f *~

endif 

このMakefileは、他のデバイスドライバを開発するときにもテンプレートとして使用することができます。環境に合わせて変更する点は、以下の2つです。

1

生成されるモジュールファイル名を指定します。

2

ROOTDIRには、atmark-distディレクトリを指定します。

11.1.3. ビルド

Makefilemessage.cの用意ができたら、message.koをビルドします。ビルドにはmakeコマンドでmoduelsターゲットを使用します。ビルドが完了するとモジュールファイルmessage.koがディレクトリ内に生成されます。

[PC ~/message]$ make modules
  :
[PC ~/message]$ ls
Makefile        message.c   message.mod.c  message.o
Module.symvers  message.ko  message.mod.o

11.1.4. インストール

モジュールファイルをatmark-distromfsディレクトリにインストールするために、makeコマンドでromfsターゲットを指定します。

[PC ~/message]$ make romfs
  :
[PC ~/message]$ ls ../atmark-dist-[version]/romfs/lib/modules/[version]/kernel/drivers
/char
message.ko

11.1.5. imageファイルの作成

make romfsを実行後、atmark-distのディレクトリに移動して、make imageを実行することで、message.koモジュールを含んだターゲットボード用のイメージファイルがimageディレクトリに生成されます。

[PC ~/message]$ cd ../atmark-dist-[version]
[PC ~/uClinux-dist]$ make image
[PC ~/uClinux-dist]$ ls images
linux.bin  linux.bin.gz  romfs.img  romfs.img.gz

imageターゲットについては、「image」を参照してください。

11.2. driversディレクトリへのマージ

作成したデバイスドライバをディストリビューションに含める方法について説明します。デバイスドライバは、linux-2.6.xディレクトリのdriversディレクトリにまとめられています。この下には、デバイスをさらにカテゴリ分けし管理されています。

以下に、先に作成したキャラクタデバイスドライバmessageを例にマージする手順を説明します。

11.2.1. ソースコードの用意

Cのソースコードは、「ソースコードの用意」と同じものを使用します。ソースコード(message.c)は、atmark-dist/linux-2.6.x/drivers/charディレクトリに配置します。

11.2.2. 追加ドライバの設定

atmark-dist/linux-2.6.x/drivers/charディレクトリにあるKconfigMakefileを編集します。具体的な変更箇所は、以下のとおりです。

例11.1 atmark-dist/linux-2.6.x/drivers/char/Kconfigの変更点

--- Kconfig.orig	2006-06-22 15:13:39.000000000 +0900
+++ Kconfig	2007-10-01 18:38:35.000000000 +0900
@@ -24,6 +24,9 @@
 	tristate "Armadillo-220/230/240 Tact Switch driver"
 	depends on ARCH_ARMADILLO2X0
 
+config MESSAGE
+	tristate "Message support"
+
 config VT
 	bool "Virtual terminal" if EMBEDDED
 	select INPUT

例11.2 atmark-dist/linux-2.6.x/drivers/char/Makefileの変更点

--- Makefile.orig	2006-06-14 19:19:35.000000000 +0900
+++ Makefile	2007-10-01 18:22:34.000000000 +0900
@@ -18,6 +18,7 @@
 obj-$(CONFIG_ARMADILLO2X0_GPIO)	+= armadillo2x0_gpio.o
 obj-$(CONFIG_ARMADILLO2X0_LED)	+= armadillo2x0_led.o
 obj-$(CONFIG_ARMADILLO2X0_SW)	+= armadillo2x0_sw.o
+obj-$(CONFIG_MESSAGE)		+= message.o
 
 obj-$(CONFIG_VT)		+= vt_ioctl.o vc_screen.o consolemap.o \
 			consolemap_deftbl.o selection.o keyboard.o

11.2.3. ドライバの選択

make menuconfigなどで追加したドライバが、Character devicesセクションに表示されるか確認します。表示されたmessageドライバを選択し、設定を保存します。

メニューに追加された message

図11.1 メニューに追加された message


11.2.4. ビルド

In Treeコンパイルのビルド方法は「ビルド」と同じです。

11.3. ドライバの動作確認

追加したデバイスドライバの動作を、アプリケーションプログラムを利用して確認します。

動作確認を行うには、事前に、「インストール」または、「ビルド」までを実行している必要があります。

11.3.1. 動作確認用イメージファイルの作成

アプリケーションプログラムは、以下のmodule_test.cを使用します。ビルドの方法については、10章新規アプリケーションの追加方法を参照してください。

/**
* file name: modules_test.c
 */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define DEVNAME "/dev/message"
#define BUFSIZE (64)

int main(void)
{
	int fd;
	int ret;
	int i = 0;
	char buf[BUFSIZE];

	fd = open(DEVNAME, O_RDONLY);
	if (fd < 0) {
		perror("open");
		return -1;
	}

	memset(buf, 0, BUFSIZE);

	do {
		ret = read(fd, buf + i, BUFSIZE - 1 - i);
		if (ret < 0) {
			perror("read");
			return -1;
		}

		i += ret;
	} while (ret && i < (BUFSIZE - 1));

	close(fd);

	printf("msg: %s\n", buf);

	return 0;
}	

11.3.2. 動作確認

まず、お使いの製品のソフトウェアマニュアルを参照して、作成したイメージファイルを製品に書き込みます。その後、追加したデバイスドライバのモジュールをカーネルに登録し[2]、デバイスノードを作成します。

[Target /]# modprobe message
Using /lib/modules/[version]/kernel/driversmessage: Major no. is assigned to 0.
/char/message.ko
[Target /]# mknod /dev/message c 0 0
	
[注記]

デバイスノードの作成は、書き込み可能なディレクトリで行なう必要があります。例では「/dev」以下に 「message」というデバイスノードを作成しますので、 /devが書き込み可能なディレクトリであることを確認してください。

ルートファイルシステムに romfs を使用している場合は、/devに tmpfsをマウントするか、他の書き込み可能な場所にデバイスノードを作成し、プログラム内の DEVNAMEを作成したデバイスノードに合せて変更してください。

アプリケーションプログラムを実行します。以下のように表示されると追加したドライバが動作していることが確認できます。

[Target /]# module_test
msg: Hello, everyone.
[Target /]#


[2] すでにカーネルにドライバが組み込まれている場合、登録を行う必要はありません。