このページは「24環境」の情報です

第18回 EEPROM デバイスドライバ編(CAT709)

海老原祐太郎
2004/1/12

第16回 デバイスドライバ入門編(CAT709)を発展させ、今回はEEPROMのドライバを実装します。~ CAT709には512byte EEPROM内蔵RTC(時計IC)が実装されています。
わずか512byte ですが用途によっては設定値を保存するなど応用が可能と思われます。

仕様書より

CAT709の仕様書4ページ目より抜粋。
shlinux_tips18_1.gif
RTC9701JE内蔵の512バイトシリアルEEPROM
0x000〜0x100番地ユーザープログラムから任意に使用できます/dev/cat709eeprom1
0x100〜0x1f0番地カーネル起動パラメータ格納領域/dev/cat709eeprom2
0x1f0〜0x1ff番地macアドレス他IPLROMが使用します

コンパイル方法

第16回のcat709port.cに対して追加で書き足しました。minor番号3と4を追加しました。

基本的にはデバイスドライバの開発は開発機で行います。
NFSでCAT709からマウントし、ロードと実行はCAT709で行います。

説明するよりもコードを載せたほうが早いので、コードを載せます。
cat709port.c というファイル名で開発機に保存してください。

/*
 * cat709port.c
 *   for linux-2.4 kernel
 *   written by Y.Ebihara
 *   2004/1/12  Version 1.0
 */

/* Makefile は次のようになる 

# cat709のカーネルを展開してビルドしたディレクトリを指定する
KERNEL=/home/ebihara/cat709-root-dir/usr/src/linux
CFLAGS=-O2 -I${KERNEL}/include
CC=sh3-linux-gcc

all:cat709port.o

clean:
        rm *.o
*/

#define MODULE
#define __KERNEL__

#define DEFAULT_MAJOR_NUM   241
#define DEVNAME             "cat709port"

/* control register */
#define PCCR 0xa4000104
#define PDCR 0xa4000106
#define PLCR 0xa4000114

/* data register */
#define PCDR 0xa4000124
#define PDDR 0xa4000126
#define PJDR 0xa4000130
#define PLDR 0xa4000134

enum miscport_minor{
  DIPSW,   // 0
  LED1,    // 1
  LED2,    // 2
  EEPROM1, // 3
  EEPROM2, // 4
  MINOR_MAX
};

#include <linux/module.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>

// EEPROM 関連
#define EEPROM1_OFFSET 0
#define EEPROM1_SIZE   0x100
#define EEPROM2_OFFSET 0x100
#define EEPROM2_SIZE   0xf0
void rtc9701je_eeprom_write(unsigned char *from, int addr, int len);
void rtc9701je_eeprom_read(unsigned char *to, int addr, int len);
static struct semaphore eeprom_sem;

/* -------------------------------------------------------- */
/* I/O access                                               */
/* 実際のI/O操作を行う関数                                  */
/* -------------------------------------------------------- */

// inb(), outb() はcat709_port_map()によるアドレス変換の対象となる
// 一方、ctrl_outb(), ctrl_inb()はアドレス変換の対象とならない。
// 従って物理番地を直接書きたいときは ctrl_ を使う(速度も速い)
//   unsigned char ctrl_inb(unsigned long 物理番地)
//   ctrl_outb(unsigned char データ,  unsigned long 物理番地) 

static void init_cat709port(){
  // initialize
}


/* LED OUTPUT */
/* led=1 or 2, data= 1 or 0 */
static void cat709_led(int led, int data){
  int x;
  led &= 0x3;
  led <<= 6;
  x=ctrl_inb(PJDR);
  if(data)
    x &= ~led;
  else
    x |= led;
  ctrl_outb(x,PJDR);
}


/* DIPSW INPUT */
static int cat709_dipsw(void){
  int x1,x2;
  x1 = ~ctrl_inb(PJDR) >> 1;
  x2 = x1 >> 1;
  return (x1 & 0x01) | (x2 & 0x0e);
}

/* -------------------------------------------------------- */
/* file operation method                                    */
/* キャラクター型デバイスドライバ                           */
/* -------------------------------------------------------- */ 

