LinuxデバイスドライバFAQ
DMAはDirect Memory Accessの略で、CPUを介在させないバス転送
(データ転送)の方法です。一方CPUを介在させるデータ転送の
方法をPIO (Programmed I/O)と言い、対比されます。メモリと
CPUは内部のバスで接続されています。またI/O機器を接続する
バスもこの内部バスに接続されます。I/O機器にあるデータを
メモリに読み込むことを考えてみます。PIOでこれを行なう
場合、データは一旦CPUに送られ、それからメモリに書き込まれます。
しかし、DMAの場合、データはI/O機器から直接CPUを通らずに
メモリに書き込まれます。ですから一般にDMAはPIOより
高速な手法になります。
ブロック転送はDMAとは異なる概念です。ブロック転送は
CPUが介在するかどうかは何も言っていません。ある
まとまったデータの転送、程度の意味と理解した方が
いいです。ブロック転送では読み込みにせよ書き込みせよ
誰がその起動を行なうかが重要なポイントです。
マスタ/スレーブという言葉で表現します。
マスタは起動を行なう主体で、スレーブはそのサービスを
受けるものです。
VMEブロック転送はVME規格に基づくブロック転送の
プロトコルです。BLTと呼びます。
規格に従ってはっきりと定義されています。
かいつまんで言うと、データ転送において、
マスタが一旦アドレスストローブ(AS)をアサートすると、
複数回のデータストローブ(DS)をアサートできるもので、
スレーブのデータアクノレッジ(DTACK)もDSに呼応して
複数回、DSをアサートします。
VME64は最近のVME規格です。以前の規格ではBLTは
定義されていたものの、MBLT(Multiple BLT)はVME64に
なってからです。これはアドレスとデータラインを
使って64ビットのデータ転送を行なうものです。
さらに、VME64拡張が規格として決まりつつあります。
この規格の特徴は5列のコネクタを用い、
3V電源をサポートしていることです。
CAMAC規格にEUR4100eがあります。
他にもブランチドライバとかシリアルドライバとか
補助コントローラの規格が
ありますが、省きます。EUR4100eはCAMAC規格として
よく使われているもので、CAMACのクレートのための
規格です。CAMACではブロック転送という概念が
使われています。Qレスポンスの扱いについて触れている
ところで、アドレススキャンモード、リピートモード、
ストップモードを定義しています。例えば、
ストップモードでは、「データのブロックが転送中である間、
モジュールはリードコマンド動作あるいはライトコマンド動作に
対してQ=1を発生しなければならない。またブロック転送の
終了条件が成立した後では、上記のリードコマンド動作あるいは
ライトコマンド動作に対して、モジュールはQ=0を
発生しなければならない。」と決められています。
ただ、注意しなければならないのはこのEUR4100eが
ブロック転送という言葉を使って各モードを定義してはいるが
モジュール自身がこれらのモードで操作されているのか
そうでないのかを知る手段はないということです。
ですから、計算機側のCAMACインターフェースが上記のモードを
サポートし、DMAを使ったブロック転送を行なえるように
しています。
1つの例が、Kinetic社のK2917, VME-CAMACインターフェースです。
これはこのインターフェースがVMEのマスタとして動作します。
一度DMAリードが起動しますと、このインターフェースはCAMACの
リード操作を行ない、モジュールからデータを読み出します。
同時にそのデータはVMEバスを介して計算機上のメモリに
CPUを介さずに送ります。インターフェースにはワードカウントが
あらかじめセットされていますので、K2917にあるCAMACの
ブロック転送のモードをセットするレジスタにQストップモードが
設定してあったら、Q=0になるか、ワードカウントが0になるまで
データ転送を続けます。
通常は、/usr/src/linuxというディレクトリのもとでカーネルやドライバは
コンパイルされますが、
下記の様にまずは自分のディレクトリにskelton.cを持ってきて、コンパイルし、
インストール、デバイスファイルの作成を行うのがいいでしょう。
デバイスファイルは一度作成するだけでいいです。
% gcc -D__KERNEL__ -D MODULE -Wall -c -I/usr/src/linux/include skelton.c
% su
Password:
# insmod skelton.o
# mknod -m 666 /dev/skelton c 32 0
# exit
再度、コンパイルが必要な場合は、下記のようにまたドライバを
リムーブしてから、インストールしなおす必要があります。
% su
Password:
# rmmod skelton
# insmod skelton.o
# exit
デバイスを見つけるためのカーネル関数はpci_find_deviceです。次のような引数になています。
pci_find_device(unsigned int vendor, unsigned int device, const struct pci_dev *from)
* @vendor: PCI vendor id to match, or %PCI_ANY_ID to match all vendor ids
* @device: PCI device id to match, or %PCI_ANY_ID to match all vendor ids
* @from: Previous PCI device found in search, or %NULL for new search.
Iterates through the list of known PCI devices. If a PCI device is found with a
matching @vendor and @device, a pointer to its device structure is returned.
Otherwise, %NULL is returned.
* A new search is initiated by passing %NULL to the @from argument.
* Otherwise if @from is not null, searches continue from that point.
この説明からわかるように、この関数を複数回呼び出すことで、複数の
PCIデバイスを見つけることができます。詳しくは
PIOを用いたPCIデバイス
ドライバを御覧ください。
カーネルのバージョンによってこの周辺は変更になりました。2.4.xでは(それ以前から)
対応するドライバのメソッドはpollメソッドになりました。
pollメソッドの中で処理の終了を待つ方法の1つとして、poll_waitカーネルルーチンを
呼ぶ方法があります。このpoll_wait待ちを起こす方法はwake_upカーネルルーチンを
呼ぶことです。
- /usr/src/linux/include/linux/poll.h:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
- /usr/src/linux/include/linux/sched.h:
#define wake_up(x) __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE,WQ_FLAG_EXCLUSIVE)
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE,WQ_FLAG_EXCLUSIVE)
Linuxには2つの主要なクロック、ハードウエアクロックとシステムタイムがあります。
ハードウエアクロックはCPU上のいかなるプログラムに対し独立に走ります。
このクロックはリアルタイムクロック、RTC、BIOSクロック、CMOSクロックとも
呼ばれます。このクロックはhwclockコマンドに読みだしや設定ができます。
% hwclock -h
hwclock - query and set the hardware clock (RTC)
Usage: hwclock [function] [options...]
Functions:
--help show this help
--show read hardware clock and print result
--set set the rtc to the time given with --date
--hctosys set the system time from the hardware clock
--systohc set the hardware clock to the current system time
--adjust adjust the rtc to account for systematic drift since
the clock was last set or adjusted
--getepoch print out the kernel's hardware clock epoch value
--setepoch set the kernel's hardware clock epoch value to the
value given with --epoch
--version print out the version of hwclock to stdout
Options:
--utc the hardware clock is kept in coordinated universal time
--localtime the hardware clock is kept in local time
--directisa access the ISA bus directly instead of /dev/rtc
--badyear ignore rtc's year because the bios is broken
--date specifies the time to which to set the hardware clock
--epoch=year specifies the year which is the beginning of the
hardware clock's epoch value
% hwclock --show
Fri Mar 23 17:21:44 2001 -0.440942秒
一方、システムタイムはタイマー割り込みによって操作されるカーネル内部の
クロックによって保持される時間です。システムタイムは1970年1月1日、
00:00:00からの秒数です。
ハードウエアクロックはLinuxが走っていない時、時間を保持する役目を
持ちます。Linuxが走り出す時システムタイムはハードウエアクロックの
時間に合わせてセットされます。そして再びハードウエアクロックが
使われることはありません。つまり、システムタイムとハードウエアクロックが
示す値はシステムスタート時は同じですが、その後独立して動作するものだと
思って下さい。
例えばつぎのようにハードウエアクロックとシステムタイムは値が異なることが
十分にあるのです。
% date
Sun Mar 25 17:32:29 JST 2001
% hwclock --show
Sun Mar 25 17:32:24 2001 -0.877934秒
デバイスを見ましょう。
% cat /proc/ioports
0000-001f : dma1
0020-003f : pic1
0040-005f : timer
0060-006f : keyboard
0070-007f : rtc
0080-008f : dma page reg
00a0-00bf : pic2
00c0-00df : dma2
00f0-00ff : fpu
02f8-02ff : serial(auto)
ここにはtimerとrtcがあります。
timerはintel8253タイマーチップというデバイスです。
rtcはMotorolaMC146818A(あるいはDallas DS12887)リアルタイムクロックチップ
というデバイスです。
rtcはデバイス名が/dev/rtcでそのドライバコードが
/usr/src/linux/drivers/char/rtc.cです。
また、/usr/src/linux/include/asm/mc146818rtc.hには
#define CMOS_READ(addr) ({ outb_p((addr),RTC_PORT(0)); inb_p(RTC_PORT(1)); })
#define CMOS_WRITE(val, addr) ({ outb_p((addr),RTC_PORT(0)); outb_p((val),RTC_PORT(1)); })
などが定義されていて、RTCポートから読みだしや書き込みが
できるようになっています。
void __init time_init(void)
{
extern int x86_udelay_tsc;
xtime.tv_sec = get_cmos_time();
xtime.tv_usec = 0;
|
|
}
xtime変数がこの中で初期化されています。get_cmos_time関数は
CMOS_READやCMOS_WRITEマクロを使ってRTCレジスタから時間を読みだします。
この変数はgettimeofday/settimeofdayシステムコールの中で使われています。
(/usr/src/linux/arch/i386/kernel/time.c:do_gettimeofday/do_settimeofday)
この変数は2038年にはオーバーフローしてマイナスの値になってしまいます。
timerはLinuxの時間管理とプロセススケジュールなどに利用されます。
HZマクロ変数は通常100と設定されてます。これは10ミリ秒に1回のタイマー
割り込みを発生させます。
1回のタイマー割り込みでjiffiesは1つ増加します。jiffiesはOSが起動されて
からのクロックティック(clock tick)数です。unsigned long volatileとして
宣言されているので約1.3年(496日)で溢れます。
(/usr/src/linux/kernel/timer.c:unsigned long volatile jiffies;)
このtimer.cはKernel internal timersやkernel timekeepingなどを行なう
コードが入っています。
また、/usr/src/linux/arch/i386/kernel/time.c(PC-specific time handler)
にはタイマー関連のコードが入っています。
この中のタイマー割り込みが発生するとtimer_interruptが呼ばれ、そこで
do_timer_interruptが呼ばれ、さらにそこでdo_timerが呼ばれます。
do_timerは/usr/src/linux/kernel/timer.cにあります。do_timerの処理は
2つに分かれています。1段目の処理は割り込みハンドラとしての処理で、
ここではjiffiesの値に1を加えます。そしてカレントプロセスに対する
処理を行なってから、2段目の処理に入ります。これはボトムハーフ(BH)ハンドラ
(timer_bhルーチン)としての処理で、カレンダーの更新を行ない、
xtime変数に設定します。