NetBSDのための機種非依存DMAフレームワーク


このテキストは BSD Magazine No.13 の特集記事の一つとして掲載された、Jason R. Thorpe氏による bus_dma 論文の和訳です。Webでの公開にあたりhtml化を含め一部の表記は見直していますが、記事の内容は当時のままです。また、原文の内容自体が執筆当時の NetBSD に対応する内容であり、最新のNetBSDバージョンにおいては対応しない部分も多々含まれます。

元の文章が学術論文であるとはいえ、正直言って訳の質としては雑誌記事として掲載できるレベルには達していなかったという反省部分もありますが、そもそもがデバイスドライバを実装できるレベルの読者を対象にした内容ですし、このまま埋めておくのももったいないので今回適当に修正およびhtml化して上げることにしました。

原文のソースは NetBSDソースツリーの src/share/doc/papers/ 以下から、原文のPDF版は www.NetBSD.org の Kernel Programming FAQ のリンク から入手できます。


A Machine-Independent DMA Framework for NetBSD

Jason R. Thorpe[注1]
Numerical Aerospace Simulation Facility
NASA Ames Research Center

NetBSDのための機種非依存DMAフレームワーク

Jason R. Thorpe[注1]
数値航空宇宙シミュレーション施設
NASAエイムスリサーチセンター

概要

移植性の高いカーネルを実装する際の課題のうちの一つに、意味的に類似してはいるが多くの場合機種依存となる実装を持つ操作に対して、適切な抽象化を作り出すことが挙げられる。これは、PCIバスのような一般的なアーキテクチャ上の特徴を共有する現代の計算機においては特に重要である。

本論文は、NetBSD/alphaとNetBSD/i386カーネルにおいて、機種に依存しないDMAマッピングの抽象化が必要である理由、そのような抽象化のための設計考察、そしてこの抽象化の実装について解説する。

1. 序文

NetBSDは、高い移植性を持つ現代的なUNIXライクオペレーティングシステムであり、現在9つのプロセッサアーキテクチャを含む18のプラットホームで動作している。Alphaとi386[注2]を含むこれらのプラットホームの一部は、一般的なアーキテクチャ上の特徴として PCIバスを共有している。異なるプラットホームの間で各種の PCIデバイスのためのデバイスドライバを共有するためには、バス空間の詳細を隠す抽象化を考案する必要がある。ここで隠されなければならない詳細は、2つの階層に分類することができる。一つはバス上のデバイスに対するCPUのアクセス(bus_space)であり、もう一つはホストメモリに対するデバイスのアクセス(bus_dma)である。ここでは、我々は後者を議論する。bus_spaceは、それ自体が複雑な内容であり、本論文で扱う範囲を超えている。

DMAという視点において、核となるデバイスドライバーから隠されるべき詳細として2つの主要な階層がある。第1の階層(ホスト詳細)は、システムメモリの物理的マッピング(とそのようなマッピングの結果として使用されるDMA機構)とキャッシュに対する処置といった問題を扱う。第2の階層(バス詳細)は、デバイスが接続されるバスに固有な仕様や制約(例えばDMA転送やアドレス範囲の制約)に関連した問題を扱う。


[注1]Jason R.ThorpeはMRJテクノロジー・ソリューション社の従業員である。この作業はNASA契約NAS2-14303によって資金助成されている。

[注2]ここで用語「i386」は、i486、Pentium、Pentium ProとPentium IIを含む、386クラス及びその上位のプロセッサのすべてを指すのに用いられる。


1.1. ホスト・プラットホーム詳細

上で実例として挙げたプラットホームでは、DMAの実行において少なくとも3つの異なる機構が用いられる。第1はi386プラットホームによって使われる。この機構は、「ウィズィウィグ(WYSIWYG; What You See Is What You Get=見えるものをそのまま得る)」として説明される。デバイスがDMA転送を実行する際に用いるアドレスは、ホストCPUが当該のメモリ位置にアクセスするのに使うものと同じアドレスである。

DMAアドレス	ホストアドレス

	[図の中身は原文参照]

	図1 WYSIWYG DMA

第2の機構はAlphaによって使われるが、これは第1の機構に非常に類似している。ホストCPUが当該のメモリー位置にアクセスするために使うアドレスは、DMAのためにデバイスバス上にダイレクトマップされたホストメモリのベースアドレスからのオフセット値である。

 DMAアドレス	ホストアドレス

	[図の中身は原文参照]

	図2 ダイレクトマップDMA

第3の機構はスキャッタギャザマップDMAであり、DMAアドレスからホストメモリの物理アドレスへの変換を行うMMUを使用する。この機構もAlphaにおいて用いられる。というのも、Alphaプラットホームでは、現在出回っているほとんどの PCIデバイスにおいてサポートされる32ビットアドレス空間よりもはるかに大きなアドレス空間をサポートする必要があるためである。

 DMAアドレス	ホストアドレス

	MMU

	[図の中身は原文参照]

	図3 スキャッタ・ギャザ・マップDMA

上記の第2および第3のDMA機構は、AlphaではDMAウィンドウを使用することにより同じように扱われる。特定のプラットホーム上で PCIバスを実装するための ASIC は、これらの DMAウィンドウを少なくとも2つ備えている。各々のウィンドウは、ダイレクトマップDMAもしくはスキャッタギャザマップDMAのために設定される。これらのウィンドウは、実行されるDMA転送のタイプ、バスタイプおよびアクセスされるホストメモリの物理アドレスの範囲により選択される。

これらの概念は、上に挙げた以外のプラットホームおよび PCI以外のバスにもあてはまる。同様の問題は、 DECstation や初期の Alpha システムの上で使われた TurboChannel バスでも、DEC MIPS や VAX ベースのサーバの上で使われた Qバス でも存在する。

ホストシステムのキャッシュの管理方法も、DMAの実行を行うデバイスにとって重要である。いくつかのシステムでは、キャッシュコヒレントDMA[訳注1]が可能である。そのようなシステムの上では、キャッシュは多くの場合ライトスルー(すなわちストアデータはキャッシュとホストメモリの両方に書き込まれる)であるか、ダーティーなキャッシュラインが存在するメモリへのアクセスを検出することができる特殊なスヌープロジック(アクセス検出機構)を備えている(それによりキャッシュは自動的にフラッシュされる)。他のいくつかのシステムでは、キャッシュコヒレントDMAが不可能である。これらのシステムでは、メモリからデバイスへのDMA転送の前のデータキャッシュのフラッシュをソフトウェアが明示的に行う必要があり、デバイスからメモリへのDMAの前にも転送により内容が古くなるキャッシュラインの無効化についても同様にソフトウェアが明示的に行う必要がある。


[訳注1]DMA転送においてキャッシュとホストメモリとの間の一貫性(coherency)が保たれることが保証されていること。


1.2. バス詳細

単一のバスに対するプラットホーム特有の DMA 詳細を隠すことに加えて、複数のバスに接続されるデバイスにとってはできるだけ多くのデバイスドライバコードを共有することが望ましい。よい実例として BusLogic の SCSI アダプタシリーズが挙げられる。このデバイスシリーズには、ISA、EISA、VLバスそして PCI のものがある。デバイスの検出や割り込みの初期化など各バスに固有の部分はいくらか存在するが、これらの一連のデバイスを動作させるための大部分のコードはそれぞれのバスのデバイスにおいても同一である。

