Device Driver
Back / Up / Next
Device DriverはKinetic 2915 PCIを動かすためのもので、RT-Linuxローダブルモジュールの形をとっている。
RT-Linuxとしての機能を使用している部分はこのDevice Driverで、babarlDAQの心臓部となっている。
Device Driverと言っても汎用性はなく、随時書き直して使用する必要がある。
基本的な部分はライブラリ、雛形を用意してあるので、それをもとに書き直せばよい。
Device Driverは収集したデータを1Block(16KBytes)バッファリングしておき、データが溜まったらTransferがそのデータを
他の構成要素に転送する。
通常RT-Linuxローダブルモジュールと通常のプロセスとの通信にはRT-FIFOと呼ばれる専用のFIFO(First In First Out)を
使用するが、babarlDAQではバッファ用のメモリをあらかじめ確保してRT-FIFOを使わないで通信を行っている。
実現している機能
- Single Read/Write
- Block Transfer (Q-IGNORE/Q-STOP/Q-REPEAT/Q-SEEK)
- DMA Block Transfer (Q-IGNORE/Q-STOP/Q-REPEAT/Q-SEEK)
- LAMによるInterrupt Request (IRQによるもの)
- Pollingによるイベント処理 (50usごとにPCI CardのInterruptをチェック) <-- 追加
Device Driverの動作
Device DriverはCollectorによってロードされる。
モジュールがロードされた瞬間からデータ収集が開始される。
この時点ですでにTransferもCollectorによって起動されており、Transferと通信しつつCAMACからデータを収集する。
データ収集はCollectorによりDevice Driverが取り除かれることで停止し、同時にTransferも終了する。
Interrupt RequestとPolling
イベントを処理する方法としてInterrupt Requestで割り込みをかけるものと、Pollingで監視するものを選択できる。
割り込みを使用する場合は
void intr_handler(void){
/* イベント処理 */
}
.
.
.
int init_module(void){
.
.
rfs_enable_interrupt(); // Enable Interrupt (PCI Card)
request_RTirq(IRQ,intr_handler); // Reqest IRQ
return 0;
}
のように簡潔に記述することができるが、Driverとユーザープロセスとの同期をとるのが難しいため、
Single Bufferしか扱えない。
Pollingで監視する場合は、Pollingの周期毎にCreate ControllerのLAMをチェックすればよいのだが、
K3922 + K2915の組み合わせでのCAMAC Single Functionはえらい時間がかかる。(8us程度)
なのでPCI Cardの割り込みは許可し、Driverではその割り込みをハンドリングしないようにしておいて、
PCI CardのRFSをチェックすることで1us程度でLAMをチェックできる。
Pollingの周期はとりあえず50us周期にしてある。
過去にPentium 90MHz Linux Kernel 2.2.33のマシンでテストしたところ、25us周期まで耐えることができたが、
あまり周期を早くしても、ユーザープロセスが動作できる時間が少なくなるため、50us周期というのが妥当な数字かと思う。
PollingのコーディングはKernel 2.0.*のときはrt_task_make_periodicという関数で行っていたが、2.2.*では興味深いことにpthreadを使う。
void *daq_handler(void *t){
while(1){
pthread_wait_np(); // Wait for next period
.
.
}
}
int init_module(void){
struct sched_param p;
hrtime_t now = gethrtime(); // Get Now Time
.
.
rfs_enable_interrupt(); // Enable Interrupt
pthread_create(&daqtask,NULL,daq_handler,(void *)1); // Create Thread
pthread_make_periodic_np(daqtask,now+100000,50000); // Set period
p.sched_priority = 1; // Set Priority
pthread_setschedparam(daqtask,SCHED_FIFO,&p); // Set Schedule Parameter
return 0;
}
Pollingを使用するとユーザープロセスとの同期をとるのが難しくないため、Double Bufferが使える。
ユーザープロセスが片方のBufferを転送している間にも、データ収集を行うことができるのでパフォーマンスをよくすることができる。
パフォーマンス、安定性、危険度から考えてPollingを使用することをお勧めする。
ただし、コンピュータ1台でデータ収集を行う場合はユーザープロセスにかけられる時間が十分ほしいので、割り込みを使用した方が
効率的かもしれない。
Device DriverとTransferとの通信 (Single Buffer with RT-FIFO)
バッファに使うMemoryを確保するためにLinuxをappend="xxxm"のオプション付きで起動する。
例えば全体でMemoryが128MBあり、2MBの空きを確保するにはappend="126m"を指定する。
Device Driver側からアクセス
#define ADDRESS (126*0x100000)
char *rt_ptr;
rt_ptr = __va(ADDRESS);
とすれば、直接rt_ptrを使ってアクセスできる。
Transfer側からアクセス
#define ADDRESS (126*0x100000)
int fdm;
char *user_ptr;
fdm = open("/dev/mem",O_RDONLY);
user_ptr = (char *)mmap(0,0x4004,PROT_READ,MAP_FILE|MAP_PRIVATE,fdm,ADDRESS);
とすれば、user_ptrを使ってアクセスできる。
バッファが溜まるとDevice DriverはFlagをたて、FIFOハンドラ(RT-Linuxのページ参照)を登録して休止する。
TransferではこのFlagを常に監視しており、Flagがたてば即座にデータを転送し、その後Device DriverのFIFOハンドラを解除する。
こうすることで安定に、高速にデータ転送を行なっている。
Device DriverとTransferとの通信 (Double Buffer without RT-FIFO)
バッファに使うMemoryを確保するためにLinuxをappend="xxxm"のオプション付きで起動するのは
Single Buffer時と同様である。
Device Driver側からアクセス (Single Bufferと同様)
#define ADDRESS (126*0x100000)
char *rt_ptr;
rt_ptr = __va(ADDRESS);
とすれば、直接rt_ptrを使ってアクセスできる。
Transfer側からアクセス
#define ADDRESS (126*0x100000)
int fdm;
char *user_ptr;
fdm = open("/dev/mem",O_RDWR);
user_ptr = (char *)mmap(0,0x8008,PROT_READ|PROT_WRITE,MAP_FILE|MAP_SHARED,fdm,ADDRESS);
とすれば、user_ptrを使ってアクセス(読み書き)できる。
Driver側はバッファが溜まるとそのバッファのFlagをたて次のBufferにデータを格納する。
2つのバッファにFlagがたっている場合はなにもしないで待機をする。
Transfer側は常にDriverがたてるFlagを監視し、Flagがたったら即座にデータ転送をする。
データ転送が終了すればFlagを解除する。
このようにして同期をとり、デッドタイムを少なくしてデータ転送を行うことができる。
Storageの書き込みさえ間に合えば、データ転送によるデッドタイムは生じない。
October 13, 2000
Hidetada Baba <baba@daq.rikkyo.ac.jp>