PCIの情報はというと /proc/pci 、 /proc/bus/pci/devices をよむと一通りのPCIデバイスの一覧を得ることができます。 ここでは各デバイスにアクセスするための I/Oポートアドレス、メモリアドレスが得られます。
デバイスドライバを作る利点もいろいろとありますが、ここでは、作らずに済む方法を示します。
デバイスドライバをつくらなくとも、ハードの操作はできます。
デバイスドライバを作らなければならないのは
I/Oポートのアクセスには、
#include <asm/io.h> outb(val,port); /* ポート port に 1バイト val を出力 */ outw(val,port); /* ポート port に 2バイト val を出力 */ outl(val,port); /* ポート port に 4バイト val を出力 */ inb(port); /* ポート port から 1バイト 入力 */ inw(port); /* ポート port から 2バイト 入力 */ inl(port); /* ポート port から 4バイト 入力 */といった関数があります(out?はMS-DOS, Windows系と引数順が異なりますのでご注意を)。
LinuxではI/Oアクセスが容易ですが、1プロセスが他のプロセスに迷惑をかける可能性を避けるため、root でなければ、使えません。 さらに、root でも普段はアクセス禁止されていて、使用前に禁止解除しなければなりません。これには以下の2つの関数を使用します。
#include <unistd.h> /* for libc5 */ (man ioperm, man iopl) #include <sys/io.h> /* for glibc */ int ioperm(unsigned long from, unsigned long num, int turn_on); int iopl(int level);前者はより安全な方法で、アドレス from から num の空間を turn_on=1 でアクセス可能にします。 この方法の弱点は 0x0000-0x03ff の空間しか対応していないことです。 そのため、PCIで自動割り当てされたボードや、最近拡張されている I/O などの高位アドレスにはアクセスできません。
後者はI/O空間全体に対して、一括して禁止解除します。
そのため、ポートを間違ったときにはそのままアクセスされ、注意が必要です(中には読むだけでハングアップするポートもあります)。
この命令は具体的にはi386のI/Oアクセスの許可判断となるフラグレジスタのIOPLビットの操作ですが
( Linux2.2:arch/i386/kernel/ioport.c )、単純には level=3 にすれば I/Oアクセスが可能、 level=0 にすれば不可能、となります。
なお、付随する特権として、ハードウェア割り込みの許可不許可(sti,cli) が使えるようになってしまいますが、うかつに使うとOSごと止りかねないので、使わない方がいいでしょう。
man mem に書いてあることによると、open して read/write/lseek して読み書きすると、物理的なアドレスの読み書きできます (fopen, fclose でも出来ないことはないと思いますが)。 他にカーネルが使用しているメモリ空間を読むために kmem というのもあります。 これらは
% ls -l /dev/mem /dev/kmem crw-r----- 1 root kmem 1, 2 May 6 1998 /dev/kmem crw-r----- 1 root kmem 1, 1 May 6 1998 /dev/memのように、root でないと普通は読めません。間違って書いたらI/Oポート以上に危いので、当然と言えば当然でしょう。
メモリを読んでみるのに実験用のプログラムを書いてみます。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> int main(int argc,char **argv) { char buff[1024]; char file[256]; int fd; unsigned int st,len; int r; char *dev=(strrchr(argv[0],'/'))?strrchr(argv[0],'/')+1:argv[0]; if(argc!=3) { fprintf(stderr,"%s <start addr> <len>\n",dev); return 1; } st=strtol(argv[1],NULL,16); len=strtol(argv[2],NULL,16); sprintf(file,"/dev/%s",dev); fd=open(file,O_RDONLY); if(fd<0) { fprintf(stderr,"cannot open %s\n",file); return 1; } lseek(fd,st,SEEK_SET); while(len) { r=read(fd,buff,len>1024?1024:len); if(r<1) break; write(1,buff,r); len-=r; } close(fd); return 0; }プログラム名が mem, kmem かに応じて(それ以外 でも動きますが...)、/dev/mem, /dev/kmem を読み出して標準 出力にだします。 od -t x1 などと組み合わせて、
# ./mem 0 10 | od -t x1 0000000 01 00 00 00 ff e7 00 f0 c3 e2 00 f0 ff e7 00 f0 0000020 #などと利用します。応用例として、
# ksyms -a | grep jiffies c011b4fc proc_dointvec_jiffies c01f2918 jiffies ← タイマ割り込み1回(普通 i386で100回/1秒) で1増える変数 80279280 jiffies_R2gig0da02d67 ←最近はこういうゴミ(ではない)つき # ./mem 1f2918 4 | od -t x1 0000000 b4 d6 8e 96 0000004 # ./mem 1f2918 4 | od -t x1 0000000 cf 8a 8f 96 0000004 # (./mem 1f2918 4 | od -t x1); sleep 10 ; (./mem 1f2918 4 | od -t x1) 0000000 f5 9f 90 96 0000004 0000000 18 c7 90 96 0000004のように、カーネル内の変数を調べたりすることも可能です。
/dev/mem などをアクセスするのに便利なものに mmap というものがあります。
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); (man mmap)これは、こういったメモリのデバイスドライバなどをわざわざ read 経由で使わなくとも良いようにするためのものです。 また、lseekのオフセットがsigned long なので、PCIなメモリに一発で移動できない、という問題を回避するにも、つかえます。
先ほどのプログラムを少しいじってみました。
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/mman.h> #include <asm/page.h> int main(int argc,char **argv) { char buff[1024]; char file[256]; char *mmaped; int fd; unsigned int st,len,poff; int r; char *dev=(strrchr(argv[0],'/'))?strrchr(argv[0],'/')+1:argv[0]; if(argc!=3) { fprintf(stderr,"%s <start addr> <len>\n",dev); return 1; } st=strtoul(argv[1],NULL,16); len=strtoul(argv[2],NULL,16); poff=st%PAGE_SIZE; sprintf(file,"/dev/%s",dev); fd=open(file,O_RDONLY); if(fd<0) { fprintf(stderr,"cannot open %s\n",file); return 1; } fprintf(stderr,"mmap: start %08X len:%08X\n",st-poff,len+poff); mmaped=mmap(0,len+poff,PROT_READ,MAP_SHARED,fd,st-poff); if(mmaped==MAP_FAILED) { fprintf(stderr,"cannot mmap\n"); return 1; } write(1,mmaped+poff,len); munmap(mmaped,len); close(fd); return 0; }ただし、mmap には微妙に癖があるようで、<asm/page.h>に定義されている PAGE_SIZE 単位の位置しかマップできないようです(手元のmanには明示されてなく、エラーのところにあります)。 その分が小細工してあります。
ひとつの方法は、Linuxのインストールなどでもよくやる、Windowsのデバイスマネージャなどで調べる、というものです。
ですが、Linux がすでに動作しているなら、
embiped1:kumagai% cat /proc/pci PCI devices found: Bus 0, device 16, function 0: Multimedia video controller: Intel SAA7116 (rev 0). Medium devsel. IRQ 9. Master Capable. Latency=64. Non-prefetchable 32 bit memory at 0xec001000. Bus 0, device 15, function 0: Bridge: Unknown vendor Unknown device (rev 1). Vendor id=136c. Device id=9054. Medium devsel. Fast back-to-back capable. IRQ 10. Non-prefetchable 32 bit memory at 0xec002000. I/O at 0x1400. I/O at 0x14f0. Bus 0, device 14, function 0: Ethernet controller: SMC 9432 TX (rev 8). Fast devsel. Fast back-to-back capable. IRQ 11. Master Capable. Latency=64. Min Gnt=8.Max Lat=28. I/O at 0x1000. Non-prefetchable 32 bit memory at 0xec000000. : Bus 0, device 7, function 2: USB Controller: Intel 82371AB PIIX4 USB (rev 1). Medium devsel. Fast back-to-back capable. IRQ 9. Master Capable. Latency=64. I/O at 0x14c0. Bus 0, device 7, function 1: IDE interface: Intel 82371AB PIIX4 IDE (rev 1). Medium devsel. Fast back-to-back capable. Master Capable. Latency=64. I/O at 0x14e0. :増設したイーサネットカードや マザーボード上の USB, IDE などのデバイスがみられます。 ここで "I/O at 0x????" は各デバイスが要求し、割り当てられた I/Oポートを、"... memory at 0x????????" は同じくメモリを示しています。 イーサネットコントローラはI/Oのほか、メモリを要求しています。
さて、ここに Unknown なデバイスがあります。
Bus 0, device 15, function 0: Bridge: Unknown vendor Unknown device (rev 1). Vendor id=136c. Device id=9054. Medium devsel. Fast back-to-back capable. IRQ 10. Non-prefetchable 32 bit memory at 0xec002000. I/O at 0x1400. I/O at 0x14f0."Unknown" というのは「カーネルが知らない」という意味で、 linux/pci.h に定義されていないだけです。 特殊なI/O拡張ボードなどはUnknownのことが多いでしょう。 これは ID=136c であるメーカがつくった 9054 というIDを持ったハードであることが分かります。 ボードメーカがマニュアルに記載しているとおもいますが、無い場合はPCI SIG のサーチで調べることができます(狙ったのだと思いますが、INTELが8086)。
ちなみに、"Bus", "device", "function" はデバイスドライバでPCIデバイスの検出を行うときに使用する、物理的にどこにボードが挿さっているかを表す数字です。
ただ、この /proc/pci はプログラムから読むには難儀です。そのためか、Linux2.2以降では /proc/bus/pci/devices というファイルが増えていて、これはただの16進数数字の並びなので、
これらの方法をつかうと、ちょっと制御をしてみたりするときに、ドライバがいるか、というと要らない可能性が高いと言えそうです。 そのほか、今後のことも考えてドライバをつくる、割り込みを使いたいなどの場合にも、これらの方法で事前にあらからテストした上で、ドライバに取り組んだりする場合にも、これらの方法が役立つことでしょう。 カーネルにドライバを組み込んで恐い思いをするのは、なるべく最低限にとどめたいので。