BusLogic の SCSI アダプタシリーズは、バスマスタと呼ばれるものの典型例である。すなわち、デバイスは DMA 転送においてすべてのバスハンドシェイクとホストメモリアクセスを自身で実行する。転送においてそれ以外の第三者を必要としない。そのようなデバイスが DMA 転送を実行する際には、 DMA アドレスをバスのアドレスに送出し、バスのフェッチまたはストア操作を実行し、アドレスをインクリメントし……といった動作を転送が終了するまで行う。デバイスはバスアドレスの信号線を使うので、デバイスがアクセスすることができるホストの物理アドレスの範囲はそのアドレスのビット数によって制限される。PCI バスは少なくとも 32ビットのアドレスを持つため、 PCIバス上のデバイスは 32ビットアーキテクチャ(例えばi386)の物理アドレス空間全体にアクセスすることができるだろう。しかし ISAは 24ビットのアドレスしか持たない。これは、デバイスが直接アクセスできる物理アドレス空間は 16Mバイトしかないということを意味する。

このアドレス制限問題に対する一般的な解決策は、 DMAバウンス (DMA bouncing)[訳注2]として知られているテクニックである。この手法では、デバイスがアクセス可能な物理アドレス範囲に位置する別のメモリ領域が必要になる。これはバウンスバッファと呼ばれているものである。メモリからデバイスへの転送においては、データは CPUによってバウンスバッファにコピーされ、それから DMA操作が開始される。反対に、デバイスからメモリへの転送においては、まず DMA操作が開始され、そして DMA操作が完了した後に CPUがデータをバウンスバッファからコピーする。

実装は簡単であるが、DMAバウンスはアドレス制限問題を解決するための最適手段とは言えない。たとえば、Alphaにおいては、DMA範囲外のメモリの物理アドレスをデバイスからアクセス可能な範囲内の DMAアドレスに変換するのにスキャッタギャザマップDMAを使用することができる。この解決策は、データのコピーが不要となるため性能も良い場合が多く、かつメモリ使用量の観点においても有利である。

BusLogic の SCSI の例へ戻って、コアとなるデバイスドライバにダイレクトマップ、スキャッタギャザマップおよび DMAバウンスの詳細な情報を記述することは望ましくない。これらの詳細を隠し、実際に使われる DMA機構に関与しない、一貫性のあるインターフェースを提供する抽象化が必要である。


[訳注2]「bouncing」を強いて日本語にすれば「中継」となるが、ここではDMAにおける用語としてそのまま「バウンス」という訳語を使う。


2. 設計考察

ホストおよびバスの詳細を隠すことは実際に非常に簡単である。 WYSIWYG およびダイレクトマップ DMA機構の扱いは取るに足らない。マシン依存部のコード層において保持される状態を用いれば、スキャッタギャザマップDMAを扱うことも非常に容易である。キャッシュの存在とそれに対する処置についても、4つの一組の「同期(sync)」操作で扱うのが簡単である。キャッシュを適切に処理することができれば、DMAバウンスもそれをDMAコヒレントでないキャッシュと同様捉えれば概念的には簡単である。残念なことに、これらの操作は個別に実行するのが非常に簡単であるにもかかわらず、従来のカーネルはこれらの操作に十分な抽象化されたインターフェースを提供していない。これは、従来のカーネルにおけるデバイスドライバは明示的にそれぞれのケースを扱わなければならないことを意味する。

これらの操作に対するインターフェースに加えて、包括的DMAフレームワークは、データバッファ構造とDMA-safeメモリ操作についても考慮する必要がある。

2.1. データバッファ構造

BSDカーネルは、データバッファを記述するのに用いられる 3つの大きく異なる構造体を持つ。第1は、仮想空間における単純な線形バッファである。これはたとえばファイルシステムバッファキャッシュを実装するのに用いられるデータ領域や、汎用カーネルメモリーアロケータによって割り当てられる種々のバッファである。第2は、mbuf チェーンである。mbuf は主にプロセス間通信やネットワーキングを実装するコードに使われる。それらの構造(チェーンで1つに連結される小さなバッファ群)はメモリ断片化を減らし、パケットヘッダを付加することを容易にする。第3は、uio 構造体である。この構造体は、カーネルアドレス空間または特定のプロセスのアドレス空間にソフトウェアスキャッタギャザを実装している。これは一般的に read(2)write(2) システムコールにより使用される。デバイスドライバは複数の単純なリニアバッファの集合として2つのより複雑なバッファ構造を扱うことも可能であるが、これはソースコードメンテナンスの観点から望ましくない。特にエラー処理の観点から、これらのデータバッファ構造を扱うコードは、複雑なものになる可能性がある。

カーネルアドレス空間にマップされたメモリに対するDMA転送という要求に加え、ユーザープロセスにおいてもプロセスアドレス空間にマップされたメモリ領域にデバイスが直接DMA転送を行う手段を提供できるよう最適化されたI/Oインターフェースを実装することも現代的なOSでは一般的である。この機能はキャラクタデバイスのI/Oに対してはユーザーバッファをカーネルアドレス空間に二重にマッピングすることで部分的に提供されているが、このインターフェースは十分に一般的とは言えず、またカーネルリソースも消費する。これは uio がプロセスのアドレス空間内のバッファのアドレス指定ができるという点で、 uio 構造にいくぶん関連がある。しかし、いくつかのアプリケーションにおいては、リニアバッファのような別のデータ構造を使うほうが望ましいだろう。これを実装するために、DMAマッピングのフレームワークは、各プロセスの仮想メモリの構造体に対するアクセス手段を備えていなければならない。

また、いずれのアドレス空間にもマップされないバッファに対するDMA転送もおそらく必要であろう。わかりやすい例としては、スクリーンキャプチャデバイスである。多くの場合、ビデオ画像をキャプチャするこれらのデバイスは、キャプチャした画像データを記憶するために物理的に連続した巨大なメモリ領域を必要とする。いくつかのアーキテクチャでは、仮想アドレス空間のマッピングは重い処理となる。アプリケーションは、デバイスに大きいバッファを提供してデバイスが連続的にバッファを更新するのを許可し、ある一定の期間のみバッファの一部の範囲のマップだけを要求するかもしれない。バッファ全体が仮想アドレス空間にマップされる必要はないので、DMAフレームワークはDMA転送において未加工(raw)[訳注3]の、マップされないバッファを使うためのインターフェースを提供する必要がある。


[訳注3]ここでは「raw」という単語に対して「未加工の」という訳語を当てることにする。


2.2 DMA-safeなメモリ処理

包括的DMAフレームワークは、いくつかのメモリ操作機能についても提供する必要がある。これらのうち最も明らかなものは、DMA-safeメモリを割り当てる(そして解放する)手段である。用語「DMA-safe」は、メモリが持つ一連の属性を述べる手段である。ます、DMA-safeメモリはバスの制約条件においてもそのアドレスが指定可能でなければならない。DMA-safeメモリは前述の条件下で、かつ呼び出し元によって指定される物理的セグメントの数を超えないように割り当てられなければならない[注3]

カーネルがDMA-safeメモリにアクセスするために、このメモリをカーネル仮想記憶空間にマップする手段が必要である。これは、1つの例外はあるがかなり簡単な操作である。キャッシュコヒレントDMAを備えていないプラットホームでは、キャッシュフラッシュは、非常に高価である。しかし、メモリの仮想空間へのマップをキャッシュ無効と指定することや、キャッシュ無効のダイレクトマップアドレスセグメントを通して物理メモリをアクセスすることが可能な場合もある。これらの状況に対応するために、このメモリの使用者が高価なデータキャッシュフラッシュを避けたいということを示すヒントをメモリマッピング関数に対して提供することができる。

プロセスアドレス空間に対する最適化されたI/Oを容易にするため、プロセスにDMA-safeメモリ領域をマップする手段を提供することが必要である。このための最も便利な手段は、デバイスドライバーの mmap() エントリーポイントを経由して行う方法である。したがって、DMAマップのフレームワークは、VMシステムのデバイスページャ[注4]とやりとりするための手段を持たなければならない。

完全なDMAフレームワークの設計においては、これらすべての要求事項について考慮をする必要がある。もし可能であればフレームワークは意味的に同じとなる操作や概念を統合するかもしれないが、いずれにせよこれらの問題の全部に対処しなければならない。次の節では、そのフレームワークによって提供されるインターフェースを解説する。


