vmedrv は,Linux 上で SBS Technologies (Bit3) 社製 PCI-VME アダプタ (Model 616/617/618/620) を使用するためのデバイスドライバです. 完全なカーネルモードで動作するローダブルモジュールで,プログラムI/O,メモリマップドアクセス(mmap()),VME 割り込み,DMA 転送など,VME アクセスに使われる全ての機能が使用可能です. 64bit 環境でも動作確認しています.
作成には充分注意していますが,間違い等が含まれている可能性があります. 動作の保証はできませんので,御了承下さい. なお,ライセンスは "LGPL Version 2.1" とします. LGPL2.1 の詳細については,配布パッケージに含まれている COPYING-LGPL2.1 ファイル を参照してください.
vmedrv は,分散型汎用オンライン環境の構築を目指す KiNOKO プロジェクトの一部として作成されました. KiNOKO プロジェクトの詳細については, KiNOKO ホームページ を御覧下さい.
OS | PIO | mmap() | 割り込み | DMA | Non-Block DMA | poll() | 利用可能モデル |
Linux 2.0.x | read()のみ | ○ | ○ | read()のみ | - | - | SBS 617 |
Linux 2.2.x | ○ | ○ | ○ | ○ | - | - | SBS 617/618/620/620-3 |
Linux 2.4.x | ○ | ○ | ○ | ○ | - | - | SBS 616/617/618/620/620-3 |
Linux 2.6.x Linux 3.x | ○ | ○ | ○ | ○ | ○ | ○ | SBS 616/617/618/620/620-3 |
動作を確認した Linux ディストリビューションは,以下のとおりです.なお,パッケージのアップデートでカーネルのバージョンが変わったときは,ドライバの再コンパイルが必要になります.
また,トラブルに関する質問の際は,使用している Linux ディストリビューションの種類・バージョンやデバイス構成・型番,エラーメッセージや dmesg コマンドの出力など,なるべく詳細な情報を記述してください.
つまり,他にコントローラがあって,割り込みの取得を行なわないなら,そのまま使えるということです.
アダプタを VME のコントローラとして使うためには,SYS ブロックにある以下のジャンパを設定する必要があります.
アダプタをコントローラとして使う場合は,アダプタをバスアービタとして機能させるために,BGI-BGO ブロックの横にある以下のジャンパも設定しなければなりません.
vmedrv で VME の割り込みを取得したい場合,VME の割り込みを PCI に伝達させるため,T-INT ブロックにある以下のジャンパを接続します.
以下にこれらのジャンパの位置を示します.基板上にジャンパ番号のシルク印刷があります.モデルにより部品の配置が若干異なりますが,ジャンパの位置は基本的に同じです.詳細は製品マニュアルを参照してください.
アダプタをコントローラとして使う場合,アダプタはクレートのいちばん左のスロットで使わなければなりません. また,一部の高級なクレートを除いて,モジュールのささっていない全てのスロットに対し,バックプレーンのジャンパ (BG[0-3]-IN/BG[0-3]-OUT, IACK-IN/IACK-OUT) を接続しなければならないことに注意してください. 逆に,割り込みを発行するモジュールの裏の IACK-IN/IACK-OUT のジャンパをはずすことを忘れないように特に注意してください.
バージョン番号の部分は使用している vmedrv のバージョン,Linux のバージョンに置き換えてください.Model 616/618/620 でも 617 と同じコードを使用します.% gunzip vmedrv-1.0.0.tar.gz % tar xvf vmedrv-1.0.0.tar % cd vmedrv/Linux2.6_Bit3_617
% cat /proc/ksyms | grep kmalloc c012e880 kmalloc_R93d4cfe6 <-- MODVERSION が使われている % cat /proc/ksyms | grep kmalloc c012e880 kmalloc <-- MODVERSION が使われていない
% make
% su # make install # dmesg いろいろなメッセージ vmedrv: Bit3 617 Bus Adapter was detected at ioport 0xdcc0 on irq 10. I/O Mapped Node at 0xdcc0. Memory Mapped Node at 0xfe010000. Mapping Register at 0xfe000000. Remote Memory at 0xf8000000. vmedrv: successfully installed at 0xdcc0 on irq 10 (major = 127). #
リブート時に,自動でドライバを組み込む方法については,KiNOKO-DAQ Technical Tips を参照してください.Linux kernel 2.4 以前用の vmrdrv では,ドライバインストール時に VME クレートの電源が入っている必要があることに注意してください.# make install
# make uninstall # dmesg いろいろなメッセージ vmedrv: removed. #
作者が動作確認をしたバージョンよりも新しいバージョンの Linux を使用した場合,コンパイルやインストールに失敗するかもしれません. その場合は,可能な限り対処しますので,上記のフィードバックのリンクより開発者に連絡をください.
もしかしたら古いシステムでは lseek64() がコンパイルエラーになるかもしれません.その場合は lseek64() を lseek() に置き換えてコンパイルしなおしてみてください.% cd .. % 少し書き換え % make
以下は,用意されているテストプログラムの一覧です.引数なしで実行すると簡単な使用方法を表示します.
vmeread.c / vmewrite.c Programmed I/O のテストプログラム vmemap.c メモリマップトアクセスのテストプログラム vmedmaread.c / vmedmawrite.c DMA転送のテストプログラム vmeint.c / vmeintwait.c / vmeintcheck.c 割り込みのテストプログラム vmepoll.c ポーリング (poll()/select() システムコール) のテストプログラム
vmeread.c,vmewrite.c,vmemap.c,vmedmaread.c,vmedmawrite.c の実行には, VMEのメモリボード(またはそのようにアクセスできるモジュール)が必要です. また,これらのプログラムのディフォルトの転送モードはアドレス 32bit,データ 32bit です.転送モードを変更する場合には,プログラムの先頭付近にある以下の部分を変更して,再コンパイルしてください.
#define DEV_FILE "/dev/vmedrv32d32"
vmeint.c, vmeintwait.c, vmeintcheck.c, vmepoll.c を実行するには,VME割り込みを発行できるモジュールが必要です.また,それらのモジュールのレジスタを適切に設定して割り込みを発生させるコードも書かなければなりません. テストプログラム中では,"test-interrupter-XXX.c" を #include し,その中で定義されている以下の関数を使用しています.テストプログラムでは林栄精器の RPV-130 を使用していますが,それ以外のモジュールを使用する場合はこれらの関数を置き換えてコンパイルしなおす必要があります.
test_interrupter_enable() test_interrupter_disable() test_interrupter_clear()
vmedrv は,KiNOKO から利用すると便利ですが,ドライバに直接アクセスして使うこともできます.上記の動作確認に用いたプログラムは,ドライバに直接アクセスする方法を示すサンプルとなっています (サンプルとして読みやすいように書いたつもりです).
配布パッケージに含まれている vmeslib は,KEK vmelib と類似のインターフェースを実装したものです.vmelib を使っていた場合には,比較的容易に移行できます.ライブラリファイルとテストプログラムは配布パッケージの中の vmeslib にあります.
従来の vmelib と完全互換なライブラリも用意しましたが,DMA や割り込み処理での複数のデバイスのオープンに問題があるため,新しいライブラリの使用を推奨しています(新しいライブラリも KEK との共同開発です).従来の vmelib は,配布パッケージ中の KEK-vmelib にあります.
以下は,ドライバに直接アクセスして使用するためのインターフェースです.
vmedrv をインストールすると,以下のエントリが /devに作成されます. これらは全て同じものですが,デフォルトの転送モードが違います. 転送モードは,オープンした後でも,ioctl()システムコールによりいつでも変更できます.int fd = open("/dev/vmedrv32d32", O_RDWR);
エントリファイル名 デフォルト転送モード /dev/vmedrv なし /dev/vmedrv16d16 アドレス 16bit,データ 16bit,PIO 転送 /dev/vmedrv16d32 アドレス 16bit,データ 32bit,PIO 転送 /dev/vmedrv24d16 アドレス 24bit,データ 16bit,PIO 転送 /dev/vmedrv24d32 アドレス 24bit,データ 32bit,PIO 転送 /dev/vmedrv32d16 アドレス 32bit,データ 16bit,PIO 転送 /dev/vmedrv32d32 アドレス 32bit,データ 32bit,PIO 転送 /dev/vmedrv24d16dma アドレス 24bit,データ 16bit,DMA 転送 /dev/vmedrv24d32dma アドレス 24bit,データ 32bit,DMA 転送 /dev/vmedrv32d16dma アドレス 32bit,データ 16bit,DMA 転送 /dev/vmedrv32d32dma アドレス 32bit,データ 32bit,DMA 転送 /dev/vmedrv24d16nbdma アドレス 24bit,データ 16bit,Non-block DMA 転送 /dev/vmedrv24d32nbdma アドレス 24bit,データ 32bit,Non-block DMA 転送 /dev/vmedrv32d16nbdma アドレス 32bit,データ 16bit,Non-block DMA 転送 /dev/vmedrv32d32nbdma アドレス 32bit,データ 32bit,Non-block DMA 転送
DMA転送モードでも,メモリマップトアクセスは,PIO転送になります.
ここでのデータ幅指定は read() および write() でのみ使用されます.メモリマップトアクセスでは,ここでの指定にかかわらず,アクセスするポインタの型によって,D08/D16/D32 の全ての転送幅を混在して使用できます.
戻り値は,ドライバのファイルディスクリプタです. オープンに失敗した場合は,負の値が返され,大域変数 errno が設定されます. 詳しくは,マニュアルページ open(2) を参照してください.
ここで,offset は VME 上のアドレスです.第3引数(whence) に指定できるのは,SEEK_SET および SEEK_CUR のみです.VME のアドレスで 0x80000000 以上にアクセスする場合は lseek64() を使用する必要があります(0x80000000 以下ではどちらも同じです.lseek64() は,比較的最近導入されたので,環境依存性があるかもしれません). 戻り値は,シーク後の新しいオフセットです. シークに失敗した場合は,負の値が返され,大域変数 errno が設定されます. 詳しくは,マニュアルページ lseek(2) または lseek64(3) を参照してください.lseek(fd, offset, SEEK_SET); lseek64(fd, offset, SEEK_SET);
戻り値は,読み出しに成功したサイズです. 読み出しに失敗した場合は,負の値が返され,大域変数 errno が設定されます. 詳しくは,マニュアルページ read(2) を参照してください.int size = read(fd, buffer, max_size);
戻り値は,書き込みに成功したサイズです. 書き込みに失敗した場合は,負の値が返され,大域変数 errno が設定されます. 詳しくは,マニュアルページ write(2) を参照してください.int size = write(fd, buffer, size);
ここで,offset は VME のアドレス,length は マッピングを行う領域のサイズです.offset は,MMU のページサイズの整数倍でなければなりません.半端なアドレスからマップしたい場合は,先頭アドレスをページサイズで切り捨てて,返されたアドレスに切り捨てたぶんを足して使用してください.この処理を行うプログラムの例を以下に示します.void* start = mmap(0, length, prot, flags, fd, offset);
#include <asm/page.h> map_offset = vme_address & PAGE_MASK; /* 中途半端な部分を切り捨てる */ page_offset = vme_address - map_offset; /* 切り捨てたサイズ */ map_length = length + page_offset; /* 切り捨てた分余分にマップする */ start = mmap(0, map_length, PROT_WRITE, MAP_SHARED, fd, map_offset); if (start == MAP_FAILED) { perror("ERROR: mmap()"); exit(EXIT_FAILURE); } mapped_address = start + page_offset; /* 切り捨てた分を足す */
386 以降の x86 シリーズ CPU では,MMU ページサイズは 4kB になっています.それ以外の CPU については,$(KERNEL_INCLUDE_DIR)/asm/page.h 中の PAGE_SIZEの定義(たぶんマクロ)を参照してください.vmedrv には,PAGE_SIZE を画面に出力するサンプルプログラム pagesize.c が含まれています.
メモリマップトアクセスでは,open() 時のデータ幅指定にかかわらず,アクセスするポインタの型によって,D08/D16/D32 の全ての転送幅を混在して使用できます.
戻り値は,マッピングされた領域の先頭アドレスです. マッピングに失敗した場合は,負の値が返され,大域変数 errno が設定されます. 詳しくは,マニュアルページ mmap(2) を参照してください.
デフォルトの割り込みベクタは幅 16 bit です. 8 bit のベクタ返すモジュールを使用する場合は,上記の例のように,SET_VECTOR_MASK でベクタの有効ビットマスクを指定してください.16 bit のベクタを使用する場合は不要です.struct vmedrv_interrupt_property_t interrupt_property; interrupt_property.irq = IRQ; interrupt_property.vector = VECTOR; interrupt_property.signal_id = SIGNAL_ID; /* 割り込みの登録 */ ioctl(fd, VMEDRV_IOC_REGISTER_INTERRUPT, &interrupt_property); /* 割り込みベクタ幅の指定(8bitベクタを使用する場合のみ) */ interrupt_property.vector_mask = 0x00ff; ioctl(fd, VMEDRV_IOC_SET_VECTOR_MASK, &interrupt_property); /* 割り込み取得後,自動で割り込み禁止を設定する(必要な場合のみ) */ /* ioctl(fd, VMEDRV_IOC_SET_INTERRUPT_AUTODISABLE, &interrupt_property); */ /* 割り込み許可 */ ioctl(fd, VMEDRV_IOC_ENABLE_INTERRUPT); /* ここで割り込み処理を行なう */ /* VME 割り込みで,登録してある SignalID のシグナルが発生する */ /* 割り込み禁止 */ ioctl(fd, VMEDRV_IOC_DISNABLE_INTERRUPT); /* 割り込みの削除 */ ioctl(fd, VMEDRV_IOC_UNREGISTER_INTERRUPT, &interrupt_property);
割り込みの許可により,その時点で登録してある全ての割り込みに対して,ハンドリングが開始されます.登録されている割り込みが発生した場合,割り込みプロパティで指定したシグナルがプロセスに投げられます.
VMEDRV_IOC_SET_INTERRUPT_AUTODISABLE を設定した場合,ドライバにより割り込みが取得されると,割り込みハンドラを出る前に自動で VMEDRV_IOC_DISABLE_INTERRUPT が行われます.ベクタを取得しても IRQ をクリアしないようなモジュールを使っている場合,割り込みの繰り返し取得でシステムが固まらないようにするため,このオプションを指定してください.なお,この場合,割り込み取得後に次の割り込みを取得するためには,毎回 VMEDRV_IOC_ENABLE_INTERRUPT を行う必要があります.
vmedrv が使用する割り込みベクタは 16 bit です. 8 bit のベクタ返すモジュールを使用する場合は,上記の例のように,SET_VECTOR_MASK でベクタの有効ビットマスクを指定してください.16 bit のベクタを使用する場合は不要です.struct vmedrv_interrupt_property_t interrupt_property; interrupt_property.irq = IRQ; interrupt_property.vector = VECTOR; interrupt_property.signal_id = 0; /* 割り込みの登録 */ ioctl(fd, VMEDRV_IOC_REGISTER_INTERRUPT, &interrupt_property); /* 割り込みベクタ幅の指定(8bitベクタを使用する場合のみ) */ interrupt_property.vector_mask = 0x00ff; ioctl(fd, VMEDRV_IOC_SET_VECTOR_MASK, &interrupt_property); /* 割り込み取得後,自動で割り込み禁止を設定する(必要な場合のみ) */ /* ioctl(fd, VMEDRV_IOC_SET_INTERRUPT_AUTODISABLE, &interrupt_property); */ /* 割り込み許可 */ ioctl(fd, VMEDRV_IOC_ENABLE_INTERRUPT); /* 割り込み待ち */ interrupt_property.timeout = TIMEOUT_SEC; result = ioctl(fd, VMEDRV_IOC_WAIT_FOR_INTERRUPT, &interrupt_property); if (result > 0) { /* VME 割り込みを取得 */ } else if (errno == ETIMEDOUT) { /* タイムアウト */ } else if (errno == EINTR) { /* シグナル等他のものに割り込まれた */ } else { /* エラー */ } /* 割り込み禁止 */ ioctl(fd, VMEDRV_IOC_DISNABLE_INTERRUPT); /* 割り込みの削除 */ ioctl(fd, VMEDRV_IOC_UNREGISTER_INTERRUPT, &interrupt_property);
VMEDRV_IOC_SET_INTERRUPT_AUTODISABLE を設定した場合,ドライバにより割り込みが取得されると,割り込みハンドラを出る前に自動で VMEDRV_IOC_DISABLE_INTERRUPT が行われます.ベクタを取得しても IRQ をクリアしないようなモジュールを使っている場合,割り込みの繰り返し取得でシステムが固まらないようにするため,このオプションを指定してください.なお,この場合,割り込み取得後に次の割り込みを取得するためには,毎回 VMEDRV_IOC_ENABLE_INTERRUPT を行う必要があります.
/* ここまでで,ioctl(fd, VMEDRV_IOC_WAIT_FOR_INTERRUPT) が使えるようにしておく */ int result = ioctl(fd, VMEDRV_IOC_CHECK_INTERRUPT, &interrupt_property); if (result == 0) { /* 割り込みがなかった */ } else if (result > 0) { /* 割り込みがあった */ /* 必要な処理を行う */ /* 割り込みのクリア */ ioctl(fd, VMEDRV_IOC_CLEAR_INTERRUPT, &interrupt_property); test_interrupter_clear(); } else /*(result < 0)*/ { /* エラー */ }
VME 割り込みが発生すると,vmedrv は POLLIN イベントを発生させます.エラーが発生すると,poll() は -1 を返し,大域変数 errno を設定します.詳しくは,マニュアルページ poll(2) を参照してください./* ここまでで,ioctl(fd, VMEDRV_IOC_WAIT_FOR_INTERRUPT) が使えるようにしておく */ struct pollfd fd_set[fd_set_size]; fd_set[0].fd = fd_my_module_1; fd_set[0].event = POLLIN; fd_set[1].fd = fd_my_module_2; fd_set[1].event = POLLIN; fd_set[2]... /* その他の入出力デバイスを登録 */ int status = poll(fd_set, fd_set_size, timeout_sec); if (status < 0) { if (errno == EINTR) { /* poll() 中にシグナル割り込みが発生した */ } else { perror("ERROR: poll()"); } } else if (fd_set[0].revent & POLLIN) { /* my_module_1 が割り込みを出している */ } else ...
戻り値は,読み出しに成功したサイズです./* 転送法を DMA にする.デフォルトがDMAのエントリをオープンした場合には必要ない.*/ int transfer_method = VMEDRV_DMA; ioctl(fd, VMEDRV_IOC_SET_TRANSFER_METHOD, &transfer_method); int read_size = read(fd, buffer, max_size);
DMA 転送中は,VME へのアクセスはしないようにしてください.また,全ての VME 割り込み処理は DMA 転送が終了するまで延期されます.
Non-block DMA を行う場合は,transfer_method に VMEDRV_NBDMA を指定してください.デフォルトが Non-Block DMA のエントリをオープンした場合には,これは必要ありません.なお,Non-Block DMA では,転送速度は DMA の半分程度かそれ以下になります(手持ちのメモリカードと PC での測定で,DMA が 15MB/sec, Non-Block DMA が 9MB/sec. ただし,これはメモリカードの性能に制限されている可能性もある).
読み出しに失敗した場合は,負の値が返され,大域変数 errno が設定されます. 詳しくは,マニュアルページ read(2) を参照してください.
戻り値は,書き込みに成功したサイズです./* 転送法を DMA にする.デフォルトがDMAのエントリをオープンした場合には必要ない.*/ int transfer_method = VMEDRV_DMA; ioctl(fd, VMEDRV_IOC_SET_TRANSFER_METHOD, &transfer_method); int written_size = write(fd, buffer, size);
DMA 転送中は,VME へのアクセスはしないようにしてください.また,全ての VME 割り込み処理は DMA 転送が終了するまで延期されます.
Non-block DMA を行う場合は,transfer_method に VMEDRV_NBDMA を指定してください.デフォルトが Non-Block DMA のエントリをオープンした場合には,これは必要ありません.なお,Non-Block DMA では,転送速度は DMA の半分程度かそれ以下になります.
書き込みに失敗した場合は,負の値が返され,大域変数 errno が設定されます. 詳しくは,マニュアルページ write(2) を参照してください.
ioctl(fd, VMEDRV_IOC_PROBE, &word_access) は,読み出しに成功した場合 word_access.data に読み出した値を格納し,0 を返します.ただし,読み出した値はバイトオーダが違うかもしれないので,直接使用しないでください.失敗した場合は -1 を返し,大域変数 errno を設定します.vmedrv_word_access_t word_access; word_access.address = address; if (ioctl(fd, VMEDRV_IOC_PROBE, &word_access) == -1) { /* エラー:データが読み出せない */ } else { /* 成功:読み出した値は word_access.data に記録されている */ /* もしかしたらバイトオーダが違うかもしれない */ }
ここで,munmap() に渡す address 引数は,mmap() で返された値,map_length 引数は,mmap()で実際に渡された値です.ページ境界の処理をした場合には,混乱しないように注意してください.munmap(start, map_length); close(fd);