IOMMUとDMA API

このページの目次

デバイス記述子内のアドレスは、CPUポインタを無理やりキャストして得られるものではありません。unmapもまた、ハードウェアに処理を停止させるコマンドではありません。DMAでは、⁠アドレス、方向、可視性、所有権の引継ぎを同時に管理する必要があります。

参照目標読書位置
アドレスとcoherent/streaming第1~2節
方向、完了、SGカウント第3~4節
IOMMU、バウンス、分離第5~6節

1. 3種類のアドレスは互いにキャストできない

アドレス使用主体典型型
CPU仮想アドレスカーネルコードによるバッファアクセスvoid *
CPU物理アドレスメモリとリソース管理phys_addr_t
DMAアドレスデバイス記述子への書き込み、デバイスによるアドレス指定dma_addr_t
2つのアドレス視点、プロトコルで拘束されたバッファ CPU仮想アドレス CPUは通常のメモリマップでアクセス RAM内のバッファ 基盤となる物理ページ デバイスのDMAアドレス dma_addr_tであり、CPUポインタではない CPU MMU ダイレクトマッピング / IOMMU / バウンス CPUによる準備/読み取り map / sync / unmap デバイスによる読み書き DMA APIはマッピングとキャッシュの規約を確立する。ドライバは依然として完了を待ち、所有権の引継ぎに従わなければならない。

IOMMUは、デバイスが発行するIOVAを物理ページアドレスに変換できる。IOMMUがない場合、DMAアドレスはブリッジオフセットなどの影響を受ける可能性がある。dma_addr_tは直接間参照することも、CPUポインタを適当にキャストして得ることもできない。あるマシンでは数値が偶然一致することがあっても、それは移植性の保証にはならない。

ドライバはハードウェアの能力に応じてDMAマスクを設定する。記述子とデータペイロードのアドレスビット幅は異なる場合があり、必要に応じてstreaming maskとcoherent maskをそれぞれ設定する。狭いマスクはプラットフォームでサポートされない可能性があるため、APIの戻り値を処理する必要がある。CPUが64ビットだからといって、デバイスが64ビットDMAをサポートすると主張してはならない。

2. Coherentとstreamingは異なるアクセスパターンを解決する

モデル一般的な用途ドライバが依然として責任を持つこと
coherent allocation長期間存在し、双方が頻繁にアクセスする記述子とステータス領域アクセス順序、排他制御、ハードウェアの完了と解放のタイミング
streaming mappingネットワークパケット、ストレージデータなどのバッチ転送方向、所有権の引継ぎ、同期とマッピングの寿命

dma_alloc_coherent()はCPUアドレスを返し、デバイスが使用するためのDMAアドレスを出力する。解放する際は、一致するデバイス、サイズ、CPUアドレス、DMAハンドルを使用する。coherentは、このメモリが通常のアクセスに必要な明示的なキャッシュ管理を免除するが、すべての書き込みが即座にソースコードの順序でデバイスに見えるわけではない。また、複合操作を自動的にアトミックにするものでもない。

例えば、記述子を公開する際、まずアドレスと長さを書き込み、適切なdma_wmb()を実行し、最後にデバイスプロトコルに従って有効ビットを設定する。デバイスが完了した後、CPUは完了フラグと共に公開されたデータを安全に読み取るためにdma_rmb()を必要とする場合もある。MMIOドアベルへのアクセス順序は、I/Oアクセサとハードウェアの要件も満たす必要がある。バリアはDMAの完了を待つものではない。

streamingの「ストリーミング」とは、必ずしも1回だけマッピングできるという意味ではなく、coherentよりも必ずしも高速であることも保証されない。パフォーマンスはプラットフォーム、キャッシュ、マッピングオーバーヘッド、アクセスパターンに依存する。概念とアドレスのルールについてはDMA HOWTOを、具体的なインターフェースについてはDMA APIを参照のこと。