[注3]この表現はやや正確ではない。実際の制約条件はメモリがマップするであろうDMAセグメントの数である。しかし、これは通常割り当てられたメモリを構成する物理メモリのセグメント数と一致する。

[注4]デバイスページャは、プロセスのアドレス空間にメモリマッピングデバイスのためのサポートを提供する。


3. bus_dmaインターフェース

以下に述べるのは bus_dma の記述である。これは NetBSD における機種非依存のバスアクセスインターフェースの DMA部分であり、bus.h[注5] として一般に参照される。インターフェースの DMA部分は、3つの DMA特有のデータ型、そして、13の関数コールから成る。bus_dmaインターフェースはまた、2つのデータ型を bus_space インターフェースと共有する。

bus_dma の機能インターフェースは、マッピングコールとメモリ操作コールの 2つのカテゴリーに分けられる。関数コール自身は、 cpp(1) マクロとして実装される場合もある。


[注5]この名前はインターフェースを外部参照するためのインクルードファイルの名前に由来する。


3.1. データ型

bus_space インターフェースと共有される 2つのデータ型のうち第1のものは bus_addr_t 型である。これは CPUアクセスもしくは DMAのために使われるデバイスのバスアドレスを記述し、システム上存在する最大のバスアドレスを指定できるだけの十分な大きさを持つ必要がある。第2は bus_size_t 型である。これはバスアドレス範囲のサイズを記述する。

必要なホストとバスの組み合わせにおける DMAの実装は、 bus_dma_tag_t によって記述される。この不透明(opaque)型[訳注4]は、機種依存部のコードによってバスのオートコンフィギュレーション機構に渡される。バス階層はそれを順番に下位層のデバイスドライバへ渡していく。このタグは、インターフェースにおけるすべての関数への1番目の引数である。

個々の DMAセグメントは、 bus_dma_segment_t によって記述される。この型は、対外的にアクセス可能な2つのメンバを持つ構造体である。1つ目のメンバ ds_addr はDMAセグメントのアドレスを持つ bus_addr_t である。2つ目のメンバ ds_len はセグメントの長さを持つ bus_size_t である。

第3の、おそらく最も重要なデータ型は、 bus_dmamap_t である。この型は、個々の DMAマップの実態を表現する構造体へのポインタである。構造体は、3つのパブリックなメンバを持つ。第1のメンバ dm_mapsize は、(もしそれが有効であれば)マップの長さを表す bus_size_t である。 dm_mapsize が 0であればそのマップが無効であることを示す。第2のメンバ dm_nsegs は、マップを構成するDMAセグメントの数を示す int である。第3のパブリックメンバ dm_segs は、 bus_dma_segment_t 構造体の配列への配列またはポインタである。

データ型に加えて、bus_dmaインターフェースではインターフェースの関数の一部に渡す各種のフラグが定義されている。これらのフラグのうちの2つ、 BUS_DMA_WAITOKBUS_DMA_NOWAIT は、リソースが入手可能になるのを待つことが許可されるか否かを関数に対して指定する[注6]。また、4つの予約されたフラグ BUS_DMA_BUS1 から BUS_DMA_BUS4 も存在する。これらのフラグは個々のバス階層のために予約されている。バス階層はそのバス固有の特別な意味を定義する必要がある場合がある。この一つの例としては、32ビットDMAアドレスを利用する VLバスデバイスの機能がある。論理的にはカーネルはそれらの VLバスデバイスを ISAバスに接続されているように扱うが、 VLバスデバイスには他の ISAデバイスの持つアドレス幅による制約がない。予約フラグは、前述のような特別なケースをバス毎に規定することを可能にする。


[訳注4]「opaque」な型とは、その値の実際の中身が使われるのが下位層のルーチンのみであり、上位層ではその値の中身や型がどのようなものであるか関知する必要がない型のことを言う。ここでは「不透明(型)」という訳語を当てる。

[注6]デバイスの割込みを扱う際に使われる割込みコンテキストとは対照的に、カーネルがプロセスコンテキストで動いているならばウェイト(もしくは「ブロッキング」とも呼ばれる)は許可される。


3.2 マップ関数

DMAマップを操作する bus_dma インターフェースには 8つの関数がある。これらは、DMAマップを作成および破棄する関数、DMAマップをロードおよびアンロードする関数、マップを同期する関数に分類できる。

始めの 2つの関数は create および destroy の 2つである。 bus_dmamap_create() 関数は、DMAマップを作成し、そのDMAマップを与えられたパラメータによって初期化する。パラメータに含まれるのは、DMAマップがマップする最大の DMA転送サイズ、DMAセグメントの最大数、与えられたセグメントの最大サイズ、そしてDMAアラインメント制約である。標準のフラグに加えて、 bus_dmamap_create() はフラグ BUS_DMA_ALLOCNOW フラグも取る。このフラグは、最大サイズの転送をマップするのに必要なすべてのリソースを、DMAマップが作成される時に割り当てられる必要があることを示す。これは、DMAマップを割り込みコンテキスト内のようにブロックが許されない箇所で毎回ロードしなければならないような場合に有効である。 bus_dmamap_destroy() 関数は、DMAマップを無効にし、割り当てられていたリソースを解放する。

次の 5つの関数は、大きく loadunload に分類される。2つの基本関数は bus_dmamap_load()bus_dmamap_unload() である。前者は、線形バッファに対する DMA転送をマップする。この線形バッファは、カーネルもしくはプロセスの仮想アドレス空間にマップされる。後者は、 DMAマップにロードされていたマップをアンロードする。マップが作られた時に BUS_DMA_ALLOCNOW フラグが指定されていれば、 bus_dmamap_load() は資源割り当てをブロックせず失敗することはない。同様に、DMAマップがアンロードされるときDMAマップのリソースは解放されない。

基本的な bus_dmamap_load() によって扱われる線形バッファに加えて、インターフェースによって処理される 3つの別のデータバッファ構造体がある。 bus_dmamap_load_mbuf() 関数は、mbufチェーンに対して作用する。それぞれのデータバッファは、カーネル仮想記憶空間内にあると仮定される。 bus_dmamap_load_uio() 関数は uio 構造体に対して作用する。関数はそのデータが存在するアドレス空間についての情報はその uio 構造体から取り出される。最後に、 bus_dmamap_load_raw() 関数は、いずれの仮想アドレス空間にマップされない、未処理の(raw)メモリに対して作用する。これらの関数でロードされるすべてのDMAマップは、 bus_dmamap_unload() 関数でアンロードされる。

最後に、マップ同期関数には 1つの関数 bus_dmamap_sync() がある。この関数は、キャッシュおよびDMAバウンスを処理するのに必要な4つのDMA同期操作を行う。4つの操作は以下の通りである。

BUS_DMASYNC_PREREAD
BUS_DMASYNC_POSTREAD
BUS_DMASYNC_PREWRITE
BUS_DMASYNC_POSTWRITE

方向は、ホストメモリの視点からのものである。つまり、デバイスからメモリへの転送が READ であり、メモリからデバイスへの転送が WRITE である。同期操作はフラグで表されるので、単一の関数呼び出しで READWRITE の操作を同時に行うことが可能である。これは、特にデバイス制御ディスクリプタのマッピングを同期させる場合に有効である。 PRE 操作と POST 操作を混在させることはできない。

DMAマップ引数と操作を指定する引数に加えて、 bus_dmamap_sync() はオフセットと長さの引数も取る。これは一部分のみの同期をサポートするために用いられる。デバイスの制御ディスクリプタが DMAで転送される場合には、マップ全体を同期させることは無駄があったり、ときには他の制御ディスクリプタを破壊する恐れもあり望ましくない可能性がある。マップ全体を同期は 0のオフセットとマップの dm_mapsize によって指定される長さを渡すことで実行可能である。