// 大雑把に言うと ユーザープロセスはデバイスファイルを「ファイルとみなして」
// open() し read(), write()を行う。
// デバイスメソッドは プロセスから呼び出される『サブルーチン』と思えば
// 大体あっている
// 従って open()やread(), write() は カーネル空間のテキストだが
// 『pidを持ったまま』プロセスのコンテキストとして走行する。


// このドライバをプロセスが open() したときに呼び出される

static int cat709port_open(struct inode *inode, struct file *filp){
  int minor;
  // マイナー番号取得マクロ
  minor=MINOR(inode->i_rdev);
  if(minor > MINOR_MAX){
    printk("<1>cat709port open() error");
    return -ENODEV;
  } 

  // モジュールの『利用度』を1あげる
  MOD_INC_USE_COUNT; 

  // filp->private_data は void* 型の変数で、プロセンス単位(openされる毎)の
  // データは private_data に保存する。
  // *filpは すべてのデバイスメソッドで引数としてやってくるので
  // filp->private_data はすべてのデバイスメソッドから参照することができる。
  // 変数がひとつで足りないときは 構造体の形をあらかじめ定義しておいて
  // open()時にkmalloc() で構造体の実態を作り、ポインタを private_data に格納する。
  // もちろん デバイスをclose() するときに kfree()することを忘れないように
  filp->private_data = (void*)minor;
  return 0;
}

static int cat709port_close(struct inode *inode, struct file *filp){
  // モジュールの『利用度』を1下げる
  MOD_DEC_USE_COUNT;
  return 0;
}

static int cat709port_read(struct file *filp, char *user_buf,size_t  count,loff_t *ppos){
  unsigned char buffer[255];
  int len;
  int data;
  int minor;

  minor=(int)(filp->private_data);
  switch(minor){
  case DIPSW:
    data=cat709_dipsw();
    len=sprintf(buffer,"%d\n",data);
    // copy_to_user(転送先,転送元,バイト長) で
    // ユーザープロセスにデータを返す
    copy_to_user(user_buf,buffer,len);
    return len;  // 転送バイト数を返す
  case EEPROM1:
    if(down_interruptible(&eeprom_sem)){ // セマフォを得られるまで待機
      return -ERESTARTSYS;  // セマフォの取得に失敗した
    }
    if((*ppos) + count > EEPROM1_SIZE)
      count=EEPROM1_SIZE - (*ppos);
    rtc9701je_eeprom_read(buffer,EEPROM1_OFFSET + (*ppos),count);
    copy_to_user(user_buf,buffer,count);
    *ppos += count;
    up(&eeprom_sem); // セマフォの開放
    return count;
  case EEPROM2:
    if(down_interruptible(&eeprom_sem)){ // セマフォを得られるまで待機
      return -ERESTARTSYS;  // セマフォの取得に失敗した
    }
    if((*ppos) + count > EEPROM2_SIZE)
      count=EEPROM2_SIZE - (*ppos);
    rtc9701je_eeprom_read(buffer,EEPROM2_OFFSET + (*ppos),count);
    copy_to_user(user_buf,buffer,count);
    *ppos += count;
    up(&eeprom_sem); // セマフォの開放
    return count;
  }
  return 0;
  // 戻り値は
  //   負:エラー
  //   0 :end-of-file に到達
  //   正:転送したバイト数
  // としなければならない
} 


