ただ、ハードウェアを操作するだけなら、デバイスドライバは必須ではありません。 なぜなら、Linux は root 権限のあるプログラムであれば、ハードウェア(I/Oポート、メモリ)にアクセスできるからです。
PCIの情報はというと /proc/pci 、 /proc/bus/pci/devices をよむと一通りのPCIデバイスの一覧を得ることができます。 ここでは各デバイスにアクセスするための I/Oポートアドレス、メモリアドレスが得られます。
デバイスドライバを作る利点もいろいろとありますが、ここでは、作らずに済む方法を示します。
デバイスドライバをつくらなくとも、ハードの操作はできます。
デバイスドライバを作らなければならないのは
デバイスドライバからは簡単にできる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ごと止りかねないので、使わない方がいいでしょう。
MS-DOSのC言語では、何も考えずポインタ変数にアドレスを代入すれば物理的なメモリ番地は参照できていましたが、Linux(Windowsも)はそうはいきません。
普段プロセスからアクセスしているメモリが物理的なメモリのどこにあるかを知るのはカーネルのみです。
また、マルチタスクOSとして、あるプロセスは他のプロセスのメモリ領域の読み書きは出来ません。
メモリの読み書きには /dev/mem を使います。
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には明示されてなく、エラーのところにあります)。
その分が小細工してあります。
ISAバス等に固定したデバイスに関しては以上の方法で基本的にはアクセス可能でしょう。ただし、PCIバスのハードウェアで、アドレスが一意に定まらない場合、それを調べる必要があります。
ひとつの方法は、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進数数字の並びなので、
ここでは、デバイスドライバなしで出来るハードウェア操作について述べました。 上に示しましたように I/Oポートのアクセスおよびメモリの読み書きは可能です。 また、PCIで自動的に決定されるアドレスも容易に知ることが可能です。
これらの方法をつかうと、ちょっと制御をしてみたりするときに、ドライバがいるか、というと要らない可能性が高いと言えそうです。
そのほか、今後のことも考えてドライバをつくる、割り込みを使いたいなどの場合にも、これらの方法で事前にあらからテストした上で、ドライバに取り組んだりする場合にも、これらの方法が役立つことでしょう。
カーネルにドライバを組み込んで恐い思いをするのは、なるべく最低限にとどめたいので。