3.3. メモリ操作関数

bus_dmaインターフェースのDMA-safeメモリを処理する関数はメモリ割り当てとメモリーマッピングの2つに分類される。

メモリ割り当てに分類される第1の関数 bus_dmamem_alloc() は、指定された属性を持つメモリを割り当てる。指定できる属性は、割り当てるメモリ領域の大きさ、割り当てでのそれぞれのセグメントのアライメント、境界制約、そして割り当てに使用できるDMAセグメントの最大数である。関数は、引数として渡された bus_dma_segment_t の配列に値を返し、配列内で有効なセグメント数を示す。このインターフェースによって割り当てられるメモリは、いずれの仮想アドレス空間にもマップされていない、未加工の(raw)メモリ[注7]である。割り当てたメモリが使用されなくなった場合は、 bus_dmamem_free() 関数で解放することができる。

カーネルまたはユーザープロセスがこれらのメモリにアクセスするためには、カーネルアドレス空間またはプロセスアドレス空間のいずれかにマップされていなければならない。これらの操作は、DMA-safeメモリ処理関数群のうちのメモリマッピングに分類される関数によって行われる。 bus_dmamem_map() 関数は、指定されたDMA-safeな未加工(raw)のメモリをカーネルアドレス空間にマップする。マッピングのアドレスは、参照名渡しポインタに返り値としてセットされる。このようにマップされたメモリは、 bus_dmamem_unmap() を呼ぶことでアンマップ可能である。

DMA-safeな未加工(raw)のメモリは、デバイスドライバの mmap() エントリーポイントを通してプロセスのアドレス空間にマップされる場合もある。これを行うため、VMシステムのデバイスページャは、マップする各ページ毎に繰り返しドライバを呼ぶ。ドライバは、ユーザーによって指定された mmap オフセットをDMAメモリオフセットに変換し、そしてメモリオフセットを pmap モジュールによって解釈される不透明型(opaque)値に変換するために bus_dmamem_mmap() 関数を呼び出す。デバイスページャは、 mmap クッキー(cookie)[訳注5]を物理ページアドレスに変換するために pmap モジュール[注8]を起動し、そしてその物理ページアドレスがプロセスのアドレス空間にマップされる。

現状では、 mmap されている領域がアンマップされることを仮想記憶システムが明示する方法、およびデバイスドライバが仮想記憶システムに対して mmap された領域を強制的にアンマップしなければならない(例えば活線挿抜可能なデバイスがシステムから取り除かれた場合)ということを指示するための手段はない。これは、大きなバグと考えられて、そして、 NetBSD の仮想記憶システムの将来のバージョンで対処されるであろう。この問題に対する変更が行われるとすれば、 bus_dma インターフェースはそれに応じて整備される必要がある。


[注7]これは、このインターフェースによって割り当てられたメモリに対してDMA転送マッピングの際に使われるインターフェースはbus_dmamap_load_raw()であることを意味する。

[訳注5]ここで使われている「cookie」とは、訳注4で述べたopaqueな型の変数に対して実際に代入された値のことを指す。ここではそのまま「クッキー」という訳語を当てる。

[注8]pmapモジュールは、NetBSD仮想記憶システムのマシン依存の階層である。


4. NetBSD/alpha および NetBSD/i386 における bus_dma の実装

この節は、2つの NetBSD ポート、 NetBSD/alpha と NetBSD/i386 における bus_dma 実装の解説である。読者がインターフェースによって分類して抜粋した代表的な形式の詳細について理解しやすいように、横並びの比較の形で示す。

4.1. プラットホーム要求事項

NetBSD/alpha は現在 6種類の PCIバスの実装をサポートしており、それぞれは異なる DMA実装を持つ。 NetBSD/alpha の相当に複雑な bus_dma の実装におけるデザインアプローチを理解するためには、それぞれのバスアダプタの相違点を理解することが必要である。これらのアダプタの一部は類似した内容および特徴を持つが、それぞれに対するソフトウェアインタフェースは極めて異なっている。(PCIに加えて、 NetBSD/alpha は DEC 3000 のモデルにおける 2種類の TurboChannel DMA 実装についてもサポートしている。簡単にするため、ここでは論議を PCIと関連するバスに限定する。)

NetBSD/alpha によってサポートされた第1の PCI実装は、 DECchip 21071/21072(APECS)[文献1]であった。これは、DECchip 21064(EV4)、21064A(EV45)プロセッサで使用されるように設計されている。この PCIホスト・バスアダプタが使われているシステムは、 AlphaStation 200、AlphaStation 400 と AlphaPC 64 システムであり、またいくつかの AlphaVME システムでも使用された。 APECS は、最大 2つの DMAウィンドウをサポートしており、これらの DMAウィンドウはダイレクトマップもしくはスキャッタギャザマップ操作のために使われる。スキャッタギャザページテーブルにはホストのRAMを使用する。

NetBSD/alpha によってサポートされた第2の PCI実装は、 Low Cost Alpha(LCA) プロセッサファミリーの DECchip 21066[文献2] と DECchip 21068 上で使われる内蔵I/Oコントローラであった。このプロセッサファミリーは、 AXPpci33、 Multia AXP システムで使われており、ある AlphaVME システムでも同様に使われていた。 LCAは、最大 2つの DMAウィンドウをサポートしており、これらの DMAウィンドウはダイレクトマップもしくはスキャッタギャザマップ操作のために使われる。スキャッタギャザページテーブルにはホストのRAMを使用する。

NetBSD/alpha によってサポートされる第3の PCI実装は、 DECchip 21171(ALCOR)[文献3]、21172(ALCOR2) と 21174(Pyxis) であった[注9]。これらの PCIホストバスアダプタは、 DECchip 21164(EV5)、21164A(EV56) と、 AlphaStation 500、AlphaStation 600 と AlphaPC 164 を含む、21164PC(PCA56) プロセッサと Digital Personal Workstation ベースのシステムで使われている。ALCOR、ALCOR2 と Pyxis は最大 4つの DMAウィンドウをサポートしており、これらの DMAウィンドウはダイレクトマップもしくはスキャッタギャザマップ操作のために使われる。スキャッタギャザページテーブルにはホストRAMを使用する。

NetBSD/alpha によってサポートされる第4の PCI実装は、 Digital DWLPA/DWLPB であった[文献4]。これは、AlphaServer 8200、8400 のシステムの上で使われる TurboLaser-to-PCI ブリッジ[注10]である。ブリッジは、KFTIA(内部) または KFTHA(外部) I/Oアダプタを介して TurboLaser システムバスに接続されている。前者は、1つの内蔵 DWLPx と1つの外部 DWLPx をサポートする。後者は、最大4つの外部 DWLPx をサポートする。 TurboLaser システムバスの上には複数の I/Oアダプタが存在可能である。それぞれの DWLPx は、最大 4台の主 PCIバスをサポートして、ダイレクトマップDMAまたはスキャッタギャザマップDMAのために使用される3つのDMAウィンドウを持つ。これらの3つのウィンドウは、 DWLPx に接続されるすべてのPCIバスにおいて共有される。DWLPx は、スキャッタギャザページテーブルのためにホストRAMを使わない。その代わりに DWLPx はオンボード SRAM を使用しており、その SRAM を DWLPx に接続されるすべての PCIバスで共有する必要がある。これは、これらのシステムで採用されているストアアンドフォワードアーキテクチャーでは DMAページテーブルのアクセスにおけるレーテンシが非常に大きくなるためである。 DWLPA は 32K のページテーブル SRAM を持ち、 DWLPB は 128K の SRAM を持つ。DWLPx はページテーブル SRAM に対するアクセスをスヌープすることができるので、この PCI実装の上では明示的なスキャッタ・ギャザTLBの無効化は必要でない。

