KiNOKO-DAQ 関連情報 掲示板

このスレッドに記事を投稿する
前のスレッド | 次のスレッド | 掲示版ホーム

ioctl()がうまく使えません
2001 年 11 月 29 日 3 時 16 分
投稿者: 岩井 剛

こんにちは、新潟大学の岩井です。いつもお世話になっています。
さんしろう君のVMEドライバ(vmedrv-0.4.0)を使わせてもらってます。
ちょっと、悩んでるので助けてください。


プログラムの途中で、A32D32 から A16D16 に転送モードを変えたくて、

vmedrv_access_modes_t access_mode = VMEDRV_A16D16;
ioctl( fd, VMEDRV_IOC_SET_ACCESS_MODE, &access_mode );

という風にしてから、

*( (unsigned short*)( intr_ptr + (off_t)0x0e ) ) |= (unsigned short)0x0f;

という具合にして使ってるのですが、どうもうまいこと反応してくれません。intr_ptrはPC上のメモリアドレスです。そうゆうものなのでしょうか?

これを解決するために、

unsigned short buf = 0x0f;
lseek( fd, intr_offset + 0x0e, SEEK_SET );
write( fd, &buf, sizeof( buf ) );

こんな風にしたらうまくいってくれます。

自分としては、上も下も同じ動作を期待して書いたのですが、何かおかしなところがあるのでしょうか?


Re: ioctl()がうまく使えません
2001 年 11 月 29 日 4 時 26 分
投稿者: 榎本 三四郎

岩井くん,こんにちは.
掲示版を作ってはみたものの,誰も使わなかったので,そろそろ捨てようかと
思っていたところです.これを機に利用されるようになるといいけど...

mmap() は,それが呼ばれた時点で,指定されたサイズの VME の領域を全て PCI に
マッピングします.その際,インターフェースカード上にあるアドレスの対応を指定する
レジスタに,アクセスモードも併せて指定しています(SBS のアダプタでは同じレジスタ).
したがって,mmap() 完了後は (munmap() して再度 mmap() しない限り),
ioctl() によるアクセスモードの変更は反映されないことになります.

PIO の read()/write() では,マッピングレジスタを毎回書き直しています.また,
DMA の read()/write() では,全く別のレジスタを使ってパラメータを指定して
います.だから,write() を使った場合にはちゃんと動いたのだと思います.

今,Web のドキュメントを見直してみると,確かにいつでも変えられると書いて
ありました.すいません.変更が反映されるのは read()/write() を使った場合のみで,
マップトアクセスの場合には mmap() する前に ioctl() で指定しなければなりません.

vmedrv は,一回の open() に対して,何度も mmap() をすることができます.
多分,同じアドレス領域に対して異なったアクセスモードでマッピングをしても,
ちゃんと動くはずです(そういうモジュールを持っていないので,テストはでき
ませんでしたが).

- - - - - - - - - - 8< - - - - - - - - - -

int fd = open("/dev/vmedrv32d32", O_RDWR);

unsigned int* start32 = (unsigned int*) mmap(0, length, prot, flags, fd, offset);

vmedrv_access_modes_t access_mode = VMEDRV_A16D16;
ioctl(fd, VMEDRV_IOC_SET_ACCESS_MODE, &access_mode);

unsigned short* start16 = (unsigned short*) mmap(0, length, prot, flags, fd, offset);

...

munmap(start16, length);
munmap(start32, length);
close(fd);

- - - - - - - - - - 8< - - - - - - - - - -

たぶんこんな感じでいいでしょう.マッピングしたメモリにアクセスする際は,
それぞれ正しいサイズの型のポインタを使ってください.

あるいは,最初から別のアクセスモードでオープンしておいてもいいはずです.

- - - - - - - - - - 8< - - - - - - - - - -

int fd32 = open("/dev/vmedrv32d32", O_RDWR);
int fd16 = open("/dev/vmedrv16d16", O_RDWR);

unsigned int* start32 = (unsigned int*) mmap(0, length, prot, flags, fd32, offset);
unsigned short* start16 = (unsigned short*) mmap(0, length, prot, flags, fd16, offset);

