このページの目次

IOMMU と DMA API

カバー範囲: IOMMU ハードウェア → DMA API (coherent/streaming) → swiotlb → IOMMU ドメイン → VFIO → 仮想化における IOMMU カーネルバージョン: 2.6 ~ 6.x

IOMMU の原理

IOMMU はデバイスに IO アドレス変換を提供します (CPU の MMU に例えられます):
  デバイス視点のアドレス (IOVA) → IOMMU ページテーブル → 物理アドレス

機能:
  1. アドレス変換: デバイスは IOVA しか「見えず」、実際の物理アドレスは不明
  2. 分離: 各デバイス/ドメインは独立したページテーブルを持つ → A は B のメモリにアクセスできない
  3. 割り込みリマップ: MSI アドレスは IOMMU によって変換される → MSI インジェクション攻撃を防ぐ
  4. 連続メモリ: 分散した物理ページを、デバイス視点で連続した IOVA にマッピング → DMA バッファ割り当てを簡素化

実装:
  Intel: VT-d (DMAR テーブル)
  AMD:   AMD-Vi (IVRS テーブル)
  ARM:   SMMU (System MMU)

DMA API

// include/linux/dma-mapping.h
// デバイスドライバは、DMA 用に物理メモリを手動で割り当て/解放すべきではない → DMA API を使用する必要がある

// === Coherent DMA (一貫性) ===
// 用途: デスクリプタリング、デバイスステータスページ (CPU とデバイスが同じ領域を頻繁に読み書き)
// 特性: CPU とデバイスが常に一貫した状態を参照 → キャッシュの自動同期
void *dma_alloc_coherent(struct device *dev, size_t size,
                          dma_addr_t *dma_handle, gfp_t gfp);
void dma_free_coherent(struct device *dev, size_t size,
                        void *cpu_addr, dma_addr_t dma_handle);

// === Streaming DMA (ストリーミング) ===
// 用途: データバッファ (1回限りの使用、方向は固定)
// 特性: 明示的な同期が必要 → Coherent よりもパフォーマンスに優れる
dma_addr_t dma_map_single(struct device *dev, void *cpu_addr,
                           size_t size, enum dma_data_direction dir);
void dma_unmap_single(struct device *dev, dma_addr_t dma_handle,
                       size_t size, enum dma_data_direction dir);

// direction: DMA_TO_DEVICE (書き込み), DMA_FROM_DEVICE (読み取り), DMA_BIDIRECTIONAL

// === Scatter-Gather ===
int dma_map_sg(struct device *dev, struct scatterlist *sg,
                int nents, enum dma_data_direction dir);

内部実装: プラットフォームに応じた選択

IOMMU がある場合:
  dma_map_single → iommu_dma_map_page() → IOMMU ドメイン内にマッピングを作成
  → ゼロコピー (IOVA から物理アドレスへのマッピングのみで、データのコピーは不要)

IOMMU がない場合:
  dma_map_single → DMA でアクセス可能な範囲が物理アドレス範囲より小さい場合 → SWIOTLB バウンスバッファを使用
  → 低アドレス領域に一時的なバッファを割り当て → データをコピー (パフォーマンスの低下)

SWIOTLB: ソフトウェアバウンスバッファ

デバイスの DMA アドレス指定範囲が限られている場合 (例: 32ビットPCIデバイスは 0~4GB しかアクセスできない)...:
  SWIOTLB は低アドレス領域 (DMAゾーン) にバウンスバッファを予約する
    dma_map:  データを高アドレスから → コピー → バウンスバッファ (DMA アクセス可能な範囲内)
    dma_unmap: バウンスバッファから → コピー → ユーザーバッファ (DMA_FROM_DEVICE の場合)

  起動パラメータ: swiotlb=65536 (64MB のバウンスバッファを割り当て)

IOMMU ドメインとグループ

// drivers/iommu/
// ドメイン: IOMMU ページテーブルの抽象化 (一連の IOVA → 物理アドレスのマッピング)
// グループ: ハードウェア分離ユニット (同じグループ内のデバイスは互いにアクセス可能)

// IOMMU グループは VFIO の分離境界である:
//   グループ内のデバイスは互いに分離できない → 同一の VM に割り当てる必要がある
//   /sys/kernel/iommu_groups/*/devices/*

VFIO: ユーザー空間デバイスドライバ

// drivers/vfio/
// ユーザー空間プログラムがハードウェアに直接 (ただし安全に) アクセスできるようにする:
//   1. IOMMU 分離: デバイスを独自の IOMMU ドメインにマッピング → ホストメモリにアクセスできない
//   2. MMIO パススルー: デバイスの BAR をマッピング → ユーザー空間から直接 mmap 可能
//   3. 割り込み転送: デバイス割り込み → IOMMU 割り込みリマップ → eventfd → QEMU/ユーザー空間

// QEMU: vfio-pci → PCI パススルー → ゲストがハードウェアに直接アクセス
// SPDK:  ユーザー空間 NVMe ドライバ → データパスのゼロコピー

参考

  • ソースコード⁠: kernel/dma/, drivers/iommu/intel/, drivers/iommu/arm/, drivers/vfio/
  • カーネルドキュメント⁠: Documentation/core-api/dma-api.rst
  • LWN: 「The DMA API」、「IOMMU groups and VFIO」

キーワード: IOMMU, DMA API, dma_map_single, coherent DMA, swiotlb, IOMMU ドメイン, VFIO