NetBSD/alpha によってサポートされる第5の PCI実装は、 Avalon A12 Scalable Parallel Processor[文献5]の A12C PCIバスであった。この PCIバスは、副I/Oバス[注11]であり、 mezzanine フォームファクタ内に一つの PCIスロットだけを持っていてイーサネットI/Oだけのために単独で使用される。この PCIバスは、ホストRAM に直接にアクセスすることができない。その代わりに、デバイスは 128K の SRAM バッファに対して DMAを行う。本質的には、これはDMAバウンスのハードウェアによる実装である。これは、A12システムの対象アプリケーション(クロスバー上でMPI[注12]を介して通信する並列計算方式アプリケーション)を考慮すれば、アーキテクチャ上の制約であるとは言えない。

NetBSD/alpha によってサポートされる第6の PCI実装は、 AlphaServer 4100(Rawhide) システムの上で使われる MCPCIA MCBUS-to-PCI ブリッジであった。 Rawhide アーキテクチャーは、「ウマ(horse)」(中心バックプレーン)と2つの「鞍(saddles)」(バックプレーンの両側の主PCIバスアダプタ)で構成される。それぞれの鞍は EISA バスアダプタを持つことも可能である。それぞれの MCPCIA は、ダイレクトマップもしくはスキャッタギャザマップ操作のために使用され、スキャッタギャザページテーブルとしてホストRAM を使う4つの DMAウィンドウを持つ。

Alpha と比べ非常に対照的であるが、i386 プラットホームは非常に単純な PCI実装を持つ。 PCIバスは PCアーキテクチャの 32ビット物理アドレス空間全体のアドレス指定を行うことができ、一般にすべての PCIホストバスアダプタはソフトウェア的に互換性がある。また、i386プラットホームは WYSIWYG DMA を持つのでウィンドウ変換は必要でない。しかし、 i386プラットホームは ISAの 24ビットアドレス制約があることとスキャッタギャザマップDMAを持たないことにより、ISAバスにおいてはDMAバウンスについて適切な処理を行う必要がある。


[注9]これらのチップセットがお互いにやや異なっているが、ソフトウェアインタフェースはかなり似たものであるため、 NetBSD/alpha カーネル上ではこれらのチップセットは共通のデバイスドライバを使用している。

[注10]「TurboLaser」は、AlphaServer 8200、8400 のシステムにおけるシステムバスの名前である。

[注11]A12 の主 I/Oバスはクロスバーであり、並列プロセッサにおいて他のノードと通信するために使われる。

[注12]MPI もしくは Message Passing Interface は、並列プログラムにおいてデータおよび制御を交換するための標準化されたAPIである。


4.2. データ構造

NetBSD/alpha および NetBSD/i386 において使われる DMAタグはよく似ている。いずれも bus_dma インターフェースにおける 13の関数体系に対して 13の関数ポインタを持つ。しかし、NetBSD/alpha の DMAタグは、さらに主I/Oバスの子デバイスのための DMAタグと、DMA関数の低レベル実装によって解釈される不透明型(opaque)クッキーを取得するための関数のポインタを持つ。

NetBSD/alpha の DMAタグによって使用される 不透明(opaque)型クッキーは、チップセットの静的に割り当てられた状態情報へのポインタである。この状態情報は一つ以上の alpha_sgmap 構造体を含む。 alpha_sgmap は、スキャッタギャザマップDMAを実現するための一つのDMAウィンドウの状態情報すべてを保持する。その状態情報はスキャッタギャザページテーブルへのポインタ、ページテーブルを管理するエクステントマップ[注13]、および DMAウィンドウのベースアドレスである。

DMAマップ構造は、マップを作成する際に使われるパラメータのすべてを含んでいる(これは、現状の bus_dma インターフェースのすべての実装においてほぼ標準となっている慣行である)。マップ生成のためのパラメータに加えて、 2つの実装はそれらの特定の DMA に特有なクセ(quirk)に固有の付加的な状態変数を含む。たとえば、 NetBSD/alpha の DMAマップは、スキャッタギャザマップDMAに関連したいくつかの状態変数を含む。一方、i386ポートの DMAマップは、マップ固有のクッキーにポインタを含む。このクッキーは、ISAの DMAバウンスのための状態情報を保持する。この状態情報は独立したクッキーにおいて記憶される。というのも Alpha上におけるスキャッタギャザマップDMAと比較してi386上におけるDMAバウンスはあまり共通のものとは言えないからである。なぜならば Alpha においてはシステムが大量の物理メモリを持つ場合においても PCI に対する DMA ではスキャッタギャザマップDMAを実行しなければならないからである。

NetBSD/alpha と NetBSD/i386 の bus_dma 実装では、 DMAセグメント構造体は、インターフェースによって定義されるパブリックメンバだけを含む。


[注13]エクステントマップは任意の数の範囲を管理するデータ構造であり、いくつかのリソース割り当てのためのプリミティブを提供する。 NetBSD は、各種のカーネルサブシステムによって使われる汎用エクステントマップマネージャを持つ。


4.3. コード構造

NetBSD/alpha と NetBSD/i386 の bus_dma 実装は、ともにコード再利用のために単純な継承方式を使う。これは、チップセットもしくはバス特有コード階層(すなわち「親」階層)において DMAタグを生成することで実現されている。タグが生成されるとき、その階層における固有な操作が要求される関数ポインタのメンバに、親階層は自身のための手順(関数など)を挿入する。固有操作を要求しない手順に対してはその関数ポインタは共通コードへのポインタで初期化される。

Alpha の bus_dma コードは、チップセット固有コード、共通ダイレクトマップ操作を記述するコード、共通スキャッタギャザ操作を記述するコード、そしてダイレクトマップおよびスキャッタギャザマップDMAに共通な操作を記述するコード、の4つの基本的なカテゴリーに分類される。共通関数の一部は、タグの関数スイッチを通して直接呼び出されることはない。それらの関数は補助的な関数であり、チップセットフロントエンドによって使用されるためだけに存在する。そのような補助的関数の例としては、一連の共通ダイレクトマップ DMAロード関数がある。これらの関数は、インターフェース定義の呼び出し手順と同じ引数全部に加えて、DMAウィンドウのベースDMAアドレスという特別な引数を取る。

一方、i386 の bus_dma 実装は、bus_dma操作、共通補助関数、そして ISA DMA フロントエンド[注14]の 3つの基本的なカテゴリーに分類される。すべてのバスで共通な操作は DMAタグの関数スイッチから直接呼び出すことができる。PCI、EISA DMA タグはこれを利用しておりバス特有の DMA操作は用意されない。ISA DMAフロントエンドは、システムが 16MB以上の物理メモリを持つ場合には DMAバウンスのためのサポートを提供する。もしシステムが 16Mバイトもしくはそれ以下の実メモリしか持たない場合は DMAバウンスは必要とされないため、ISA DMAフロントエンドは単に bus_dma 関数呼び出しを共通の操作にリダイレクトするだけである。


[注14]ISA は、現在 NetBSD/i386 において DMA に対する特別な要求事項を伴ってサポートされる唯一のバスである。これは、システムの将来バージョンでは変わるかもしれない。(訳者注: i386 においても PentiumPro 以降の CPU でサポートされる 4Gバイト超の物理メモリをサポートしようとする場合には、 PCI や EISA などの 32ビットバスのデバイスに対しても DMAバウンス処理が必要となる)


4.4. オートコンフィギュレーション

NetBSD カーネルのオートコンフィギュレーションシステムでは、デバイスツリーにおけるノード(デバイス)階層順探索が使われる。この手順は、機種依存部のコードが機種非依存のオートコンフィギュレーションフレームワークに対して大元となるバスを検出したことを通知することにより起動される。ここで述べる2つのプラットホームにおいては、この mainbus と呼ばれる大元のバスは仮想的なデバイスであり、システムのいずれの物理的なバスとも直接一致はしない。 mainbus のためのデバイスドライバは、機種依存部のコードで実装される。このドライバの役割は、1つもしくは2つ以上の主I/Oバス構成することである。

