Linux Kernel 2.2 Documentation:
/usr/src/linux/Documentation/IO-mapping.txt
IO-mapping.txt
Linux による IO マッピングに関する質問への回答メール
[プレインテキスト版]
- 原著作者: Linus Torvalds <torvalds@transmeta.com>
- 翻訳者: Hiroshi MIURA <miura@blue.gr.jp>
- バージョン: 2.2.0
- 翻訳日時: 2000/08/29
「これは、IO マッピングをめぐる質問への回答メールである。そのため添付の
技術文書としてはフォーマットがすこし変になっている。」
AHA-1542 はバスマスターで動作するデバイスである。そしてあなたのパッチは、コ
ントローラにバッファーの物理アドレスを与えるようにドライバーを変更するもの
だ。これは x86 においては正しい処理と言える (なぜならすべてのバスマスター
デバイスは物理メモリーのマッピングを直接見るから)。
「しかしながら、さまざまな構成のシステムの中にはメモリアドレスを
参照するために、実のところ *3 種類の* 相異なる方法が存在しているもの
も多い。そして実際に、今回のようなケースでは 3 番目の方法、いわゆる
「バスアドレス」と呼ばれる方法を使うべきなんだ」
原則として、3 つのアドレス指定方法でのメモリの表し方は以下のようになる。
(これは「実メモリ」、すなわち通常の RAM を指している。詳細は最後の方を見て欲しい)
- CPU が変換していない。これは「物理」アドレスだ。物理アドレス 0 番は、
CPU がメモリのバス(回路)にオール 0 を出力したときに、CPU が参照する
アドレスだ。
- CPU が変換したアドレス。これは「仮想」アドレスである。そして、完全に
CPU 内部のアドレスであり、CPU が適切な変換を施して「CPU が変換していない」
アドレスへと対応づけをおこなう。
- バスアドレス。これは CPU ではなく*他の*デバイスから見た時のメモリの
アドレスである。現在、各デバイスがそれぞれの方法によってメモリをア
クセスすることで、理論上は多くの異なったバスアドレスを取り得る。
しかし、必要に迫られない限りわざわざ仕組みを複雑にしようとする
ようなハードウエア技術者はまずいないので、すべての外部ハードウエア
は、メモリーを同じ方法で参照していると思ってよい。
さて、通常の PC のバスアドレスは、厳密に物理アドレスに等しい。そして、本
当に事は単純である。しかしながら、そうした単純さというのはメモリーとデ
バイスがおなじアドレス空間を共有しているから生まれているので、それが
一般的に他の PCI/ISA セットアップの上でも必ずしも真であるとはいえない。
では実例を示そう。プレップ機(PReP:PowerPC Reference Platform)の場合、
CPU はメモリマップを以下のように参照する。(これはメモリから見た場合):
0-2 GB "実メモリ"
2 GB-3 GB "システム IO" (inb/out や同様の x86 のアクセスをおこなう)
3 GB-4 GB "IO メモリ" (IO バスを介した共有メモリ)
さて、とても簡単だと思っただろうが、あなたが同じことをデバイス側
から考えた場合には逆のことになる。物理メモリのアドレス 0 は、実際には
どの IO マスターにとっても、2GB 以上のアドレスを意味しているのだ。
それで、CPU はどれかバスマスターのデバイスに物理メモリの 0 番に書いても
らおうと思った時、それはメモリアドレスの 0x80000000 を与えなければな
らなくなってしまう。
そういうわけで PPC に対して実際にカーネルのマッピング次第で異なると
はいうものの、たとえばこんな感じの設定に落ち着くだろう。
物理アドレス: 0
仮想アドレス: 0xC0000000
バスアドレス: 0x80000000
このすべてのアドレスは同じことを表している。これは単に違う変換を通して
同じものを見ているだけなのだ。
同じように、Alpha 上では、通常の変換は
物理アドレス: 0
仮想アドレス: 0xfffffc0000000000
バスアドレス: 0x40000000
である。
(しかし Alpha でも、ものにより、物理アドレスとバスアドレスが同
じものを示しているようなものもある)
いずれにせよ、これらの全ての変換を見つけるには、次のようにすればよい。
#include <asm/io.h>
phys_addr = virt_to_phys(virt_addr);
virt_addr = phys_to_virt(phys_addr);
bus_addr = virt_to_bus(virt_addr);
virt_addr = bus_to_virt(bus_addr);
では、いつ、これが必要になるか?
実際にカーネルからポインタでアクセスしようとする時には、あなたは
*仮想*アドレスが欲しくなる。そこで、以下のようにやればいい。
/*
* これは、コントローラと通信するためのハードウエア "mailbox"
* コントローラは、直接この領域を見ることができる。
*/
struct mailbox {
__u32 status;
__u32 bufstart;
__u32 buflen;
..
} mbox;
unsigned char * retbuffer;
/* コントローラからアドレスを取得する。*/
retbuffer = bus_to_virt(mbox.bufstart);
switch (retbuffer[0]) {
case STATUS_OK:
...
その一方、コントローラーにバッファーのアドレスを与える時には、バスア
ドレスが欲しくなる。つまり、
/* コントローラに検出状態を"sense_buffer"にいれるよう依頼する */
mbox.bufstart = virt_to_bus(&sense_buffer);
mbox.buflen = sizeof(sense_buffer);
mbox.status = 0;
notify_controller(&mbox);
こんな風になる。
そして、一般には物理アドレスを使いたくなることは*絶対*ない。なぜなら、
CPU を通して、それを使うことは不可能だからだ(CPU は変換された仮想アド
レスを介してのみ利用可能だ)。そしてバスマスターデバイスからそれをつか
うことができない。
ではなぜ、われわれはそれにもかかわらず物理アドレスを意識しないといけ
ないのか。通常のコードではそれほど出てくるものでもないが、物理アドレス
が必要なケースが確かにあるのだ。たとえばメモリマッピングを使用する際に
物理アドレスは必要とされる。たとえば、"remap_page_range()" というメモリ
管理関数は、メモリの物理アドレスを別のアドレスへマップしなおすからだ。
(メモリ管理層は、CPU の外にあるデバイスについて知らない。そのため、「バ
スアドレス」やその関連について知らなくてもよいはずだ)
注意!! 上記の記述は、全体の問題の一部でしかない。上記は単に"実メモ
リ" つまりは CPU メモリ(RAM)について言及しているだけである。
それとは完全に異なるメモリの種類がある。それは PCI や ISA バスにおける
「共有メモリ」だ。それは一般に RAM ではなくて、ネットワークカードなど
におけるパケットバッファーのようなものでありえる。(とはいえビデオグ
ラフィックカードの場合には、フレームバッファーとして使われる普通の
DRAM であるけれども)
このメモリは「PCI メモリ」とか「共有メモリ」とか、「IO メモリ」とか、
あるいはそれに類した名称で呼ばれる。それにアクセスする方法は一つし
かない。つまり、readb/writeb とその関連の関数である。あなたは、一切それ
らのメモリのアドレスを取り扱う必要はない。なぜなら本当にそのようなアド
レスに対してできることは何もないからだ。どういうことかというと、「実
メモリ」と概念上おなじメモリ空間というわけでは全くないし、したがってそ
れを指すポインタを逆参照することもできはしない(かなしいことに x86 ア
ーキテクチャーにおいては、それは同じメモリ空間を指している。つまり x86
ではポインタの逆に参照をみて働かせることができる。しかしそれは互換性の
面で良くない)。
そのようなメモリには、以下のように対処できる。
- 読み込み:
/*
* 最初の 32bit を 0xC0000 にある ISA メモリから読み込む。
* これは、DOS でいうところの、C000:0000 を意味する。
*/
unsigned int signature = readl(0xC0000);
- remap と書き込み:
/*
* 0xFC000000 にある 1MB のサイズのフレームバッファ PCI メモリ領域を
* remap する。それにより、その領域にアクセスすることができる。
* 直接アクセスできるのは、640KB-1MB の領域だけなので、それ以外は
* remap しないといけない。
*/
char * baseptr = ioremap(0xFC000000, 1024*1024);
/* 'A' を領域のオフセット 10 に書き込む */
writeb('A',baseptr+10);
/* ドライバを unload するときにマップを解除する */
iounmap(baseptr);
- コピーおよび消去:
/* get the 6-byte Ethernet address at ISA address E000:0040 */
memcpy_fromio(kernel_buffer, 0xE0040, 6);
/* write a packet to the driver */
memcpy_toio(0xE1000, skb->data, skb->len);
/* clear the frame buffer */
memset_io(0xA0000, 0, 0x10000);
OK、それでポータブルに IO をアクセスするための基本を押えた。質問は?
コメントは? あなたは、上記でもう充分すぎるくらい複雑だと思っただろう。
しかしある日、あなたは自分の目の前に 500MHz の Alpha マシンがおかれて、ドラ
イバーワークを楽しむことになるかもしれないよ ;-)
注意。カーネルバージョン 2.0.x (とその前)は、間違えて ioremap() 関数を
vremap() と呼んでいた。ioremap() が正しい名前であるが、最初にオリジナル
を私が書いた時には、ちゃんと考えなかった。両方をサポートしないといけ
ない人たちは、以下のように
/* 古いばかな命名をサポートする */
#if LINUX_VERSION_CODE < 0x020100
#define ioremap vremap
#define iounmap vfree
#endif
ソースファイルの先頭に、このようなコードをおいて、2.0.x システムにおいても
ただしい名前をつかうようにできる。
そして、上の記述はその実際よりも悪く聞こえる。多くのリアルドライバーは
本当にこれらの複雑なことをすべて行なっておらず、(あるいはむしろ、
エラー処理やタイムアウト処理などにおいてほどには、それほど実際の IO アク
セスにおいては複雑ではないのである。)一般に、ドライバーを修理するのは
大変なことではない。そして多くの場合、コードは後の方が実際に良く見え
る。
unsigned long signature = *(unsigned int *) 0xC0000;
対
unsigned long signature = readl(0xC0000);
Linuxのバージョン2 は実際、より理解しやすくなったとおもうんだけど、ちがう?
Linus
[翻訳: 三浦広志 miura@blue.gr.jp]
Linux カーネル 2.2 付属文書一覧へ戻る