3. 方向はデバイスとメモリ間のデータフローに基づく

  • DMA_TO_DEVICE:デバイスがメモリを読み取る(例:ネットワークパケットの送信)。
  • DMA_FROM_DEVICE:デバイスがメモリに書き込む(例:ネットワークパケットの受信)。
  • DMA_BIDIRECTIONAL:双方が書き込みを行う場合。所有権と同期プロトコルに従う必要がある。

「読み取り」や「書き込み」とだけマークしないでください。そうすると、CPUとデバイスの2つの視点で逆になってしまいます。方向が確定している場合は正確な方向を使用してください。map、sync、unmapで使用するデバイス、範囲、方向は、APIのペアリング要件に一致している必要があります。

受信の1回限りの処理は以下のように理解できます。CPUが適切なバッファを割り当て、FROM_DEVICEとしてマッピングし、返されたDMAアドレスをデバイスに渡します。デバイスが本当に完了したことを確認してからマッピングを解除し、その後CPUが読み取ります。マッピング解除はハードウェアを停止させるコマンドではありません。デバイスがアクセスしなくなったことを確認してから解除する必要があります。

マッピングを解除しない場合でも、CPUとデバイスはアクセス権を引継ぐ必要がある

マッピングを長期間保持する場合、デバイスが完了したことを確認した後でdma_sync_single_for_cpu()を呼び出し、CPUがアクセスフェーズに入ります。デバイスを返す前にdma_sync_single_for_device()を呼び出します。これらの呼び出しはキャッシュ管理やバウンスバッファのコピーを含む場合がありますが、他方の同時アクセスをドライバが阻止するわけではありません。

非一貫性プラットフォームの簡略化モデルでは、CPUキャッシュとRAMの内容を別々に表示します。同期アクションによって所有権を移譲し、デバイスの完了とCPUが読み取り可能になる間の欠けているステップを観察します。

図を準備しています
DMAはRAMに書き込んだが、なぜCPUの古いキャッシュはすぐに使えないのか?

デバイスの完了、キャッシュの可視性、マッピングの寿命は異なる条件である。syncは進行中のDMAを待たず、unmapはハードウェアを停止させない。CPU VA、物理アドレス、DMAアドレスは互いに交換できない。

どのメモリを直接マッピングできるか

dma_map_single()は、制約を満たす連続メモリ、例えば適切に割り当てられたkmallocバッファに適用できる。スタックメモリ、カーネルイメージアドレス、およびvmallocが返す仮想連続アドレスは、汎用の単一セグメントDMAバッファとして直接使用できない。vmallocの基盤となるページは、適切なページ単位の処理またはSGパスを介して処理する必要がある。

また、CPUフィールドとデバイスが書き込む領域がキャッシュラインを共有しないようにする必要がある。非一貫性プラットフォームでは、この共有がデータの上書きを引き起こす可能性がある。dma_mapping_error(dev, addr)でマッピングの失敗を判断する。DMAアドレスが0だからといって、自動的に失敗を意味するわけではない。

4. Scatter-gatherの2つの数量

SGにより、複数のメモリ断片を使って1回のデータ転送を表現できる。DMA層は隣接する断片を結合できるため、デバイスに見えるセグメント数は入力エントリ数より少なくなる可能性がある。

以下はローカルなAPIの使用例であり、sglが正しく構築され、orig_nents > 0であると仮定している。

int mapped_nents = dma_map_sg(dev, sgl, orig_nents, direction);
if (mapped_nents == 0)
    return -EIO;

/* ハードウェアのDMAセグメントを設定する:マッピング後の数量とsg_dma_*フィールドを使用する。 */
struct scatterlist *sg;
int i;
for_each_sg(sgl, sg, mapped_nents, i) {
    dma_addr_t addr = sg_dma_address(sg);
    unsigned int len = sg_dma_len(sg);
    /* addr/lenを実際のハードウェア記述子に埋め込む;ここではハードウェアは起動しない。 */
    dev_dbg(dev, "DMA segment %d: %pad / %u\n", i, &addr, len);
}