NetBSD/alpha においては、主I/Oバスを実装するチップセットは mainbus 階層においても主I/Oバスであると見なされる。プラットホーム固有コードはチップセットの名前を指定し、 mainbus ドライバはそのチップセットの名前を検出することでそのチップセットを設定(configration)する。チップセットのデバイスドライバがアタッチされるときに、そのDMAウィンドウやデータ構造は初期化される。初期化が完了すると、ドライバはチップセットに論理的にアタッチされる1つもしくは2つ以上の主PCIバスを検出し、そしてこれらのバスのためにPCIバスデバイスドライバに対して DMAタグを渡す。バスドライバは、PCIバス上のそれぞれのデバイスその他を順に検出し設定していく。

PCIバスドライバがPCI-to-PCIブリッジ(PPB)に遭遇した場合には、 DMAタグはそのまま PPB デバイスドライバに渡される。PPB ドライバはブリッジの向こう側に接続されるセカンダリPCIバス上に順番に DMAタグを渡していく。しかし、PCIバスドライバが異なるバスタイプ(例えばEISAやISA)のブリッジに遭遇した場合は、機械依存コードによる介入が必要である。この場合、これらのバスは異なる DMAタグを要求する場合がある。このため、すべての PCIとその他のバスのブリッジ (PCxB) ドライバーは、機種依存部のコードにおいて実装される。DMAタグを得るための機種依存なフックを用意すれば PCxB ドライバを機種非依存部のコードとして実装することも可能ではあるが、セカンダリバスは特殊な機種依存となる割り込み設定および割り当てを要求する可能性があるため、そのような実装は行われていない。仮に機種依存のバス遷移詳細を扱うコールバックのすべてを実装したとしても、それにより共有できるコードの量はほとんどその労力に見合わない。

バスドライバによって検出された特定のハードウェアデバイスとデバイスドライバが関連づけられる際に、デバイスの初期化およびアクセスに必要ないくつかの情報が与えられる。これらの情報のうちの1つは DMAタグである。もしドライバが DMA動作を要求するのであれば、ドライバはこのタグを保持しておく必要がある。前に説明したように、このタグはすべての bus_dma インターフェースの呼び出しにおいて使用される。

バスおよびデバイスを構成するための手順は本質的に NetBSD/alpha 場合と同一であるが、 NetBSD/i386 では主I/Oバスの構成方法が大きく異なっている。 PCプラットホームは最初からずっと ISAバスを中心にして設計されてきた。 EISA や PCI は、さまざまな方法によってデバイスドライバの視点からは ISA と非常に類似したものになっている。これらの 3つのすべては、 I/Oマップ[注15]およびメモリマップされた空間を持つ。 PC上のハードウェアとファームウェアは標準でこれらのバスをマップするため、オペレーティングシステムのソフトウェアによるバスアダプタの初期化は不要である。このため、オートコンフィギュレーションの観点からは、 PCI、 EISA、および ISA のいずれのバスも主I/Oバスと見なすことが可能である。

NetBSD/i386 の mainbus ドライバは、降順で主I/Oバスを設定する。最初に PCI、次に EISA、最後に ISAである。mainbus ドライバはそれぞれのバスの DMAタグへ直接アクセスでき、DMA タグを直接I/Oバスに渡す。 EISA と ISA については、 mainbus 階層はそれらが PCI バスのコンフィグ中に見つけられない場合に限りこれらのバスをコンフィグしようとする。正確さを期すため、NetBSD/i386 は PCI-to-EISA(PCEB) と PCI-to-ISA(PCIB) ブリッジを識別して、それらのブリッジに対してもデバイスツリーにおけるオートコンフィギュレーションノードを割り当てる。 NetBSD/alpha とほぼ同じ方法で、 EISA と ISA バスはこれらのノードに論理的に接続される。ブリッジドライバもまたバスの DMAタグへ直接アクセスすることができ、それぞれに応じてタグをI/Oバスに渡す。


[注15]I/O マップされた空間は、Intel プロセッサ上では特別な命令によってアクセスされる。


4.5. 基本的な操作の例

この項では、仮想的な DES暗号化カードのデバイスドライバによって使われる bus_dma インターフェースを実装する機種依存部コードの動作を解説する。これは bus_dma の本来のアプリケーションでないが、非常に理解しやすい例として挙げる。 bus_dma インターフェースの開発において目的としていたアプリケーションは高性能な階層大容量記憶システムであるが、その詳細は非常に複雑なものになる。

NetBSD デバイスドライバの詳細のすべてについてはここでは解説しない。それよりも DMAの観点から重要なもののみを解説する。

具体例を挙げるという目的のため、カードは PCI と ISA のモデルのものがあるとする。ここでは 2つのプラットホームについて述べるので、実際の例としては 4つの組み合わせが存在することになる。それらについて次のような記号を用いて表すことにする。

[alpha/ISA]
[alpha/PCI]
[i386/ISA]
[i386/PCI]

ここで [i386/ISA] プラットホームは 16Mバイト以上の RAM を持っていると仮定する。よって、もし DMA-safeメモリが明示的に使われない限り、転送には DMAバウンスが必要となるかもしれない。同様に [alpha/PCI] プラットホームのダイレクトマップ DMAウィンドウはすべてのシステム RAM についてアドレス指定可能であると仮定する。

マップの同期の解説においては、特有の個別操作を必要とする場合にのみ解説を行うので注意してもらいたい。[alpha/ISA][alpha/PCI] のどちらの場合も、すべての同期操作は Alpha の mb 命令[文献6]を用いた CPUのライトバッファの掃き出しを実行する。[i386/PCI] の場合はすべての同期操作では何の操作も行われない。[i386/ISA] の場合における DMA-safeメモリに対する同期操作も同様である。

4.5.1. ハードウェア概要

カードはバスマスタであり、DMAを通して固定長のコマンドブロックを読み取ることによって動作する。 SET KEYENCRYPT および DECRYPT の3つのコマンドがあり、コマンドをコマンドブロック内に書き込み、カードの dmaAddr レジスタにコマンドブロックのDMAアドレスを書き込むことで動作を開始する。コマンドブロックは、6つの32ビットワード cbCommandcbStatuscbInAddrcbInCountcbOutAddr、そして cbOutCount を含む。 cbInAddrcbOutAddr 要素は、カードのDMAエンジンによって使われるソフトウェアスキャッタギャザリストのDMAアドレスである。 cbInCountcbOutCount 要素は、それぞれのリストにおけるスキャッタギャザエントリの数である。それぞれのスキャッタギャザエントリは、DMAアドレス、長さ(いずれも32ビットワード)を含む。

カードがリクエストを処理する際には、DMAを通してコマンドブロックを読み取り、そしてコマンドブロックを調べ、いずれの動作を行うかを決定する。サポートされている 3つのコマンドすべての場合において、カードは DMAアドレス cbInAddr にある入力スキャッタギャザリストを cbInCount ×8 バイト分だけ読み込む。それからカードは該当する処理エンジンに入力を切り換える。 SET KEYコマンドの場合、スキャッタギャザリストは、DESキーをカード上の SRAM に DMA転送するのに用いられる。それ以外のコマンドの場合は、入力はパイプライン化された DESエンジンに向けられ、暗号化または復号化のいずれかのモードに切り替えられる。そして DESエンジンは cbOutAddr によって指定される出力スキャッタギャザリストを cbOutCount ×8 バイト分読み込む。DESエンジンに対してすべての DMAアドレスが与えられると、すべてのデータが処理されるまで入力-処理-出力のサイクルが開始される。コマンドが完了すると、 cbStatus にステータスワードが書き込まれ、ホストに対して割り込みを発生させる。ドライバソフトウェアは、コマンドが正常に完了したどうか確認するためにこのステータスワードを読み取る必要がある。

