なお, 本ページからの一部リンクは直接ディスク上のファイルを参照する ようになっている. カーネルソースを/usr/src/linux に展開してある コンピュータ以外では正常にはリンクされていない.
なお、ここで取り扱っている内容は主として Linux 2.0.x のものであり、 サンプルのドライバも 2.0.x 用のものである。
PCI のプラグアンドプレイでアドレスが自動設定される場合でも /proc/pci を調査することでベースアドレスを知ることができる. ただし, いっしょに示される割り込みは使用できない. (参考)
以上が, およそ必要と思われる機能である. 最低限 open/close が 必要であり, 入出力操作には read,write,ioctl のいずれかは必要である.
例: Linux 2.0.x static struct file_operations aPCIP54_fops = { NULL, /* lseek */ NULL, /* read */ NULL, /* write */ NULL, /* readdir */ aPCIP54_select, /* select */ aPCIP54_ioctl, /* ioctl */ NULL, /* mmap */ aPCIP54_open, /* open */ aPCIP54_close, /* release(close) */ NULL, /* fsync */ NULL, /* fasync */ NULL, /* check_media_change */ NULL, /* revalidate */ }; Linux 2.2.x static struct file_operations aPCIP54_fops = { NULL, /* lseek */ NULL, /* read */ NULL, /* write */ NULL, /* readdir */ aPCIP54_select, /* poll(select) */ aPCIP54_ioctl, /* ioctl */ NULL, /* mmap */ aPCIP54_open, /* open */ NULL, /* flush */ aPCIP54_close, /* release(close) */ NULL, /* fsync */ NULL, /* fasync */ NULL, /* check_media_change */ NULL, /* revalidate */ NULL, /* lock */ };この例では open,close,ioctl,select の機能に対してそれぞれ aPCIP54〜 という関数を割り当てることを意味している. サポートしない場合は NULL を指定する。それぞれについては以下に解説する.
dword を使用することからも分かるように返り値(&data_ulong に ポインタで指定)は32bitである. この数値はI/Oポートアドレスを 示す場合とメモリを示す場合で形式が若干異なる.
request_irq(aPCIP54[i].irq,aPCIP54_interrupt, SA_INTERRUPT|SA_SHIRQ,"aPCIP54", (void *)(APCIP54_IRQ_MAGIC|i))のように request_irq を使用する. 引数は順に, 割り込み番号 (手動設定で既知・PCIなどから取得), 割り込み時実行関数, 割り込みの利用法, 割り込みを登録する名前, 登録を識別するポインタである (sched.h参照). Linux では一つの割り込みを複数のボードで共有する機構がサポート されており, SA_SHIRQ を指定することで共有可能となる. 共有で複数の割り込み実行関数を登録する場合, 最後の識別情報で 割り込み解除時にどれを解除するかを決定するため, うかつにNULLなどは 指定できない. ここでは, 適当な他と重複しないような値を設定している.
実際の割り込み関数は
static void aPCIP54_interrupt(int irq,void *dev_id,struct pt_regs *regs);
と事前に定義されている. 割り込み番号, 識別ポインタ,
およびレジスタの組である. 最後の引数の詳細は不明である
(
/usr/src/linux/include/asm/ptrace.hで定義).
Linux 2.0.x static int aPCIP54_open(struct inode * inode, struct file * file) static void aPCIP54_close(struct inode * inode, struct file * file) Linux 2.2.x static int aPCIP54_open(struct inode * inode, struct file * file) static int aPCIP54_close(struct inode * inode, struct file * file)という形式で定義される。これらを通じて重要な情報がもたらされる。
open 内では必要なデータの初期化などを、close ではそれらの終了処理を 行う。open で領域が確保できないなどの障害がある場合や、そもそも、 同時には1つのアクセスしか認めていない場合などは -EBUSY を 返すことで "Device is busy" の意味を返すことができる。正常時は 0を返す。
最後に、使用中のドライバがはずされてしまうことがないように open で
MOD_INC_USE_COUNT;
を実行する。それに対応して、close では
MOD_DEC_USE_COUNT;
を実行する。これはドライバの参照カウントを増やす/減らすという
動作をするものであり、参照カウントが0の時のみ、rmmod でモジュールを
除去できる。なお、開発時にバグで不整合を起こしても除去できないので
注意が必要である。
Linux 2.0.x static int ml_raw_read(struct inode * inode, struct file * file, char * buf, int count) static int ml_raw_write(struct inode * inode, struct file * file, const char * buf,int count) Linux 2.2.x static int ml_raw_read(struct file * file, char * buf, size_t count, loff_t *) static int ml_raw_write(struct file * file, const char * buf,int count, loff_t *)動作は *buf で指示された領域に対して最大countバイトのデータの 読み込み・書き込みを行うわけだが、直接は書き込めない。buf は ユーザプロセスの動作している空間でのアドレスであり、ドライバの 動作するメモリ空間とは異なるためである。そのため、アクセスには 専用の関数を用いる。
Linux 2.0.x memcpy_tofs(dest(ユーザ), src(カーネル), size); memcpy_fromfs(dest(カーネル), src(ユーザ), size); Linux 2.2.x(asm/uaccess.h) copy_to_user(dest(ユーザ), src(カーネル), size); copy_from_user(dest(カーネル), src(ユーザ), size);これらは memcpy と同様な操作を行うが、 tofs, fromfs という付録が ついている。例として示した、メモリンクドライバの場合、 直接物理アドレス上のメモリ領域とデータを やり取りするため、例として示したソースでは、そのアドレスを 先頭番地と file->f_pos の和で算出している。また、転送前に 領域サイズと現在位置f_posの兼ね合いで転送量を制限している (実際にはこの他にさらに細工がある)。 処理内容によっては一旦、テンポラリのバッファに転送してから、 処理するという手法も有り得、その場合はその先頭ポインタを 渡せば良い。
転送量がごくわずかである場合には
get_user_byte(addr), get_user_word(addr), get_user_long(addr)
put_user_byte(val,addr), put_user_word(val,addr),get_user_long(val,addr)
(Linux 2.2.x: get_user, put_user)
が存在する。これらは 1,2,4バイト単位でやり取りするのに便利であり、
少量であれば、処理速度も速い。
(参考:
/usr/src/linux/include/asm/segment.h)
このメモリンクドライバの場合、cat などで読み出せることを条件に 開発したため、 f_pos によって読み書きした位置を記録し、連続的に アクセスできるようにしたが、毎回固定長のデータをやりとりする 場合など、f_pos を使用せずに、常に先頭から読み書きするように するという手法も考えられる。実際、メモリンクの場合には、 別のコンピュータと、共有メモリの特定番地を介した通信を 行うような用途が主であると考えられ、その場合は各読み書きごとに 常にその番地を基準としたほうがよい場合があるためである。 そのような場合、当然 lseek で毎回その場所に移動するのも手では あるが、手間がかかる。
Linux 2.0.x static int ml_raw_lseek(struct inode * inode, struct file * file, off_t offset, int orig) Linux 2.2.x static int ml_raw_lseek(struct file * file, loff_t offset, int orig)の形式をとる。第3第4引数は lseek の第2第3引数に相当する。 処理内容は orig に指定された 0〜2の数値による基準点 (先頭・現在値・終点)から offset ずれたところに f_pos など 読み書きポイントを移動させることにある。実際には非常に 単純であるが、f_pos の値の範囲をチェックする必要があるだろう。 不正な場合は -EINVAL を返すことで lseek が -1 を返し、 errono に EINVAL が入る。
ioctl をうけるには
Linux 2.0.x static int aPCIP54_ioctl(struct inode * inode,struct file * file, unsigned int iocmd,unsigned long ioarg) Linux 2.2.x static int aPCIP54_ioctl(struct inode * inode,struct file * file, unsigned int iocmd,unsigned long ioarg)形式の関数を作成し、ファイル操作テーブルに登録することである。 ioctl を呼んだ場合の第2引数、あれば第3引数が iocmd, ioarg に入る。 処理内容は当然、ドライバ作製者の決定による。返り値は0と負値であり 負値の場合にはこれまで同様 errno に そのエラーが入る。 このことを利用すると、ボードの存在の有無程度の真偽を返答すれば 良い内容では ioarg などを使用せず、返り値のみで対応できる。
処理として、複数の情報を要したり、値を返す必要がある場合には ioarg 経由で構造体などのポインタを渡すのが良いと考えられる。 この場合、ドライバ側でその情報を利用するには、リード・ライトで 解説した memcpy_fromfsで 取り込み、何らかの値を返すには memcpy_tofs で書き込めば良い。もちろん、構造体の定義は一致させて おく必要がある。
さて、デバイスドライバでこの select を実装するには、最低限、 select を司る関数本体と、休眠状態から起こすための部分と二つ 必要である。select で休眠させる場合には select_wait(); という関数が 休眠中のプロセスを起こすには wake_up_interruptible(); という関数が 存在する。たとえば、aPCI-P54 ドライバの場合は割り込み関数内で wake_up_interruptible(); を使用しているし、他に実験用に開発している ドライバ類には、第1のファイルのアクセスで第2のファイルを 起こすといった機構、また、後述のポーリングによって起こすように しているものもある。とにかく、何らかの方法で起こすようにする。
/usr/src/linux/kernel/sched.cには wake_up_interruptible();のほかにwake_up(); という関数も存在する。 その違いは wake_up させるプロセスの違いのようであるが、 定かではない。ただ、Linux カーネルのドライバ類のソースを 見る限りは前者が使用されているようである。
Linux 2.0.x static int aPCIP54_select(struct inode *inode, struct file *file, int flag, select_table *wait) Linux 2.2.x static int aPCIP54_select(struct file *file, struct_poll_table_struct *) (注:動作未確認)なる形式の関数を定義し、ファイル操作テーブルに記載すればよい。 第3引数は select システムコールを使用する際の、読み込み準備、 書き込み準備、例外の3条件のいずれで問い合わされたかをあらわす数値で /usr/src/linux/include/linux/fs.hで定義された SEL_IN, SEL_OUT, SEL_EX のいずれかが渡される。また第4引数は 休眠状態にするための関数で使用する。
休眠の状態が整ったところで select_wait を実行する。select_wait は 引数として struct wait_queue ** と呼ばれたときに渡された select_table *wait をとる。前者は 「待機状態を 記録する構造体をしめすポインタ」を返すためのようであるが、 詳しいことは不明である。 カーネルソースの解析などにより、各 プロセス用毎に strcut wait_queue * を用意しておき それに & をつけて渡せばいいことが分かっている。これは wake up 時に 必要となる。
割り込みなどの関数では、wake_up_interruptible を呼び出し、 プロセスを実行可能状態にする。引数は select_wait を呼んだ際の 第1引数である。aPCI-P54 ドライバでは見た目若干異なる形式に見えるが、 select_wait の際には単一のファイルをwaitにしているが、割り込み発生時 には、割り込み待機中のすべてのファイルに対して発行しているためである。 注意点としては、FIFO があるような、あとでハードを調べれば確実に データのあるなしが分かるような場合は問題ないが、PIO の割り込みのように 割り込みがあったことがハードから読み取れない場合(読み取れるが、 一般に割り込み受信時にクリアする)、フラグを立て(例では変数waiting)、 select の関数で確実に結果を返せるようにする。そうしなければ、 システムのパフォーマンスに若干の影響を与えるようになる。
割り込みを受信する場合、初期化のところで
前述のように、割り込み関数を登録する。形式は
static void aPCIP54_interrupt(int irq,void *dev_id,struct pt_regs *regs);
となっている。登録時に渡すポインタで dev_id 経由で情報を受け取ることも
可能である。実際の割り込み関数ですべき処理はハードの種類にもよるので
ここでは詳細について触れないが、一般には、ボード固有の割り込み要件の
クリア、情報の保存、実際のデータ処理などが必要であろう。そして、
select で待つような場合は、準備ができた場合に wake_up_interruptible を
呼び、待機の終了をOSに報せる。
割り込み機構を持たない・利用しない場合、定期的にハードのステータスなどを 読み取り、準備を判断する必要がある場合がある。このとき、OS に 備わっているポーリング機能を利用できる。これはタスクのスケジュールにも 利用されているタイマ割り込み(標準100Hz)の発生時に登録されている 関数を呼び出してくれる、というものである。メモリンクドライバには その機能をとりつけてある。
// polling static void mld_poll (unsigned long /*dummy*/); static struct timer_list poll_timer = {NULL, NULL, 0, 0, mld_poll}; static void mld_poll(unsigned long /*dummy*/) { //printk("."); #ifdef ENABLE_VS poll_vs_ml(); // 拡張システムのポーリング関数の呼び出し #endif poll_timer.expires = POLL_CYCLE + jiffies; add_timer (&poll_timer); } init_module内: poll_timer.expires = POLL_CYCLE + jiffies; add_timer (&poll_timer);機構としては周期的なポーリングと言うより、ある時間後に指定の関数を 実行、といったものである。ここでは mld_poll 関数を呼んでいる。 mld_poll の引数はここでは使用していないが、struct timer_list の 第4変数がわたされる。これは1度きりの機構のため、呼び出された 関数内で再設定が必要である。ここで使用されている jiffies は 前述のOSのタイマ割り込み毎に1ずつカウントアップされる数値で、 POLL_CYCLE 後に再度実行されるようにしている。なお、add_timer したままモジュールを切り放すわけには行かないので、終了処理 cleanup_module内で
gcc -c apcip54.c -DLINUX -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -pipe -m486コンパイル時には -DLINUX -O2 が必要。また、最大限、安全性を考え、 警告を出力させる。
例: #define MODULE #define __KERNEL__ #include <linux/kernel.h> #include <linux/errno.h> #include <linux/module.h> #include <linux/mm.h> #include <linux/string.h> #include <linux/malloc.h> #include <linux/fs.h> #include <linux/stddef.h> #include <linux/version.h> #include <asm/segment.h>ほかに、PCI関係を操作するには linux/pci.h、linux/bios32.h が必要であり、 I/Oポートにアクセスするには asm/io.h が必要である。
#define printk printk_null inline void printk_null() { return ; }と記載すると、全 printk を無効にできる。
inline volatile unsigned long long int RDTSC(void) { unsigned int h,l; /* read Pentium cycle counter */ __asm__(".byte 0x0f,0x31" :"=a" (l), "=d" (h)); return ((unsigned long long int)h<<32)|l; }long long は64ビット変数である。経験から得た注意点としては、 これによって処理時間を測定する場合、
unsigned long long st,en; st=RDTSC(); target_function(); en=RDTSC(); printk("%Ld\n",en-st);などと書いた場合、最適化によって st=en となることがある。その場合は GetRDTSC 宣言の最初の inline を落とす。
結果は当然CPUのクロックに依存するので、変換する必要はあるが、 実測した結果、CPUクロックの公称値と実測値には1%以下であるが 微妙な誤差があるようである(測定側での誤差の可能性もあるが)。 なお、400MHz の場合、オーバーフローするには1500年弱かかる (2^64/400000000/365.25/24/60/60).