デバイスドライバ開発
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