static int cat709port_write(struct file *filp, const char *user_buf,size_t  count,loff_t *ppos){
  char buffer[256];
  int data;

  switch((int)(filp->private_data)){
  case LED1:
    copy_from_user(buffer,user_buf,1);
    if(buffer[0]=='0')
      cat709_led(1, 0);
    else if(buffer[0]=='1')
      cat709_led(1, 1);
    return count;
  case LED2:
    copy_from_user(buffer,user_buf,1);
    if(buffer[0]=='0')
      cat709_led(2, 0);
    else if(buffer[0]=='1')
      cat709_led(2, 1);
    return count;
  case EEPROM1:
    if(down_interruptible(&eeprom_sem)){ // セマフォを得られるまで待機
      return -ERESTARTSYS;  // セマフォの取得に失敗した
    }
    if((*ppos) + count > EEPROM1_SIZE)
      count=EEPROM1_SIZE - (*ppos);
    copy_from_user(buffer,user_buf,count);
    rtc9701je_eeprom_write(buffer,EEPROM1_OFFSET + (*ppos),count);
    *ppos += count;
    up(&eeprom_sem); // セマフォの開放
    return count;
  case EEPROM2:
    if(down_interruptible(&eeprom_sem)){ // セマフォを得られるまで待機
      return -ERESTARTSYS;  // セマフォの取得に失敗した
    }
    if((*ppos) + count > EEPROM2_SIZE)
      count=EEPROM2_SIZE - (*ppos);
    copy_from_user(buffer,user_buf,count);
    rtc9701je_eeprom_write(buffer,EEPROM2_OFFSET + (*ppos),count);
    *ppos += count;
    up(&eeprom_sem); // セマフォの開放
    return count;
  }
  return count;
  // 戻り値は
  //   負:エラー
  //   0 :end-of-file に到達
  //   正:転送したバイト数
  // としなければならない
  // write()の時は、ユーザープロセスが書き出ししたかったバイト数(count)を
  // 全部返してあげるのでユーザープロセスは大満足する(^-^)
}

// file_operations構造体とはジャンプテーブルやベクターテーブルのようなもので
// デバイスファイルを open() read() write() close() されたときに呼び出す
// 関数一覧である

static struct file_operations cat709port_fops = {
  owner:THIS_MODULE,           // おまじない
  read:cat709port_read,        // read
  write:cat709port_write,      // write
  open:cat709port_open,        // open
  release:cat709port_close     // close
};


/* -------------------------------------------------------- */
/* driver module load / unload */
/* -------------------------------------------------------- */

// モジュールがロードされたとき(insmod されたとき)は
// init_module()が呼ばれる
// 戻り値は
//   負:エラー発生
//   0 :正常終了
// とする。負を返すとモジュールは常駐しない 

static int init_module(void){
  printk("<1>cat709port hello\n"); 

  sema_init(&eeprom_sem,1);  // セマフォ初期化

  // register_chrdev(メジャー番号, デバイス名,
  //                 ファイルオペレーションズ構造体へのポインタ)
  // で、カーネルに対してキャラクタ型デバイスドライバの登録を行う
  if(register_chrdev(DEFAULT_MAJOR_NUM,DEVNAME,&cat709port_fops) != 0){
    printk("<1>cat709port register fail\n");
    return -1;
  }
  // ハードウェア的な初期化
  init_cat709port();
  return 0;
} 

// モジュールがアンロードされたときき (rmmod されたとき)は
// cleanup_module()が呼び出される

static void cleanup_module(void){
  printk("<1>cat709port goodby\n");
  unregister_chrdev(DEFAULT_MAJOR_NUM,DEVNAME);
}

MODULE_LICENSE("GPL");

/*
ちなみにこのドライバの使い方は以下の通り

■ 準備

# rommode rw
# mkdir /lib/modules/2.4.21/kernel/drivers/misc
# cp cat709port.o /lib/modules/2.4.21/kernel/drivers/misc
# depmod -a
# modprobe cat709port

メジャー番号241, マイナー番号0
# mknod /dev/cat709dipsw c 241 0
# chmod 444 /dev/cat709dipsw

メジャー番号241, マイナー番号1
# mknod /dev/cat709led1 c 241 1
# chmod 666 /dev/cat709led1

メジャー番号241, マイナー番号2
# mknod /dev/cat709led2 c 241 2
# chmod 666 /dev/cat709led2

メジャー番号241, マイナー番号3
# mknod /dev/cat709eeprom1 c 241 3
# chmod 666 /dev/cat709eeprom1

メジャー番号241, マイナー番号4
# mknod /dev/cat709eeprom2 c 241 4
# chmod 666 /dev/cat709eeprom2

■ 使い方 

$ cat /dev/cat709dipsw
0〜15が読める 

$ echo 1 > /dev/cat709led1
LED1が点灯する 

$ echo 0 > /dev/cat709led2
LED2が消灯する

$ cat /dev/cat709eeprom1
EEPROM1の領域を表示する

$ echo "hello" > /dev/cat709eeprom1
EEPROM1に書き込みを行う

*/

