I/O命令
すでに述べられているように、インテル86系のCPUを使用したI/Oを行なう
場合、I/O空間とメモリ空間は別々の命令によってアクセスされます。
I/OデバイスのI/O空間へのアクセスはアセンブラ命令のI/O命令を利用する
のに対して、I/Oデバイスのメモリ空間へのアクセス方法は、
CPUがプログラムやデータを格納するために利用する通常のメモリへのアクセス方法
と同じように、ポインタを使って行なわれます。
I/O命令の代表的なものをあげて見ます。
- 8ビット入力 :inb (unsigned short port)
- 16ビット入力:inw (unsigned short port)
- 32ビット入力:inl (unsigned short port)
- 8ビット出力 :outb (unsigned char value, unsigned short port)
- 16ビット出力:outw (unsigned short value, unsigned short port)
- 32ビット出力:outl (unsigned int value, unsigned short port)
これらの関数は/usr/include/sys/io.hで下記のように定義されています。
extern inline unsigned char
inb (unsigned short port)
{
unsigned char _v;
__asm__ __volatile__ ("inb %w1,%0":"=a" (_v):"Nd" (port));
return _v;
}
extern inline unsigned int
inl (unsigned short port)
{
unsigned int _v;
__asm__ __volatile__ ("inl %w1,%0":"=a" (_v):"Nd" (port));
return _v;
extern inline void
outb (unsigned char value, unsigned short port)
{
__asm__ __volatile__ ("outb %b0,%w1": :"a" (value), "Nd" (port));
}
}
extern inline void
outl (unsigned int value, unsigned short port)
{
__asm__ __volatile__ ("outl %0,%w1": :"a" (value), "Nd" (port));
}
上記の定義から、I/O空間へのアクセスはアセンブラ命令へと変換される
ことがおわかりでしょう。
I/O命令の代表的なものをあげて見ます。
- 8ビット入力 :readb(addr)
- 16ビット入力:readw(addr)
- 32ビット入力: readl(addr)
- 8ビット出力 :writeb(b,addr)
- 16ビット出力:writew(b,addr)
- 32ビット出力:writel(b,addr)
これらの関数は/usr/include/asm/io.hで下記のように定義されています。
#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
#define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
#define readl(addr) (*(volatile unsigned int *) __io_virt(addr))
#define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
#define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
#define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))
#define __io_virt(x) ((void *)(x))
御覧の通り、メモリ空間へはポインタを利用したシンプルなアクセスになっています。
次のコードはC言語で書かれたI/Oアクセスの例題です。
#include
main() {
char a;
a = inb(0x70);
outb(0x70,a);
}
これをコンパイルします。
gcc -O -S t.c
ここで、-Oは必要な、オプティマイズのフラグです。-Sはアセンブラコードを
出力するためのフラグです。
.file "t.c"
.version "01.01"
gcc2_compiled.:
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp,%ebp
#APP
inb $112,%al
#NO_APP
movb %al,%dl
movsbw %dl,%dx
movb $112,%al
#APP
outb %al,%dx
#NO_APP
leave
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.7.2.3"
となり、inb()関数が、下記のようにinbアセンブラ命令に変換されます。
inb $112,%al
また、outb()関数が、下記のようにinbアセンブラ命令に変換されます。
outb %al,%dx
ユーザプログラムからI/O空間へアクセスする最も簡単は方法を示します。
これらの方法は実行時においてrootの特権が必要とされますが、
大変便利です。デバイスドライバを開発する時、とりあえずこれらの
方法でコードのデバッグをしておいて、デバイスへのアクセス方法が
確立してコードが安定したら、デバイスドライバに組み込むということが
可能です。ただし、これらの方法はユーザプログラムから簡単にシステムを
破壊することが可能になります。注意して使いましょう。
I/Oアドレスが0から0x3FFまででしたらiopermシステムコールを使うのが
いいでしょう。次のように使います。
#include <sys/io.h>
#define ADDR 0x310
#define SIZE 10
if (ioperm(ADDR, SIZE, 1)) {
printf("Can't get I/O permissions \n");
exit (-1);
}
これはI/Oアドレス0x310から10バイト分アクセスを許可するものです。
I/Oアドレスが0x400以上の場合iopermは使えません。
そこで登場するのが
#include <sys/io.h>
if( iopl( 3 ) ) {
printf("can not change the privilege level\n");
exit(0);
}
このようにすれば65536ポート(0xFFFF)まで利用範囲が広がります。
さて、そのようにしてI/O空間の一部がユーザプログラムから利用可能に
なるとI/O命令がそのまま使用できるようになります。
下記のコードはその一例です。
int data;
outl(RST, ADDR);
data = inl( ADDR );