/* 実際のドライバは、ハードウェアの使用が停止したことを確認してから、以下の文を実行しなければならない。 */
dma_unmap_sg(dev, sgl, orig_nents, direction);
使用位置渡すべき数量
ハードウェアへのDMAセグメント設定mapped_nents
dma_unmap_sg元のorig_nents
dma_sync_sg_for_*元のorig_nents

マッピング層はデバイス視点のセグメントを生成するが、元のマッピングを復元するために必要な情報も保持する必要がある。結合後のカウントをすべてのインターフェースに使用してはならない。SG APIの説明を参照。

5. IOMMUとSWIOTLBは同時に存在できる

IOMMUはページテーブルを通じてアドレス変換、権限、分離を提供し、離散した物理ページを連続したIOVAにマッピングすることもある。任意のSGが単一セグメントになることを保証するものではなく、セグメント長、境界、マッピングリソースの制約を受ける。割り込みリマップは、プラットフォームが提供する関連するが独立した機能であり、DMA変換が存在するからといって、割り込みの安全性条件がすべて満たされると断言してはならない。

SWIOTLBはバウンスバッファを提供する。これはデバイスが操作できる中間バッファであり、DMA層が対応する同期ポイントでデータを転送する。デバイスのアドレス指定が制限されている場合のほか、機密計算における共有メモリの制約などにもバウンスが使用される。したがって、「IOMMUがあればコピーなし、IOMMUがない場合のみSWIOTLB」という単純な話ではない。

コピーの方向はDMAの方向に関連する。TO_DEVICEの場合、CPUが準備したデータをデバイスが読み取り可能な領域に送る必要がある。FROM_DEVICEの場合、CPUがデータを受信する際にデバイスからの結果を取り戻す必要がある。コピーが必要かどうか、どの同期ポイントで発生するかは、マッピングと同期パスによって決定され、map/unmapのたびに双方向コピーが行われると仮定してはならない。

SWIOTLBのプールサイズパラメータには独自の単位があり、スロット数を直接KiBとして扱ってはいけない。例えば、一般的な2 KiBスロットの場合、65536スロットは64 MiBではなく128 MiBである。具体的なパラメータ、動的プール、制限については、対象カーネルのSWIOTLBドキュメントを参照のこと。

6. Domain、group、およびVFIO

オブジェクト回答する質問
IOMMU domainどのアドレス空間とマッピングポリシーを使用するか?複数のデバイスが共有可能
IOMMU groupシステムが確実に分離できる最小限のデバイスは何か?

groupはトポロジ、ACS、およびデバイスの動作に影響され、固定された「PCI function 1つにつき1グループ」とは限らない。

group内の特定のfunctionを信頼できないユーザー空間に渡す場合、グループ内の他の関連デバイスも制御する必要があり、分離の境界を回避できるドライバアクセスを残してはならない。これは、各functionが実際に同じVMに提示されることを要求するものではない。具体的なバインディングと所有権の制約はVFIO/IOMMUインターフェースによって管理される。

VFIOは、デバイスリソース、DMAマッピング、割り込みの制御された公開を許可する。デバイスがアクセスするゲストメモリの基盤は依然としてホストの物理ページであり、保護の目標は「ホストメモリに一切アクセスできない」ことではなく、認可されたマッピングのみをアクセスできるようにすることである。IOMMUがない特殊なモードも、同じ分離保証を持つとは見なせない。VFIOドキュメントを参照。

参照順序

アドレスの異常については、まずDMAマスク、戻り値、および記述子への書き込まれたアドレスを確認する。データの不整合については、方向、完了のタイミング、キャッシュラインの共有、およびバリアを確認する。解放後の破損については、ハードウェアとコールバックの終了前にunmap/freeが行われていないか確認する。IOMMUフォールトは、あるデバイスのアクセスがマッピング条件に違反したことを示すが、フォールトがあるからといって、ページテーブルの実装に誤りがあると断定してはならない。

関連読書:PCIeとUSBドライバモデル、DRMとグラフィックス。