...

munmap(start16, length);
munmap(start32, length);
close(fd16);
close(fd32);

- - - - - - - - - - 8< - - - - - - - - - -

open() 後のアクセスモードの変更ができないドライバが多いので,移植性を
考えるなら,こっちの方がいいかもしれません.


Re: Re: ioctl()がうまく使えません
2001 年 12 月 1 日 4 時 56 分
投稿者: いわい

こんばんは、岩井です。おかげさまでなんとか問題が解決しました。お忙しい中、すみません。
まだ、VMEモジュールが実験でどんな風に使われるのか、いまいち全貌が見えてないだけに、なかなか汎用クラスを作るというとこまではいかないんですけど、とりあえず、さんしろう君のアドバイス通りに、

int fd32 = open("/dev/vmedrv32d32", O_RDWR);
int fd16 = open("/dev/vmedrv16d16", O_RDWR);

unsigned int* start32 = (unsigned int*) mmap(0, length, prot, flags, fd32, offset);
unsigned short* start16 = (unsigned short*) mmap(0, length, prot, flags, fd16, offset);

こんな感じにそのままパクって、各転送モードごとに最初にマッピングさせちゃってます。ところが、再び困ってます。

というのも、ハードウェアのアドレスというのはユーザコーディングに任せることになると思うんですけど、たとえば、Interrupt Registerのアドレスが 0x0010 だとしたら、どっかで、そこを指すポインタを持たせなくちゃいけませんよね?
で、まあ、普通に

unsigned short* intrrupt_ptr = start16 + 0x0010;

とかしたら、2バイトの10(16)個先を指しちゃって、期待するところを指しません。クラス開発者は型を知ってるから内部で適当にいじくるべきなのでしょうか?
それとも一度 void にキャストして、つまり、

unsigned short* interrupt_ptr = (unsigned short*)( (void*)start16 + 0x0010 );

という風に計算したらいいんでしょうか?ちなみに、これは「ANSI C++ではダメ」といって、WARNINGが出ます。う〜ん。。。


Re: Re: ioctl()がうまく使えません
2001 年 12 月 1 日 18 時 27 分
投稿者: 榎本 三四郎

榎本です.こんにちは.

説明を短くするために mmap() が返すポインタを unsigned short* にキャストして
しまいましたが,アドレス演算をしたいなら,caddr_t とかを使う方が便利だと思います.
caddr_t は,off_t と併せて,アドレスに対して普通の整数演算ができます.

- - - - - - - - - - - 8< - - - - - - - - - - -

#include <sys/types.h>

caddr_t base_ptr = (caddr_t) mmap(...);

off_t register_offset = 0x10;

unsugned short* register_ptr = (unsigned short*) (base_ptr + register_offset);

- - - - - - - - - - - 8< - - - - - - - - - - -

void* に対して演算ができないのは,ポインタの指す型のサイズが分からないからです.
アドレスに対して普通の整数演算をしたいという場合は,ちゃんとそういう型を
使えと言うのが ANSI の意図なのでしょうか.
(ヘッダファイルでは,caddr_t は char* を typedef しただけでしたが)

ちなみに,こういうことをやりたくて,ポインタを int にキャストするというのも
たまに見ますが,これは良くないことらしいです.CPU によっては,ポインタのサイズ
が int よりも大きいことがあるというのが最大の理由だったと思います.
char* へのキャストだったら,ちゃんと動くとは思いますが...意味が分かりにくくなるかな?


Re: Re: Re: ioctl()がうまく使えません
2001 年 12 月 3 日 16 時 19 分
投稿者: 岩井 剛

こんにちは、岩井です。すみません。ホント。

なるほど。caddr_tですか。そういえば坂本(ひ)さんも使ってました。ただ、これもansiオプションを使うと、未定義として扱われてしまうので、避けてたんですよね。わかりました、VME周りに関しては非ANSIにするのがいいのかなぁ?