4.5.2. デバイスドライバー概要

この DESカード用のデバイスドライバは、 open()close() および ioctl() のエントリーポイントを提供する。性能を引き出すためドライバはユーザーアドレス空間に対する DMAを使用する。ユーザーが要求する操作に対応するリクエストを ioctl を通して出すと、ドライバはそれを作業待ち行列に置く。 ioctl() システムコールは直ちにリターンし、アプリケーションに対し動作を許可するか、もしくは sigsuspend() によって動作をブロックする。カードが現在アイドル状態であれば、ドライバはカードに対して直ちにコマンドを発行する。ジョブが完了するとカードは割り込みを発生し、ドライバは SIGIO シグナルを通してユーザーに対しリクエストが完了したことを通知する。作業待ち行列上にさらにジョブが存在する場合は、すべてのジョブがなくなるまで待ち行列から次のジョブが取り出されて開始される。

4.5.3. ドライバー初期化

ドライバインスタンスが作られる(アタッチされる)とき、ドライバは動作上必要となるデータ構造を作成して初期化する必要がある。このドライバは、制御用構造体(制御ブロックとスキャッタギャザリスト)に対して1つ、そして、ユーザーリクエストによって渡されるデータに対して複数の DMAマップを使う。データマップはドライバ待ち行列エントリの中に保持されるが、その待ち行列エントリはジョブが渡されるときに作成される。

次に、ドライバは制御構造体用に DMA-safeなメモリを割り当てなければならない。ドライバは、 bus_dmamem_alloc() を使い 3ページのメモリを割り当てる。単純化のためドライバは単一メモリセグメントを要求する。この例におけるすべてのプラットホームおよびバスにおいては、この操作は必要な制約条件に基づいてメモリを割り当てるための仮想記憶システム中の関数を呼ぶだけである。[i386/ISA] の場合には、 ISA 階層は 0〜16Mバイトの範囲を指定するために ISA階層自身を呼び出し条件に追加する。それ以外の場合は、単に存在する実メモリ範囲全体を指定する。

このメモリの一部分はコマンドブロックのために使われる。メモリの他の部分は、 2つのスキャッタギャザリスト用に二等分される。そしてこのメモリは BUS_DMA_COHERENT フラグとともに bus_dmamem_map() を呼ぶことでカーネル仮想記憶空間にマップされ、3つの構造体へのカーネルポインタが初期化される。i386上でメモリがマップされるとき、 BUS_DMA_COHERENT フラグによってキャッシュ禁止ビットがPTEにおいてセットされるようになる。このフラグに対する特別な操作は Alpha 上では必要ない。しかし、 Alpha の場合では単一のセグメントのダイレクトマップがあるので、メモリは Alpha のダイレクトマップされたカーネルセグメントを通してマップされ、カーネル仮想記憶空間を使用する必要はない。

最後に、ドライバは bus_dmamap_load() にこのメモリのカーネル仮想アドレスを渡すことで制御構造体 DMAマップをロードする。処理の開始を簡単にするため、ドライバは各種の制御構造の DMAアドレスを(それらのオフセットをメモリのDMAアドレスに加えることで)キャッシュする。すべての場合において、下位のロード関数は仮想アドレス空間の各ページに対して一つずつ物理アドレスを pmapモジュールから取り出していき、可能であれば複数のセグメントを一つにまとめていく。ここではメモリはもともと単一のセグメントに割り当てられているので、ロード関数は単一のDMAセグメントにマップする。

4.5.4. 処理の例

ユーザーがすでに暗号用キーをセットし、それを使ってデータバッファを暗号化しようとしていると想定しよう。呼び出しを行うプログラムは、入力バッファ、出力バッファ、およびステータスワードのポインタ(これらはすべてユーザー空間にある)を用意してリクエストを作成し、「バッファ暗号化」の ioctl を発行する。

カーネルへのエントリにおいて、ドライバは DMA実行中にデータがページアウトされてしまうのを防ぐためにユーザーのバッファをロックする。1つのジョブ待ち行列エントリが割り当てられ、2つのDMAマップがそのジョブ待ち行列エントリのために作成される。1つは入力バッファ用、もう1つは出力バッファ用である。すべての場合において、この操作では標準の DMAマップ構造体が割り当てられる。[i386/ISA] の場合には、それぞれの DMAマップに対してさらに ISA DMA クッキーが割り当てられる。

待ち行列エントリが割り当てられたら、それを初期化する必要がある。このプロセスでの最初の処理は、入出力バッファ用DMAマップのロードである。このプロセスは入力バッファと出力バッファとで本質的に同一であるので、ここでは入力バッファのマップに対する動作のみを説明する。

[alpha/PCI][i386/PCI] では、下位のコードはユーザーのバッファを走査してそれぞれのページの物理アドレスを取り出す。 [alpha/PCI] では、 DMAウィンドウのベースがこのアドレスに加算される。得られたセグメントのアドレスと長さはマップの DMAセグメントリストに入れられる。各セグメントが連続である場合はそれらのセグメントは連結される。

[alpha/ISA] においてはほぼ同様のプロセスが行われる。しかし、物理アドレスをマップのセグメントリストに入れるのではなく、スキャッタギャザマップDMAアドレス空間が割り当てられ、対応するページテーブルエントリにアドレスが設定される。このプロセスが完了すると、スキャッタギャザマップされた領域の先頭を指す一つのDMAセグメントがマップのセグメントリストに置かれる。

[i386/ISA] の場合は、同じようにユーザーのバッファを走査するが、これが 2回行われる。1回目の走査では、バッファ中に 16Mバイトの閾値より上位にあるページがないことを確認するために行われる。もしバッファに閾値より上位にあるページがなければ、手順は [i386/PCI] の場合とまったく同じである。しかし、ここでは例を示すために、バッファは閾値の範囲外にページを持ち転送には DMAバウンスが必要であるとする。バウンスバッファはこの時点で割り当てられる。ここでカーネルは依然としてプロセスコンテキスト上にあるため、この割り当てはブロックされても構わない。バウンスバッファへのポインタは ISA DMA クッキーに格納され、バウンスバッファの物理アドレスがマップのセグメントリストに置かれる。

次の作業は、転送を待ち行列にに入れる、もしくは転送を開始することである。ここでは簡単な例にとどめるため、他に実行中の転送は存在しないと仮定する。このプロセスでの第一歩は、キャッシュされたカードのスキャッタギャザリストの DMAアドレスを用いて制御ブロックを初期化することである。これらのスキャッタギャザリストはさらに DMAマップのセグメントリストの内容によって初期化される。データ転送の開始をカードに指示する前には、DMAマップを同期する必要がある。

同期すべき第1 のマップは、入力バッファマップである。これは PREWRITE 操作である。[i386/ISA]の場合は、ここでユーザーのバッファがバウンスバッファにユーザーのアドレス空間からコピーされる[注16]。同期すべき次のマップは、出力バッファマップである。これは PREREAD 操作である。最後に制御マップの同期を行う。処理の終了後にも制御ブロックからはステータスを読み出すため、この同期は PREREAD|PREWRITE の操作となる。

この時点でDMA転送処理が開始可能となる。カードの dmaAddr レジスタにキャッシュされた制御ブロックの DMAアドレスを書き込むことでカードは動作を開始する。ドライバはユーザー空間にリターンし、プロセスは処理が完了したことを伝えるシグナルを待つ。

転送処理が完了すると、カードはホストに対して割り込みを発生させる。ここで割り込みハンドラは、DMAシーケンスを完了させ動作の完了を要求元のプロセスに通知する動作を行う。