Makefileは以下のようになります。
KERNEL=行は、CAT709のカーネルを展開し、config し、make したことのあるディレクトリを指定します(超重要)

KERNEL=/home/ebihara/cat709-root-dir/usr/src/linux
CFLAGS=-O2 -I${KERNEL}/include
CC=sh3-linux-gcc 

all:cat709port.o

clean:
        rm *.o      # rmの前にはTABを入れてね

コンパイルは make するだけです。
出来上がった cat709port.o がデバイスドライバになります。
fileコマンドで確かめると Hitachi SH の relocateble(再配置可能) オブジェクトになっていることが分かります。

$ make
$ file cat709port.o
cat709port.o: ELF 32-bit LSB relocatable, Hitachi SH, version 1 (SYSV), not  stripped

デバイスドライバのロードと実行 ~

以下の作業は CAT709 で行います。

ドライバを組み込む
# insmod /NFSマウント/開発機のどこかのディレクトリ/cat709port.o
cat709port hello

組み込まれたか確認する
# lsmod
Module                  Size  Used by    Not tainted
cat709port              1468   0
ds                      6332   0  (unused)
sh3_ss                  3212   3
pcmcia_core            34880   0  [ds sh3_ss]

メジャー番号を調べる
# cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 ttyS
  5 cua
 10 misc
 90 mtd
108 ppp
128 ptm
136 pts
162 raw
204 ttySC
205 cusc
241 cat709port   ←241番に登録された
254 pcmcia

そうしたらデバイスファイルも作ります。
デバイスファイルへのファイル操作が、デバイスドライバへの操作になります。

# rommode rw

メジャー番号241, マイナー番号3
# mknod /dev/cat709eeprom1 c 241 3
# chmod 666 /dev/cat709eeprom1

メジャー番号241, マイナー番号4
# mknod /dev/cat709eeprom2 c 241 4
# chmod 666 /dev/cat709eeprom2

# rommode ro

実際に使ってみます

$ echo "hello" > /dev/cat709eeprom1
EEPROM1に書き込みを行う

$ cat /dev/cat709eeprom1
EEPROM1空間が読める

$ cat /dev/cat709eeprom2
console=ttySC0,115200 root=1f02 ro
カーネル起動パラメータが読めます

$ echo "console=ttySC0,115200 root=1f02 ro " > /dev/cat709_eeprom2
カーネル起動パラメータの変更ができます。

/dev/cat709eeprom1は256バイトのユーザーデータ用領域として使えます。
/dev/cat709eeprom2はカーネルパラメータ格納用ですので、ここを書き換えることで起動パラメータが書き換えられます。
これも便利だと思います。

組 込 み 

ドライバの動作検証ができたのでCAT709に保存します。
出来上がったドライバは、/lib/modules/カーネルバージョン/kernel/drivers/misc/ ディレクトリに保存します。
最初 misc ディレクトリが無いので作っておきます。

以下の作業は CAT709で行います。

# rommode rw
# mkdir /lib/modules/2.4.21/kernel/drivers/misc
# cp cat709port.o /lib/modules/2.4.21/kernel/drivers/misc
# depmod -a
# vi /etc/modules

1行追加する
cat709port


# rommode ro

/etc/modules ファイルにモジュール名を追加しておくと、
起動時に自動的にロードしてくれます。
ただし、モジュール名とファイル名を関連付けるため、
depmod -a を1回実行しておく必要があります(超重要)

詳しくは 

ここで取り上げたのはlinuxデバイスドライバの一番簡単な例です。
勉強するなら オライリー出版 のLINUXデバイスドライバ 第2版 が最適です。左のリンクで購入できます。

Last-modified: 2006-01-19 (木) 16:40:35 (4964d)