> char* へのキャストだったら,ちゃんと動くとは思いますが...意味が分かりにくくなるかな?
そうなのです、これは現在のKONOEで使われてるんですが、まったくもって意味不明だったのですが、さんしろう君のおかげでやっと理解できました!ありがとうございます。

とにかく問題は解決しました!当面の方針としては、

fd32 = open( A32D32_FILE, O_RDWR );
fd16 = open( A16D16_FILE, O_RDWR );

caddr_t start32 = (caddr_t)mmap( ..., fd32, ... );
caddr_t start16 = (caddr_t)mmap( ..., fd16, ... );
off_t offset = 0x????;
unsigned short* intr_ptr = (unsigned short*)( start16 + offset );

という感じで、かつ 非ANSI で行くことにします。
あ〜、でも書いてて思いましたが、結局、
// RPV130のクリア動作、CSR2 は 0x0e
*( (unsigned short*)( (void*)intr_ptr + CSR2 ) ) |= 0x0003;
なり、
*( (unsigned short*)( (char*)intr_ptr + CSR2 ) ) |= 0x0003;
なりしてやらないといけませんね。もはや、これはしょうがないのか。

いろいろありがとうございました。とても勉強になりました。カムランドがんばってください。そういえば、この前、白井先生が新潟に講義しにきてました。先生にもよろしくお伝えください。

そして、またなにかありましたら(なるべくないようにしますが)、よろしくお願いいたします。


Re: Re: Re: ioctl()がうまく使えません
2001 年 12 月 3 日 20 時 29 分
投稿者: 榎本三四郎

こんにちは.榎本です.
まとまりかけた話を蒸し返すようで申し訳ないのですが...

mmap() をするときに,VME のアドレススペース全体を一度に
マップしようとしていませんか? (違ってたらごめんなさい)

それでも,ちゃんと動いていればいいんですけど,例えば SBS のアダプタでは,
4kB をマッピングできるレジスタが 8k 個しかなく,最大でも 32MB までしか
マッピングできません(実際にはドライバの仕様のためそれよりも小さい).
つまり,A32D32 のアクセスモードだけでも,ごく一部しかマッピングできない
ことになります.他のアクセスモードもと考えたら,不要な制限を作ることにも
なりかねません.

使い方にもよりますが,多分使うモジュールの数だけドライバを open()
する方が便利だと思います.そうしたら,アクセスモードに対して気を使う
必要も多分なくなって,アドレスの演算も楽になると思います.

例えば,I/O レジスタのベースアドレスが 0x3000 (A16D16) だとしたら,

- - - - - - - - - - 8< - - - - - - - - - -

off_t io_register_vme_address = 0x3000;
size_t io_register_map_size = 0x1000;
off_t io_register_CSR2 = 0x000e;

int io_register_fd = open("/dev/vmedrv16d16", O_RDWR);
caddr_t io_register_base = mmap(
0, io_register_map_size, prot, flags, io_register_fd, io_register_vme_address
);

((unsigned short*) (io_register_base + io_register_CSR2)) |= 0x0003;

- - - - - - - - - - 8< - - - - - - - - - -

みたいな感じでどうでしょうか.
I/O レジスタにクラスを割り当てれば,アクセスモードやマップサイズなどのパラメータは
クラス内部に記述できるし,うとおしい io_register_ という接頭語もなくなります.
open() や mmap() などもうまくカプセル化できそうです.

あと,caddr_t がいやなら,char* を適当な名前に typedef してはどうでしょうか?
(実際, sys/types.h 中の caddr_t の定義もそうなっています)


BIT3とは関係なくモジュールだけマップできますか?
2001 年 12 月 4 日 19 時 0 分
投稿者: 岩井 剛

こんばんは、岩井です。すみません、何度も、何度も。

> mmap() をするときに,VME のアドレススペース全体を一度に
> マップしようとしていませんか? (違ってたらごめんなさい)