ここで実行する第一の作業は入力バッファマップの同期である。これは POSTWRITE である。次に出力バッファマップの同期を行う。これは POSTREAD である。[i386/ISA] の場合は、ここで出力バウンスバッファの内容がユーザーのバッファへコピーされる[注17]。最後に、制御マップの同期を行う。これは POSTREAD|POSTWRITE の操作である。

DMAマップが同期されたあと、それらのマップをアンロードする必要がある。[alpha/PCI][i386/PCI] の場合、解放すべきリソースはなく単にマップが無効とマークされるだけである。[alpha/ISA] の場合、スキャッタギャザマップDMA のリソースが解放される。[i386/ISA]の場合、バウンスバッファが解放される。

ユーザーのバッファはこれ以降使用されることはないので、バッファはデバイスドライバによりアンロックされる。この時点でプロセスに対しI/Oが完了したことを示すシグナルを出すことができる、最後の作業は、入出力バッファのDMAマップとジョブ待ち行列のエントリの破棄である。


[注16]仮想記憶システムに相当な変更を必要としたので、これは現在実装されていない。これは copyin()copyout() 関数は現在のプロセスのコンテキスト上のみで機能し、バウンスの時点ではそのコンテキスト存在しない可能性があるからである。現在ではこの問題に対応する仮想記憶システムへの変更は行われているので、ユーザー空間に対する DMAバウンスのサポートは NetBSD の今後のリリースで登場するだろう。一方カーネル空間に対する DMAバウンスは現状でもサポートされている。

[注17]ここでも [i386/ISA] の入力バッファマップの PREWRITE 操作の注記と同じ内容が適用される。


結論

bus_dma インターフェースは、ちょうど NetBSD 1.3 のリリースサイクルが開始される直前に、開発バージョン 1.2G の NetBSD カーネルに対して導入された。コードが NetBSD マスタソースに入れられた際には、いくつかのドライバ(大部分は SCSI コントローラのドライバであった)は一斉にこのインターフェースを使うように変更された(これらのドライバはすべてあらかじめ bus_space インターフェースを使うように変更されていた)。これらのドライバは bus_dma の正しい使用例を提供しただけではなく、それ以前の NetBSD カーネルでは存在しなかった機能である、16MB以上の RAM を持つ PC でのバスマスタ ISA デバイスのサポートも提供した。

Alpha プラットホームの bus_dma インターフェースの最初の実機でのテストは、 AXPpci33 コンピュータ上にバスマスタ ISA デバイス(アダプテック 1542 SCSI コントローラ)をインストールして行われた。Alpha での bus_dmamap_load() の実装における些細なバグを一つ修正しただけでデバイスは完全に動作した。

新しいインターフェースの適用のためのデバイスドライバの書き換え作業において、書き換え後の各ドライバからは相当量の類似コードが削除可能であるということが明らかになった。削除されたのはソフトウェアスキャッタギャザリストを作成するためのループ処理であった。いくつかの場合においては、bus_dmamap_load() でのこの処理ループの実装がより効率的で、かつ、複数セグメントの統合をサポートしていることから、ドライバの性能は際だって向上した。

DMAを使う大部分の機種非依存のドライバは新しいインターフェースに変換され、さらに他のプラットホームにおいても必要なバックエンドが実装された。結果は非常にすばらしいものであった。テストを実施したほぼすべてのデバイスとプラットホームの組み合わせにおいて、デバイスドライバは追加の修正をすることなく動作した。この際における一部の例外は、大部分はホストとデバイスのバイトオーダーの相違に対する扱いであり、これは DMAとは直接関係しない事項である。

bus_dma インターフェースは、たとえば VMEバスのような、新たな機種非依存のバスオートコンフィギュレーションフレームワークのための道も開いた。将来的には、 bus_dma インターフェースは PCI-to-VME ブリッジのサポートを容易にし、Sun、モトローラ、そして、Intelシステムが共通の VMEデバイスドライバを共有できるようになるだろう。

我々は、カーネルを新しいプラットホームに移植するプロセスを非常に単純化し、移植性の高いデバイスドライバの開発を非常に容易にするなど、bus_dma インターフェースが NetBSD カーネルにおける大きな構造上の利点となることを確認した。要約すると、抽象化は設計時に意図していたとおり「最大限のコード再利用による広範囲のプラットホームサポート手段」を提供した。

参考文献

[文献1]
Digital Equipment Corporation、
DECchip 21071、DECchip 21072コアロジックチップセットデータシート、
1994年11月、DECオーダーナンバーEC-QAEMA-TE。

[文献2]
Digital Equipment Corporation、
DECchip 21066アルファAXPマイクロプロセッサデータシート、
1994年5月、DECオーダーナンバーEC-N0617-72。

[文献3]
Digital Equipment Corporation、
DECchip 21171コアロジックチップセットテクニカルリファレンスマニュアル、
1995年9月、DECオーダーナンバーEC-QE18B-TE。

[文献4]
Digital Equipment Corporation、
DWLPA、DWLPB PCIアダプタテクニカルマニュアル、
1996年7月、DECオーダーナンバーEK-DWLPX TM。

[文献5]
H. Ross Harvey、
Avalon A12並列スーパーコンピュータ動作原理、
1997年10月、Avalonコンピューターシステム社。

[文献6]
Richard L. Sites、Richard T. Witek、
Alpha AXPアーキテクチャリファレンスマニュアル第2版
1995年 デジタル・プレス

7. NetBSDの入手

どこでNetBSDオペレーティングシステムそのもののソースおよびバイナリを得られるかという情報を含む、NetBSDに関する詳細な情報は、www.netbsd.org にある。

本論文に対する更新は定期的に現れるかもしれない。それらは http://www.netbsd.org/Documentation/research/ にて見つけられるであろう[訳注6]


[訳注6]現在このURL上には本 bus_dma の論文は掲載されていないが、NetBSD ソースツリー中の /usr/src/share/doc/papers/bus_dma/ 以下にroffのソースファイルの形式で置かれており、make を実行することで Postscript形式のファイルが得られる。しかし内容は執筆時点から更新されていないようである。


8. 謝辞

以下の人々に対して、bus_dma設計段階における彼らの非常に建設的な情報と洞察に感謝したい:

Chris Demetriou、Charles Hannum、Ross Harvey、Matthew Jacob、Jonathan Stone、そして、Matt Thomas。

また、Chris Demetriou、Lonhyn Jasinskyj、Kevin Lahey、Yvonne Malloy、David McNab、そしてHarry Waddellに、本論文を洗練させるためのレビューと私に対する援助に彼らが費やした時間に対して特別な感謝を表したい。

著者について[訳注7]

Jason R. Thorpeは、NASAのエイムスリサーチセンターの数値航空宇宙シミュレーション施設のネットワークシステムエンジニアである。

彼の専門の関心は、移植性の高いオペレーティングシステム、高速コンピュータネットワーク、そしてネットワークプロトコルの設計と実装を含む。

NAS施設のネットワークおよび大容量記憶システム開発計画のサポートにおけるNetBSDオペレーティングシステムの彼の業績に加えて、彼はインターネット技術特別調査委員会のアクティブな参加者である。

彼は1993年半ばからNetBSDプロジェクトの貢献者であり、かつてはほとんどすべてのポートを動かしていた。

彼は現在NetBSDのhp300ポートを保守しており、またNetBSD Core Groupのメンバでもある。

著者とは下記にて連絡をとることができる:

Numerical Aerospace Simulation Facility, Mail Stop 258-5, NASA Ames Research Center, Moffett Field, CA 94035,

もしくは、電子メールthorpej at nas.nasa.gov経由


[訳注7]現在Jason R. Thorpe氏はNASAから離れている。


NetBSDのページに戻る
tsutsui@ceres.dti.ne.jp