デバイスドライバ開発


 Linux用のデバイスドライバ開発は、Windowsのデバイスドライバ開発と
 比較すると、非常に簡単です。キャラクタ型のデバイスドライバならば
 マイクロコンピュータのファームウエアの方が、難しいと思います。


 Linux用のキャラクタ型デバイスドライバの開発は、作法があり、それ
 に従えば、難しくないと考えています。

 自分の場合は、次の3つに分割して開発していきます。
  1. ドライバ登録、削除処理関数
  2. オープン、クローズ処理関数
  3. I/O初期化関数
  4. リード、ライト関数
  5. その他

仕様作成

 どんなデバイスドライバでも、仕様を定めないと作れません。  今回は、ポート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ポートを初期化して利用します。
  1. 必要があれば、プルアップ設定
  2. 方向設定
  3. 必要があれば、初期値設定
 下記のマクロ指定で、プルアップ、制御、データの各アドレスを指定します。  #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

戻る