デバイスドライバ開発
Linux用のデバイスドライバ開発は、Windowsのデバイスドライバ開発と
比較すると、非常に簡単です。キャラクタ型のデバイスドライバならば
マイクロコンピュータのファームウエアの方が、難しいと思います。
Linux用のキャラクタ型デバイスドライバの開発は、作法があり、それ
に従えば、難しくないと考えています。
自分の場合は、次の3つに分割して開発していきます。
- ドライバ登録、削除処理関数
- オープン、クローズ処理関数
- I/O初期化関数
- リード、ライト関数
- その他
仕様作成
どんなデバイスドライバでも、仕様を定めないと作れません。
今回は、ポートJに接続したスイッチの状態をリードできる
仕様とします。ビットごとに処理したい場合、一度に複数の
ビットをリードして処理したい場合が出てくるので、どちら
も可能とします。
デバイスドライバをローダブルモジュールとして脱着可能と
します。この場合、メジャーとマイナーの番号が必要になり
ます。
メジャー番号は、デバイスの種別を規定してしまうので慎重に
選ばないとなりませんが、テストであれば、数種類の番号の中
のどれかを利用できます。今回は、242を利用します。
マイナー番号は、同じデバイスが複数接続されているときに、
特定のひとつを同定する目的で利用します。
今回は、0〜8と固定しました。
デバイスドライバには、ノードが必要となります。root権限で
次のように指定することにします。
$ mknod /dev/joynone0 c 242 0
$ mknod /dev/joynone1 c 242 1
$ mknod /dev/joyright c 242 2
$ mknod /dev/joyleft c 242 3
$ mknod /dev/joywest c 242 4
$ mknod /dev/joynorth c 242 5
$ mknod /dev/joyeast c 242 6
$ mknod /dev/joysouth c 242 7
$ mknod /dev/joys c 242 8
$ chmod 444 /dev/joynone0
$ chmod 444 /dev/joynone1
$ chmod 444 /dev/joyright
$ chmod 444 /dev/joyleft
$ chmod 444 /dev/joywest
$ chmod 444 /dev/joynorth
$ chmod 444 /dev/joyeast
$ chmod 444 /dev/joysouth
$ chmod 444 /dev/joys
キャラクタ型デバイスドライバの名前を決めます。
joyportとします。
ドライバ登録、削除処理関数
Linuxでは、デバイスドライバをinsmodで登録、rmmodで削除します。
Linuxカーネルは、insmodで次のモジュールを呼び出します。
module_init
このモジュールが、どういう関数に割当たるかを、デイバスドライバ
作成者は決めるのが仕事になります。
今回は、関数joyport_initmoduleとします。
具体的に、何をすればよいかというと、システムコールregister_chrdevで
メジャー番号、デイバス名、ファイル操作関数相当テーブルを登録します。
実際のコードは、以下です。
static int joyport_initmodule(void)
{
printk("<1>joyport welcome. this program was compiled for " MACHINE " \n");
// キャラクタデバイスとして、登録できないときの処理
if ( register_chrdev(DEFAULT_MAJOR_NUM,DEVNAME,&joyport_fops) != 0 ){
printk("<1>joyport register fail\n");
return -1;
}
// ハードウエア初期化
joyport_hw_init();
return 0;
}
Linuxカーネルは、rmmodで次のモジュールを呼び出します。
module_exit
このモジュールが、どういう関数に割当たるかを決めます。
今回は、関数joyport_exitmoduleとします。
システムコールunregister_chrdevでメジャー番号、デイバス名を削除します。
実際のコードは、以下です。
static void joyport_exitmodule(void)
{
printk("<1>joyport good bye !\n");
// キャラクタデバイスを削除
unregister_chrdev(DEFAULT_MAJOR_NUM,DEVNAME);
}
ファイル操作関数相当テーブルは、構造体で次のように指定します。
static struct file_operations joyport_fops = {
owner :THIS_MODULE, // おまじない
read :joyport_read, // read
write :joyport_write, // write
open :joyport_open, // open
release:joyport_close // close
};
この設定で、open、release、read、writeの各関数を利用して
デバイスドライバへのアクセスが可能になります。ここで決定
した名称で、ドライバ内部の関数が呼び出されます。
追記:printkは、カーネル内部で利用するprintfのような関数です。
printfと違い、浮動小数点数値を表示できません。
オープン、クローズ処理関数
構造体定義で、関数名を決めたので、関数本体を定義します。
システムコールopenと内部関数joyport_openを対応させます。
関数joyport_openは、デバイスファイルのマイナー番号を取得し、
利用できるようにします。
static int joyport_open(struct inode *inode, struct file *filp)
{
int minor;
// i-nodeを利用して、デバイスファイルのマイナー番号を取得する
minor = MINOR(inode->i_rdev);
if ( minor > MINOR_MAX ) {
printk("<1>joyport open() error");
return -ENODEV;
}
// 内部ポインタ設定
filp->private_data = (void*)minor;
return 0;
}
システムコールcloseと内部関数joyport_closeを対応させます。
関数joyport_closeは、単純に値を返すだけに留めます。
static int joyport_close(struct inode *inode, struct file *filp)
{
return 0;
}
I/O初期化関数
CAT760が採用しているSH−4は、ルネサスのマイクロコンピュータ
なので、I/Oポートを初期化して利用します。
- 必要があれば、プルアップ設定
- 方向設定
- 必要があれば、初期値設定
下記のマクロ指定で、プルアップ、制御、データの各アドレスを指定します。
#define PJPUPR ((volatile unsigned char *)(0xfe4000a0))
#define PJCR ((volatile unsigned short *)(0xfe400020))
#define PJDR ((volatile unsigned char *)(0xfe400060))
ポートJは、PTJ7<->PTJ1の7ビットで構成です。
PTJ1は、出力のみで利用可能なので、残りの6ビットを信号入力とします。
プルアップは、PTJ7<->PTJ2のみで指定します。
PJPUPRは8ビット、PJCRは16ビットレジスタなので、必要な値を列挙します。
PJPUPR PJCR
PTJ7 -> プルアップ -> 1 -> 1
1
PTJ6 -> プルアップ -> 1 -> 1
1
PTJ5 -> プルアップ -> 1 -> 1
1
PTJ4 -> プルアップ -> 1 -> 1
1
PTJ3 -> プルアップ -> 1 -> 1
1
PTJ2 -> プルアップ -> 1 -> 1
1
PTJ1 -> 何もしない -> 0 -> 0
0
以上で、設定値が判明したので、初期化関数内部で設定します。
ハードウエア初期化の関数joyport_hw_init内部で、初期化します。
void joyport_hw_init(void)
{
*PJPUPR = 0xfc ;
*PJCR = 0xfff0 ;
}
リード、ライト関数
構造体定義で、関数名を決めたので、関数本体を定義します。
システムコールreadと内部関数joyport_readを対応させます。
関数joyport_readでは、ユーザー空間とカーネル空間でデータ交換する
必要があるため、パラメータに、ユーザー空間バッファポインタを必要です。
static int joyport_read(struct file *filp, char *user_buf,size_t count,loff_t *ppos)
{
int minor;
minor = (int)(filp->private_data);
// マイナー番号が、指定範囲内にあれば、内部処理実行
if( NONE1 < minor && minor <= JOYS ) {
unsigned char buffer[255]; // カーネル空間で利用するバッファサイズ
int len,data,dipswno,i,j;
// マイナー番号取得
dipswno = minor - NONE0;
// マイナー番号に対応したデバイスファイルからデータ取得
data = joy_get(dipswno);
// 文字列に変換
len = sprintf(buffer,"%d\n",data);
if ( dipswno == JOYS ) {
len = sprintf(buffer,"%02X\n",data);
}
// カーネル空間からユーザー空間にデータ転送
copy_to_user(user_buf,buffer,len);
return len; // 文字列長指定
}
return 0;
}
システムコールwriteと内部関数joyport_writeを対応させます。
このデバイスドライバは、入力のみを扱うので、関数joyport_writeでは、
マイナー番号を取得して、0を返すだけにします。
static int joyport_write(struct file *filp, const char *user_buf,size_t count,loff_t *ppos)
{
int minor;
minor = (int)(filp->private_data);
return 0 ;
}
その他
ポートJのデータレジスタは、マクロ定義でPJDRとしたので、
このマクロアドレスを利用して、データを入力します。
メジャー番号は、242固定とします。
#define DEFAULT_MAJOR_NUM 242
マイナー番号を、列挙型で定義します。
enum miscport_minor{
NONE0, // 0
NONE1, // 1
RIGHT, // 2
LEFT, // 3
WEST, // 4
NORTH, // 5
EAST, // 6
SOUTH, // 7
JOYS, // 8
MINOR_MAX
};
マイナー番号は、ビットごとに指定しますが、全ビット指定は8にします。
デバイス名も必要となるので、マクロ定義します。
#define DEVNAME "joyport"
上位の関数は、マイナー番号を指定すると、入力値を取得できる方が
便利なので関数の引数にマイナー番号を指定します。
関数joy_get定義
static int joy_get(int no)
{
int d,result;
/* エラー処理 */
if ( no < RIGHT || JOYS < no ) return -1 ;
/* データ入力 */
d = *PJDR ;
/* ビット処理 */
if ( no < JOYS ) {
result = ( d >> no ) & 1 ;
} else {
/* バイト処理 */
result = d & 0xfc ; /* 1111 1100 */
}
return result;
}
全ソースコード
/*
* joyport.c
* for linux-2.6 kernel
* written by K.Ooyu
* 2006/6/27 Version 0.1
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/config.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
#define DEFAULT_MAJOR_NUM 242
#define DEVNAME "joyport"
enum miscport_minor{
NONE0, // 0
NONE1, // 1
RIGHT, // 2
LEFT, // 3
WEST, // 4
NORTH, // 5
EAST, // 6
SOUTH, // 7
JOYS, // 8
MINOR_MAX
};
#define MACHINE "CAT760"
#define PJPUPR ((volatile unsigned char *)(0xfe4000a0))
#define PJCR ((volatile unsigned short *)(0xfe400020))
#define PJDR ((volatile unsigned char *)(0xfe400060))
void joyport_hw_init(void)
{
*PJPUPR = 0xfc ;
*PJCR = 0xfff0 ;
}
static int joy_get(int no)
{
int d,result;
/* 範囲チェック */
if ( no < RIGHT || JOYS < no ) return -1 ;
/* データ入力 */
d = *PJDR ;
/* ビットごとの処理 */
if ( no < JOYS ) {
result = ( d >> no ) & 1 ;
} else {
/* バイト単位で処理 */
result = d & 0xfc ; /* 1111 1100 */
}
return result;
}
static int joyport_open(struct inode *inode, struct file *filp)
{
int minor;
// マイナー番号取得マクロ
minor = MINOR(inode->i_rdev);
if ( minor > MINOR_MAX ) {
printk("<1>joyport open() error");
return -ENODEV;
}
filp->private_data = (void*)minor;
return 0;
}
static int joyport_close(struct inode *inode, struct file *filp)
{
return 0;
}
static int joyport_read(struct file *filp, char *user_buf,size_t count,loff_t *ppos)
{
int minor;
unsigned char buffer[255];
int len,data,dipswno,i,j;
minor = (int)(filp->private_data);
if( NONE1 < minor && minor <= JOYS ) {
dipswno = minor - NONE0;
data = joy_get(dipswno);
// 文字列に変換する
len = sprintf(buffer,"%d\n",data);
if ( dipswno == JOYS ) {
len = sprintf(buffer,"%02X\n",data);
}
// copy_to_user(転送先,転送元,バイト長) で
// ユーザープロセスにデータを返す
copy_to_user(user_buf,buffer,len);
return len; // 転送バイト数を返す
}
return 0;
}
static int joyport_write(struct file *filp, const char *user_buf,size_t count,loff_t *ppos)
{
int minor;
minor = (int)(filp->private_data);
return 0 ;
}
static struct file_operations joyport_fops = {
owner :THIS_MODULE, // おまじない
read :joyport_read, // read
write :joyport_write, // write
open :joyport_open, // open
release:joyport_close // close
};
// モジュールがロードされたとき(insmod されたとき)は
// init_module()が呼ばれる
static int joyport_initmodule(void)
{
printk("<1>joyport welcome. this program was compiled for " MACHINE " \n");
// デバイスドライバ登録
if ( register_chrdev(DEFAULT_MAJOR_NUM,DEVNAME,&joyport_fops) != 0 ){
printk("<1>joyport register fail\n");
return -1;
}
// ハードウェア初期化
joyport_hw_init();
return 0;
}
// モジュールがアンロードされたとき (rmmod されたとき)は
// cleanup_module()が呼び出される
static void joyport_exitmodule(void)
{
printk("<1>joyport good bye !\n");
unregister_chrdev(DEFAULT_MAJOR_NUM,DEVNAME);
}
module_init(joyport_initmodule);
module_exit(joyport_exitmodule);
MODULE_LICENSE("GPL");
コンパイル
コンパイルは、簡単です。
ただし、カーネルが入っているパスを指定する必要があります。
$ make -C <カーネルソースパス> M=`pwd` modules
また、Makefileの中に、以下のように記述しなければなりません。
obj-m := joyport.o
自分の環境では、カーネルソースパスは/root/cat-kernel/linux-2.6.15-cat
なので、以下のようにしています。
$ make -C /root/cat-kernel/linux-2.6.15-cat/ M=`pwd` modules
これでも、タイプするのが面倒なので、シェルスクリプト
を作成しておきます。スクリプト名は、mkitとします。
make -C /root/cat-kernel/linux-2.6.15-cat/ M=`pwd` modules