う、いまいち、まだよく理解できてないんですが、多分そんなことはしてないです。少なくとも、そうゆう意図でプログラムを書いてはいません。
一応、マニュアルにしたがって、モジュールのメモリ占有サイズと開始アドレスを与えてマップしています。
さらに言えば、BIT3というモジュールの役割がいまいちわかってません。単なるバイパスなのか?(クレートコントローラー?)くらいに考えて使ってます。バイパスなのだから、この人に直で書いたり、この人から直で読んだりすることはないだろうと思ってるのでサイズも適当に与えてしまってます。

それとですね、さんしろう君と食い違いがある最大のポイントはモジュールのみをマップ出来るか出来ないか?ということだと思います。ドライバにくっついてきたvmeint.cでもI/Oレジスタを直接マップしてますが、それがどうしても出来ないのです。しょうがないから、最初にBIT3のアドレスを与えてmmap()呼んで、その返り値にI/Oレジスタのハードウェアアドレスを加えて、それをもってI/Oレジスタである、と唱わせていたのです。

僕としては当初はできれば、モジュールごとにopen(),mmap(),close(),munmap()を行なわせたいので、

// BIT3とI/Oレジスタが入ってるけど、使うのはI/Oレジスタのみ

// I/Oレジスタのディップスイッチで設定したアドレス
off_t address = 0x8FF0;


size_t size = 0x1000;
off_t CSR2 = 0x000e;
int fd = open( "/dev/vmedrv16d16", O_RDWR);
caddr_t base =
(caddr_t)mmap( 0, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, address );
*((unsigned short*) (base + CSR2)) |= 0x0003;


という風にしたんです。
でも、perror()の返り値はいつも「Device not configured」なんです。open()には成功しています。で、じゃあ、いったいどこだったらうまくいくんだ?というわけで、アドレス0x0000 から 0xffff まで動かしてマップしてみたところ、0x0000 が成功しましたが、返り値であるアドレスにアクセスしてみたら、BIT3をマップしてました。

??ちなみに今、BIT3を"/dev/vmedrv32d32" でマップするにはベースアドレスは 0x80000000 になるようにしてます。ちょっと理解を超えてきました。きっとどこかでミスをしているんでしょうが。


あとですね、話は飛んでしまいますが、もうひとつ疑問に思ってるのが、例えば、I/Oレジスタクラスを作って、コンストラクタで毎回、open()とmmap()が呼ばれるとするじゃないですか。そうゆう場合って、同じノードにアクセスすることになっちゃいますよね?それはクラス開発者の責任で、select()とかFD_XXX()とか使って解決するしかないんでしょうか?それとも、全く気にしなくていいんでしょうか?


Re: BIT3とは関係なくモジュールだけマップできますか?
2001 年 12 月 4 日 21 時 30 分
投稿者: 榎本 三四郎

こんにちは.榎本です.

まず,mmap() は,offset に与えられるのは 4kB の倍数だけです.
これは,CPU と Linux カーネルの仕様です.
ちなみに,この 4kB という数字は,x86 シリーズの CPU の場合です.
他の CPU や将来の x86 では異なる可能性があります.

理由は... あまり詳しくないのですが,x86 などの CPU は,メモリを
完全に一様な領域として扱っているのではなく,4kB の連続領域を
1ページとして,ページごとに管理しているためです.メモリマッピングなど
もページ単位で行われるため,マッピングの先頭アドレスもページ境界である
4kB の倍数になっていなければならないわけです.

ちなみに,4k は,16 進数では 0x1000 です.したがって,モジュールの
ベースアドレスは,A16 モードであれば,0x0000, 0x1000, 0x2000, ... 0xf000
のいずれかである必要があります.サイズは,いくつでも良かった気がしますが,
結局は内部で 4kB 単位に切り上げられるので,普段は最初から 4kB 単位に切り上げて
指定しています.

どうしても 0x8ff0 などのような半端なアドレスからマッピングしたい場合は,
以下のようにしてはどうでしょうか.

- - - - - - - - - - 8<- - - - - - - - - -

off_t vme_address = 0x8ff0;

int PAGE_SIZE = 0x1000; // 実際には "asm/page.h" で定義されている
int PAGE_MASK = ~(PAGE_SIZE - 1); // これも

off_t vme_map_address = vme_address & PAGE_MASK;
off_t vme_map_offset = vme_address & ~PAGE_MASK;

caddr_t map_base = mmap(0, ..., vme_map_address);
caddr_t module_base = map_base + vme_map_offset;


// module_base の方を使う


munmap(map_base);

- - - - - - - - - - 8<- - - - - - - - - -

今思ったけど,PAGE_SIZE のようなものは,vmedrv.h の中でも定義してあったほうが
便利ですね.たぶんそのうちそうします.


Bit3 の役割は,PIO の read()/write() と mmap() に限って言えば,
アドレス変換器です.つまり,PCI に入っているインターフェースカードは,
PCI バス上のあるアドレス範囲に応答します.この範囲は,lspci -v などと
したときに表示される領域です.

% /sbin/lspci -v

00:0a.0 Bridge: Bit3 Computer Corp. VME Bridge Model 618 (rev 42)
Flags: bus master, slow devsel, latency 32, IRQ 11
I/O ports at 2020
Memory at feb60000 (32-bit, non-prefetchable)
Memory at feb50000 (32-bit, non-prefetchable)
Memory at fc000000 (32-bit, non-prefetchable) <- これ

%

で,PCI 側のカードが,この領域へのアクセスを VME 側のカードに伝えて,
VME 側のカードがそれを VME クレートへのアクセスに変換します.

この PCI のアドレスと VME のアドレスの対応の変換表が Bit3 のカード上に
あって,マッピングレジスタと呼ばれているものです.CPU と同じく,アドレススペースを
4kB ごとに区切って,1ページにつき一つのレジスタが対応するアドレスを保持しています.
Bit3 上には,これが 8k個あって,最大 4kB * 8k = 32MB までマッピングできるわけです.

このあと,PCI のメモリをプロセス中のアドレス(mmap() で返される値) に変換しなければ
なりませんが,これは CPU とカーネルの仕事です.この変換を管理するために,4kB ごとの
ページが使われます.

それから,vmedrv を経由している限り,Bit3 のカード自体は,通常のプロセスからは
全く見えません.ドライバをオープンすること以外,Bit3 カードの存在を意識しなければ
ならないことはないはずです.


最後に,同じドライバノード(/dev/vmedrv*) を同時にいくつもオープンし,
同時にアクセスすることには,特に問題はありません(実際にはドライバはカーネル
の下にあって,内部で自発的に sleep などをしない限り「同時」にアクセスされる
ことはない).もし(ほぼ)同時に read() が呼ばれた場合でも,たぶん一方のレスポンスが
ちょっと遅くなるだけのことです.

ただ,DMA を使う場合は,DMA 中に別のところで VME にアクセス
しないように注意してください.DMA 転送中は,プロセス自体はスリープしていて,
他のプロセスがVME にアクセスすることができてしまいますが,Bit3 のマニュアルには,
それをしたら何が起こるか分からないと書いてあります.

read()/write() に関しては,ドライバ内部でロックをしようとも考えたのですが,
メモリマップ経由のアクセスはドライバではどうしようもないので,いまのところ全て
ユーザの管理にまかせています.


やりました!
2001 年 12 月 5 日 2 時 33 分
投稿者: 岩井 剛

こんばんは、岩井です。
長々とありがとうございました!結果からいいますと、うまく動きました!I/Oレジスタのアドレスを0x1000に設定したら、いとも簡単に。。。
さんしろう君のドライバのドキュメントにも書いてあったんですけど、自分にはあまり関係ないことだろうと勝手に理解してました。(なぜ、0x8FF0にこだわったかというと、マニュアルのデフォルト値だったからです)

それと、中途半端なアドレスをコンストラクタに与えられた場合もオーバーロードで解決することができそうです。
それと、ノードの件も理解できました。DMAはまだ使ったことがないので、問題になるのはまだ先になりそうですが。

親切に回答していただいて、本当にありがとうございました。感謝、感謝です。


このスレッドに記事を投稿する