IOMMU (Input-Output Memory Management Unit)

IOMMU를 DMA 주소 변환(Address Translation)과 디바이스 격리(Isolation)를 담당하는 핵심 보안 계층으로 심층 분석합니다. Intel VT-d, AMD-Vi, ARM SMMU의 구조 차이와 페이지 테이블(Page Table) 모델, DMA remapping/interrupt remapping 동작, ATS/PASID/SVA 기반 고급 공유 주소 공간(Address Space), VFIO 가상화(Virtualization) 환경의 격리 보장, 성능 저하를 줄이기 위한 TLB flush 전략, DMA fault 디버깅(Debugging)과 우회 경로(SWIOTLB)까지 고성능 장치와 멀티테넌트 환경에서 필요한 운영 포인트를 다룹니다.

전제 조건: DMAPCI/PCIe 문서를 먼저 읽으세요. IOMMU는 디바이스 DMA 주소 변환과 보호를 담당하므로, DMA 매핑(Mapping) 개념과 PCIe 디바이스 구조를 먼저 이해해야 합니다.
일상 비유: IOMMU는 공항 보안 검색대와 비슷합니다. 디바이스(승객)가 메모리(탑승구)에 접근할 때 허가된 영역만 접근하도록 검증하고, 불법 접근을 차단합니다.

핵심 요약

  • IOMMU — 디바이스 DMA 주소를 물리 주소(Physical Address)로 변환하고 접근 제어(Access Control)하는 하드웨어입니다.
  • DMA Remapping — 디바이스의 DMA 주소를 가상화하여 메모리 보호와 격리를 제공합니다.
  • IOMMU Domain — 디바이스 그룹이 공유하는 주소 공간과 페이지 테이블입니다.
  • VFIO — IOMMU를 활용하여 가상 머신에 디바이스를 안전하게 할당합니다.
  • PASID/SVA — CPU와 디바이스가 동일한 가상 주소 공간을 공유합니다.

단계별 이해

  1. IOMMU 필요성 — 디바이스가 임의 메모리에 접근하면 보안 위협이므로, IOMMU로 DMA 주소를 제한합니다.

    악의적인 디바이스나 버그가 있는 드라이버로부터 메모리를 보호합니다.

  2. 주소 변환 — IOMMU는 디바이스가 사용하는 IOVA를 물리 주소로 변환합니다.

    IOMMU 없으면 DMA 주소 = 물리 주소, IOMMU 있으면 DMA 주소 = IOVA (가상화)

  3. Domain과 Group — 디바이스를 Domain(주소 공간)에 할당하고, Group(격리 단위)으로 관리합니다.

    VFIO는 Group 단위로 디바이스를 가상 머신에 할당합니다.

  4. 실전 활용 — KVM/QEMU 가상화에서 GPU, NVMe 등 물리 디바이스를 VM에 직접 할당(passthrough)할 때 IOMMU가 필수입니다.

    이를 통해 VM이 거의 네이티브 성능으로 디바이스를 사용할 수 있습니다.

관련 문서: DMA (DMA 매핑), PCI/PCIe (PCI 디바이스), 가상화 (KVM) (디바이스 패스스루), 메모리 관리(Memory Management) 개요 (페이지 테이블)

개요

IOMMU(Input-Output Memory Management Unit)는 CPU의 MMU와 유사하게 I/O 디바이스가 메모리에 접근할 때 주소 변환과 보호를 제공합니다.

PCIe Device DMA 요청 IOMMU IOVA → PA 변환/검증 System Memory Domain 기반 격리

주요 목적

하드웨어 구현

벤더 기술 설명
Intel VT-d (Virtualization Technology for Directed I/O) DMA/인터럽트 remapping, PASID 지원
AMD AMD-Vi (I/O Virtualization) IOMMU v2/v3, GCR3 테이블
ARM SMMU (System Memory Management Unit) SMMUv2/v3, Stream ID 기반 매핑

IOMMU 아키텍처

주요 구성 요소

CPU/MMU Virtual Address Physical Memory IOMMU Translation Tables (Page Tables) Root Table | Context Table | 4-level Page Table Interrupt Remapping Table PCIe Root Complex Device 0 (BDF 00:00) Device 1 (BDF 01:00) Device 2 (BDF 02:00) DMA Address Translation

IOMMU Domain

IOMMU domain은 동일한 주소 공간을 공유하는 디바이스 그룹입니다.

struct iommu_domain {
    unsigned int type;            /* IOMMU_DOMAIN_* */
    const struct iommu_ops *ops;
    unsigned long pgsize_bitmap;  /* 지원 페이지 크기 */
    struct iommu_domain_geometry geometry;
    void *iova_cookie;            /* IOVA allocator */
    struct iommu_iotlb_gather iotlb_gather;
};

/* Domain Types */
#define IOMMU_DOMAIN_BLOCKED      0  /* 모든 DMA 차단 */
#define IOMMU_DOMAIN_IDENTITY     1  /* 1:1 매핑 (passthrough) */
#define IOMMU_DOMAIN_UNMANAGED    2  /* 수동 관리 (VFIO) */
#define IOMMU_DOMAIN_DMA          3  /* DMA API 통합 */
#define IOMMU_DOMAIN_DMA_FQ       4  /* Flush Queue 최적화 */
코드 설명

include/linux/iommu.h에 정의된 IOMMU 주소 공간의 핵심 추상화 구조체입니다.

  • typeIOMMU_DOMAIN_BLOCKED(모든 DMA 차단), IOMMU_DOMAIN_IDENTITY(1:1 패스스루), IOMMU_DOMAIN_UNMANAGED(VFIO 수동 관리), IOMMU_DOMAIN_DMA(DMA API 통합), IOMMU_DOMAIN_DMA_FQ(Flush Queue 지연 무효화) 중 하나입니다. 부팅 시 커널 파라미터(iommu=pt 등)와 드라이버 요청에 따라 결정됩니다.
  • ops벤더별 IOMMU 드라이버가 등록하는 iommu_ops 콜백 테이블 포인터입니다. map, unmap, attach_dev 등 실제 하드웨어 조작은 이 콜백을 통해 수행됩니다.
  • pgsize_bitmap해당 domain이 지원하는 페이지 크기를 비트맵으로 표현합니다. 예를 들어 4KB/2MB/1GB를 지원하면 해당 비트가 설정됩니다. iommu_map()은 이 값을 참조하여 huge page 매핑 여부를 결정합니다.
  • iova_cookieDMA domain에서 IOVA 할당자(iova_domain) 또는 MSI cookie를 가리킵니다. drivers/iommu/dma-iommu.ciommu_get_dma_cookie()로 초기화됩니다.
  • iotlb_gatherIOTLB 무효화를 배치 처리하기 위한 누적 구조체입니다. iommu_unmap() 시 범위를 모아두었다가 iommu_iotlb_sync()에서 한 번에 flush하여 성능을 높입니다.

IOMMU Group

IOMMU group은 격리 가능한 최소 디바이스 단위입니다. 같은 그룹의 디바이스는 서로를 구분할 수 없습니다.

struct iommu_group {
    int id;
    struct kobject kobj;
    struct kobject *devices_kobj;
    struct list_head devices;      /* 그룹 내 디바이스 */
    struct iommu_domain *domain;
    void *iommu_data;
};

/* IOMMU Group 확인 */
# ls -l /sys/kernel/iommu_groups/
drwxr-xr-x 2 root root 0 Jan  1 00:00 0
drwxr-xr-x 2 root root 0 Jan  1 00:00 1
drwxr-xr-x 2 root root 0 Jan  1 00:00 2

# ls /sys/kernel/iommu_groups/0/devices/
0000:00:00.0  0000:00:00.1
ACS (Access Control Services)와 IOMMU Group: IOMMU group은 하드웨어 격리 단위입니다. 같은 PCIe 스위치 뒤에 있는 디바이스가 ACS를 지원하지 않으면 하나의 group으로 묶여 개별 passthrough가 불가능합니다. lspci -vvv | grep "Access Control Services"로 ACS 지원 여부를 확인하세요. 서버용 마더보드는 대부분 ACS를 지원하지만, 소비자용 보드는 미지원이 많습니다.

Default Domain 정책

커널은 부팅 시 각 디바이스에 기본 IOMMU domain을 할당합니다. 이 정책은 커널 파라미터와 드라이버 요청에 따라 결정됩니다.

/* Default domain 타입 결정 흐름 */
/*
 * 1. iommu=pt → IOMMU_DOMAIN_IDENTITY (1:1, 변환 없음)
 * 2. iommu.passthrough=1 → IOMMU_DOMAIN_IDENTITY
 * 3. 기본값 → IOMMU_DOMAIN_DMA (DMA API 통합)
 * 4. VFIO가 그룹을 잡으면 → IOMMU_DOMAIN_UNMANAGED
 */

/* sysfs에서 domain 타입 확인/변경 (커널 6.2+) */
# cat /sys/kernel/iommu_groups/1/type
DMA

# 런타임에 identity로 변경 (디바이스에 드라이버가 없을 때만)
# echo identity > /sys/kernel/iommu_groups/1/type

Nested Translation (2단계 변환)

가상화 환경에서 게스트 OS도 IOMMU를 사용할 수 있도록 2단계 주소 변환을 지원합니다. CPU의 EPT/NPT(Extended/Nested Page Tables)와 유사한 개념입니다.

단계 변환 관리 주체
Stage 1 (First Level) GVA/GIOVA → GPA 게스트 OS
Stage 2 (Second Level) GPA → HPA 하이퍼바이저 (호스트)
/* Nested domain 할당 (커널 6.7+) */
struct iommu_domain *nested;
nested = iommu_domain_alloc_user(parent_domain,
    IOMMU_HWPT_ALLOC_NEST_PARENT, user_data);

/* Stage 1: 게스트가 관리하는 페이지 테이블 */
/* Stage 2: 호스트가 관리하는 페이지 테이블 */
/* 최종 물리 주소 = Stage2(Stage1(GIOVA)) */

IOMMU Operations

iommu_ops 구조체(Struct)

struct iommu_ops {
    bool (*capable)(enum iommu_cap);
    struct iommu_domain *(*domain_alloc)(unsigned int type);
    void (*domain_free)(struct iommu_domain *domain);

    int (*attach_dev)(struct iommu_domain *domain,
                      struct device *dev);
    void (*detach_dev)(struct iommu_domain *domain,
                        struct device *dev);

    int (*map)(struct iommu_domain *domain,
               unsigned long iova, phys_addr_t paddr,
               size_t size, int prot, gfp_t gfp);
    size_t (*unmap)(struct iommu_domain *domain,
                   unsigned long iova, size_t size,
                   struct iommu_iotlb_gather *gather);

    phys_addr_t (*iova_to_phys)(struct iommu_domain *domain,
                                 dma_addr_t iova);

    struct iommu_device *(*probe_device)(struct device *dev);
    void (*release_device)(struct device *dev);

    void (*iotlb_sync)(struct iommu_domain *domain,
                       struct iommu_iotlb_gather *gather);
    int (*enable_nesting)(struct iommu_domain *domain);

    /* SVA (Shared Virtual Addressing) */
    int (*sva_bind)(struct device *dev, struct mm_struct *mm,
                   void *drvdata);
    void (*sva_unbind)(struct device *dev, struct mm_struct *mm);

    /* Page Request Interface (PRI) */
    int (*page_response)(struct device *dev,
                          struct iommu_fault_event *evt,
                          struct iommu_page_response *msg);
};
코드 설명

include/linux/iommu.h에 정의된 IOMMU 드라이버 인터페이스입니다. Intel VT-d, AMD-Vi, ARM SMMU 등 각 벤더가 이 콜백을 구현합니다.

  • domain_alloc / domain_freeIOMMU domain(주소 공간)을 할당/해제합니다. 할당 시 벤더별 페이지 테이블 루트를 생성합니다. Intel은 PML4, ARM SMMU는 CD(Context Descriptor)를 초기화합니다.
  • attach_dev / detach_dev디바이스를 domain에 연결/분리합니다. 연결 시 하드웨어 테이블(Intel Context Table, AMD DTE, ARM STE)에 디바이스→domain 매핑을 기록하고 IOTLB를 flush합니다.
  • map / unmapIOVA→물리 주소 매핑을 페이지 테이블에 기록/삭제합니다. prot 인자로 읽기/쓰기 권한을 설정합니다. unmapiotlb_gather에 무효화 범위를 누적합니다.
  • iova_to_physIOVA를 물리 주소로 변환합니다. 페이지 테이블을 소프트웨어로 워크하여 결과를 반환합니다. 디버깅과 VFIO의 DMA 매핑 검증에 사용됩니다.
  • probe_device / release_device디바이스가 IOMMU에 등록/해제될 때 호출됩니다. probe_device는 디바이스의 IOMMU group 결정과 기본 domain 할당을 트리거합니다.
  • sva_bind / sva_unbindSVA(Shared Virtual Addressing)를 위해 디바이스를 프로세스의 mm_struct에 바인딩합니다. PASID Table Entry에 프로세스 페이지 테이블 주소를 기록합니다.
  • page_responsePRI(Page Request Interface) 페이지 폴트에 대한 응답을 디바이스에 전달합니다. 커널이 요청된 페이지를 할당한 후 이 콜백으로 완료를 알립니다.

IOMMU 매핑

/* IOMMU domain 할당 */
struct iommu_domain *domain;
domain = iommu_domain_alloc(&pci_bus_type);

/* 디바이스를 domain에 연결 */
iommu_attach_device(domain, dev);

/* IOVA → Physical Address 매핑 */
unsigned long iova = 0x100000;
phys_addr_t paddr = virt_to_phys(buffer);
size_t size = PAGE_SIZE;
int prot = IOMMU_READ | IOMMU_WRITE;

iommu_map(domain, iova, paddr, size, prot);

/* IOVA로 물리 주소 조회 */
phys_addr_t phys = iommu_iova_to_phys(domain, iova);

/* 매핑 해제 */
iommu_unmap(domain, iova, size);

/* 디바이스 분리 및 domain 해제 */
iommu_detach_device(domain, dev);
iommu_domain_free(domain);
코드 설명

drivers/iommu/iommu.c의 IOMMU 매핑 API를 사용한 전형적인 수동 매핑 시퀀스입니다. VFIO나 커스텀 드라이버에서 DMA API 대신 직접 IOMMU를 제어할 때 이 패턴을 사용합니다.

  • iommu_domain_alloc()버스 타입(예: pci_bus_type)에 연결된 IOMMU 드라이버의 domain_alloc 콜백을 호출하여 새 주소 공간을 생성합니다. 반환된 domain은 IOMMU_DOMAIN_UNMANAGED 타입입니다.
  • iommu_attach_device()디바이스를 domain에 연결합니다. 내부적으로 기존 domain을 detach하고 벤더별 attach_dev 콜백으로 하드웨어 테이블을 갱신합니다.
  • iommu_map()IOVA 0x100000을 버퍼의 물리 주소로 매핑합니다. IOMMU_READ | IOMMU_WRITE 플래그로 읽기/쓰기 권한을 부여합니다. 벤더 드라이버의 map 콜백이 페이지 테이블 엔트리를 기록합니다.
  • iommu_iova_to_phys()매핑이 올바르게 설정되었는지 검증할 때 사용합니다. 소프트웨어 페이지 테이블 워크로 IOVA에 대응하는 물리 주소를 반환합니다.
  • iommu_unmap() → detach → free역순으로 정리합니다. unmap으로 페이지 테이블 엔트리를 제거하고, detach로 하드웨어 연결을 해제한 뒤, domain_free로 페이지 테이블 메모리를 해제합니다.

DMA API 통합 경로

디바이스 드라이버가 dma_map_single()을 호출하면, IOMMU가 활성화된 환경에서는 iommu_dma_ops를 통해 IOVA 할당 → 페이지 테이블 기록 → IOTLB flush가 자동으로 수행됩니다. 이 섹션에서는 DMA API에서 IOMMU 벤더 드라이버까지의 전체 호출 경로를 추적합니다.

DMA API → IOMMU 호출 흐름 디바이스 드라이버 dma_map_single() DMA Core dma_map_page_attrs() iommu_dma_ops iommu_dma_map_page() IOVA 할당 + PT 기록 벤더 iommu_ops .map_pages() HW 페이지 테이블 기록 IOTLB flush dma_direct_* IOMMU 없을 때 직접 경로 fallback iommu_dma_map_page() 내부: ① alloc_iova_fast() → ② iommu_map() → ③ (strict 모드면) iommu_iotlb_sync() iommu_dma_unmap_page() 내부: ① iommu_unmap() → ② iotlb_gather에 범위 누적 → ③ (lazy면 FQ, strict면 즉시 flush) → ④ free_iova_fast()

iommu_dma_ops 설치 경로

/* drivers/iommu/dma-iommu.c */
/* IOMMU가 디바이스를 probe할 때 DMA ops 설치 */
void iommu_setup_dma_ops(struct device *dev)
{
    /* 1. iommu_probe_device()에서 호출 */
    /* 2. DMA domain이면 iommu_dma_ops 설치 */
    /* 3. IDENTITY domain이면 dma_direct_ops 유지 */
    dev->dma_ops = &iommu_dma_ops;
}

/* iommu_dma_ops 구조체 */
static const struct dma_map_ops iommu_dma_ops = {
    .map_page        = iommu_dma_map_page,
    .unmap_page      = iommu_dma_unmap_page,
    .map_sg          = iommu_dma_map_sg,
    .unmap_sg        = iommu_dma_unmap_sg,
    .alloc           = iommu_dma_alloc,
    .free            = iommu_dma_free,
    .mmap            = iommu_dma_mmap,
};

iommu_dma_map_page() 코드 흐름

static dma_addr_t iommu_dma_map_page(
    struct device *dev, struct page *page,
    unsigned long offset, size_t size,
    enum dma_data_direction dir, unsigned long attrs)
{
    struct iommu_domain *domain = ...;
    phys_addr_t phys = page_to_phys(page) + offset;
    dma_addr_t iova;
    int prot;

    /* 1. IOVA 할당 (rcache → rb-tree fallback) */
    iova = iommu_dma_alloc_iova(domain, size, dma_limit, dev);
    if (!iova)
        return DMA_MAPPING_ERROR;

    /* 2. 보호 속성 결정 */
    prot = dma_info_to_prot(dir, dev_is_dma_coherent(dev), attrs);
    /* DMA_TO_DEVICE → IOMMU_READ */
    /* DMA_FROM_DEVICE → IOMMU_WRITE */
    /* DMA_BIDIRECTIONAL → IOMMU_READ | IOMMU_WRITE */

    /* 3. IOMMU 페이지 테이블에 매핑 기록 */
    ret = iommu_map(domain, iova, phys, size, prot, GFP_ATOMIC);

    /* 4. Strict 모드면 IOTLB flush (lazy면 생략) */
    return iova + offset;
}

dma_direct vs iommu_dma 경로 비교

항목 dma_direct (IOMMU 없음/passthrough) iommu_dma (IOMMU 활성)
DMA 주소 물리 주소 그대로 반환 IOVA (가상 DMA 주소) 반환
주소 변환 없음 IOVA → PA (페이지 테이블 워크)
IOVA 할당 불필요 alloc_iova_fast() (rcache/rb-tree)
보안 없음 (디바이스가 전체 메모리 접근 가능) 도메인 격리 (허가된 범위만 접근)
오버헤드(Overhead) 최소 (~수 ns) IOVA 할당 + PT 기록 (~수백 ns~수 µs)
바운스 버퍼(Buffer) 필요할 수 있음 (SWIOTLB) 불필요 (IOVA로 재매핑)
DMA ops 디스패치(Dispatch) 흐름: dma_map_single(dev, ...)dma_map_page_attrs()dev->dma_ops가 NULL이면 dma_direct_map_page(), 설정되어 있으면 ops->map_page() 호출. IOMMU가 활성화되고 DMA domain이 할당된 디바이스는 iommu_dma_ops가 자동 설치됩니다.
GFP_ATOMIC 제약: DMA 매핑은 인터럽트 컨텍스트에서 호출될 수 있어, iommu_map()GFP_ATOMIC이 전달됩니다. 페이지 테이블 할당이 실패하면 DMA_MAPPING_ERROR가 반환되며, 드라이버는 이를 반드시 확인해야 합니다. 대량 DMA 매핑이 예상되면 사전에 페이지 테이블을 pre-allocate하세요.

IOVA 할당자

IOMMU 서브시스템은 디바이스가 사용할 I/O 가상 주소(IOVA)를 관리하기 위해 전용 할당자를 사용합니다. IOVA 할당자는 DMA API(dma_map_*)가 호출될 때 자동으로 IOVA를 할당하고, unmap 시 반환합니다.

IOVA Domain 구조

/* include/linux/iova.h */
struct iova_domain {
    spinlock_t iova_rbtree_lock;
    struct rb_root rbroot;           /* Red-Black Tree로 IOVA 관리 */
    struct rb_node *cached_node;     /* 마지막 할당 노드 캐싱 */
    struct rb_node *cached32_node;   /* 32-bit 장치용 캐시 */
    unsigned long granule;           /* 최소 할당 단위 (보통 PAGE_SIZE) */
    unsigned long start_pfn;         /* IOVA 시작 PFN */
    unsigned long dma_32bit_pfn;     /* 32-bit DMA 경계 */
    struct iova_rcache *rcaches;     /* per-CPU 재활용 캐시 */
};

struct iova {
    struct rb_node node;
    unsigned long pfn_hi;            /* IOVA 범위 상한 PFN */
    unsigned long pfn_lo;            /* IOVA 범위 하한 PFN */
};

IOVA Magazine 캐시(Cache)

IOVA 할당/해제가 빈번한 고성능 네트워크·스토리지 워크로드에서는 RB-tree 잠금 경합(Lock Contention)이 병목(Bottleneck)이 됩니다. 이를 완화하기 위해 per-CPU magazine cache (rcache)를 사용합니다.

/* IOVA 할당 흐름 */
/* 1. per-CPU rcache에서 먼저 시도 (잠금 없음) */
/* 2. 캐시 미스 → RB-tree에서 할당 (spinlock 획득) */
/* 3. 해제 시 rcache에 반환 (캐시 가득 차면 RB-tree로) */

struct iova *iova = alloc_iova(&iovad, size >> iova_shift(&iovad),
                                dma_limit >> iova_shift(&iovad), true);
if (!iova)
    return DMA_MAPPING_ERROR;

/* IOVA 해제 */
free_iova(&iovad, iova->pfn_lo);
성능 팁: iommu.strict=0(Flush Queue 모드)과 rcache를 함께 사용하면, IOTLB 무효화(Invalidation)를 지연(Latency)시키고 IOVA를 per-CPU 캐시에서 빠르게 재활용(Recycling)하여 DMA 매핑 오버헤드를 크게 줄일 수 있습니다.

IOVA 할당자 심층 분석

IOVA 할당은 DMA 매핑 성능의 핵심 요소입니다. 고빈도 네트워크/스토리지 워크로드에서 초당 수백만 건의 IOVA 할당/해제가 발생하므로, per-CPU magazine cache (rcache)를 통한 잠금(Lock) 없는 빠른 경로가 필수적입니다.

IOVA 할당 흐름: rcache Fast Path → rb-tree Slow Path alloc_iova_fast() per-CPU rcache hit? Yes (잠금 없음) loaded magazine IOVA 즉시 반환 No (miss) loaded ↔ prev 매거진 스왑 prev hit? Yes prev magazine IOVA 반환 No rb-tree fallback spinlock + RB-tree 검색 (느린 경로) free_iova_fast(): IOVA → loaded magazine에 반환. 가득 차면 loaded→depot, 새 빈 매거진 할당 depot 가득 참 → 매거진의 모든 IOVA를 rb-tree에 반환

iova_magazine 구조

/* drivers/iommu/iova.c */
#define IOVA_MAG_SIZE    128  /* 매거진당 IOVA 슬롯 수 */

struct iova_magazine {
    unsigned long size;              /* 현재 저장된 IOVA 수 */
    unsigned long pfns[IOVA_MAG_SIZE]; /* IOVA PFN 배열 */
};

struct iova_cpu_rcache {
    spinlock_t lock;
    struct iova_magazine *loaded;    /* 현재 활성 매거진 */
    struct iova_magazine *prev;      /* 이전 매거진 (백업) */
};

struct iova_rcache {
    struct iova_cpu_rcache __percpu *cpu_rcaches;
    struct iova_magazine *depot[MAX_GLOBAL_MAGS]; /* 전역 매거진 풀 */
    unsigned long depot_size;
    spinlock_t depot_lock;
};

/* 크기별 rcache 인덱스: 1 페이지, 2 페이지, ..., 128 페이지 */
/* 요청 크기 → log2 버킷 → 해당 rcache에서 검색 */

alloc_iova_fast() 코드 경로

unsigned long alloc_iova_fast(
    struct iova_domain *iovad,
    unsigned long size,         /* 요청 크기 (페이지 단위) */
    unsigned long limit_pfn,    /* DMA 주소 상한 */
    bool flush_rcache)
{
    unsigned long iova_pfn;

    /* Fast path: per-CPU rcache에서 할당 (잠금 최소) */
    iova_pfn = iova_rcache_get(iovad, size, limit_pfn);
    if (iova_pfn)
        return iova_pfn;

    /* Slow path: rb-tree에서 할당 (spinlock 필요) */
    iova_pfn = __alloc_and_insert_iova_range(iovad, size, limit_pfn);
    return iova_pfn;
}

/* 해제 */
void free_iova_fast(struct iova_domain *iovad,
                     unsigned long pfn, unsigned long size)
{
    /* rcache에 반환 시도 */
    if (iova_rcache_insert(iovad, pfn, size))
        return;

    /* rcache 가득 참 → rb-tree에 직접 반환 */
    free_iova(iovad, pfn);
}
코드 설명

drivers/iommu/iova.c에 구현된 IOVA 할당/해제 함수입니다. per-CPU magazine cache(rcache)를 우선 사용하여 잠금(Lock) 경합을 최소화하는 2단계 설계입니다.

  • alloc_iova_fast() — Fast pathiova_rcache_get()으로 현재 CPU의 magazine cache에서 요청 크기에 맞는 IOVA를 꺼냅니다. spinlock 없이 동작하므로 고빈도 DMA 워크로드에서 핵심 성능 경로입니다.
  • alloc_iova_fast() — Slow pathrcache 미스 시 __alloc_and_insert_iova_range()로 RB-tree에서 할당합니다. iova_rbtree_lock spinlock을 획득해야 하므로 멀티코어 환경에서 경합이 발생할 수 있습니다.
  • free_iova_fast() — rcache 반환iova_rcache_insert()로 해제된 IOVA를 per-CPU cache에 반환합니다. 다음 alloc_iova_fast() 호출 시 즉시 재활용되어 RB-tree 접근을 피합니다.
  • free_iova_fast() — RB-tree fallbackmagazine이 가득 차면 free_iova()로 RB-tree에 직접 반환합니다. magazine 크기는 고정이므로 캐시 오버플로우 시에만 이 경로를 탑니다.

32-bit DMA 별도 경로

32-bit DMA 장치는 4GB 이하 IOVA만 사용할 수 있으므로, iova_domaincached32_node를 별도로 유지합니다.

rb-tree에서 검색 시:

경로 잠금 레이턴시 비고
rcache loaded hit 없음 (per-CPU) ~10-30 ns 최적 경로
rcache prev hit CPU-local spinlock ~30-50 ns loaded/prev 스왑(Swap)
depot → magazine depot spinlock ~50-100 ns 전역 풀 접근
rb-tree fallback iova_rbtree_lock ~100-500 ns 최악 경로 (경합(Contention) 시)
rcache 효율 모니터링: IOVA rcache의 히트율은 /sys/kernel/debug/iova/에서 확인할 수 있습니다(debugfs 활성 시). rcache 히트율이 90% 미만이면 매거진 크기 조정이나 Flush Queue 모드(iommu.strict=0) 활성화를 검토하세요. Flush Queue는 IOVA 재사용을 지연시켜 rcache에 반환되는 IOVA를 늘려 히트율을 높입니다.
Flush Queue와 IOVA 재사용: Strict 모드에서는 unmap 즉시 IOTLB flush + IOVA 반환이 이루어지지만, Flush Queue 모드에서는 IOVA가 FQ에 보류되다가 10ms 타이머(Timer) 후 일괄 flush됩니다. 이 기간 동안 해당 IOVA는 재사용되지 않아 rcache 효율에 영향을 줄 수 있습니다. 그러나 배치 flush로 인한 IOTLB 무효화 횟수 감소가 더 큰 성능 이득을 가져옵니다.

IOMMU Group 형성 알고리즘

IOMMU Group은 하드웨어적으로 격리 가능한 최소 디바이스 집합입니다. 같은 그룹의 디바이스는 서로의 DMA 트래픽을 볼 수 있어, 보안 관점에서 하나의 단위로 취급해야 합니다. 그룹 형성은 PCIe 토폴로지(Topology)와 ACS(Access Control Services) 지원 여부에 의해 결정됩니다.

IOMMU Group 형성 알고리즘 PCI 디바이스 열거 (probe) iommu_probe_device(dev) pci_device_group(dev) Root Port까지 ACS 지원? 전 경로 ACS 지원 독립 Group 개별 passthrough 가능 ACS 미지원 구간 있음 병합 Group 같은 스위치 뒤 디바이스 묶임 ACS (Access Control Services) 필수 비트 SV (Source Validation) | TB (Translation Blocking) | RR (P2P Request Redirect) | CR (P2P Completion Redirect) UF (Upstream Forwarding) | EC (Egress Control) — 모두 활성화되어야 완전한 격리

pci_device_group() 로직

/* drivers/iommu/iommu.c */
struct iommu_group *pci_device_group(struct device *dev)
{
    struct pci_dev *pdev = to_pci_dev(dev);

    /* 1. DMA alias 확인 */
    /*    일부 디바이스는 다른 BDF로 DMA 수행 (quirk) */
    pci_for_each_dma_alias(pdev, get_pci_alias_or_group, &data);

    /* 2. alias 그룹이 이미 있으면 합류 */
    if (data.group)
        return data.group;

    /* 3. PCI Express가 아닌 디바이스 */
    /*    (레거시 PCI: 같은 버스의 모든 장치가 한 그룹) */
    if (!pci_is_pcie(pdev))
        return pci_bus_group(pdev);

    /* 4. Root Port까지 ACS 경로 확인 */
    if (pci_acs_path_enabled(pdev, NULL, REQ_ACS_FLAGS))
        return iommu_group_alloc();  /* 새 독립 그룹 */

    /* 5. ACS 미지원 → 상위 브리지의 기존 그룹에 합류 */
    return find_or_create_bridge_group(pdev);
}

pci_acs_path_enabled() — ACS 체크 체인

/* drivers/pci/acs.c */
bool pci_acs_path_enabled(struct pci_dev *start,
                           struct pci_dev *end,
                           u16 acs_flags)
{
    /* start부터 end(NULL이면 Root Port)까지 경로의
     * 모든 브리지/스위치가 요청된 ACS 비트를 지원하는지 확인 */
    struct pci_dev *pdev = start;
    while (pdev != end) {
        if (!pci_acs_enabled(pdev, acs_flags))
            return false;
        pdev = pci_upstream_bridge(pdev);
    }
    return true;
}

/* 필수 ACS 플래그 */
#define REQ_ACS_FLAGS  (PCI_ACS_SV | PCI_ACS_RR | PCI_ACS_CR | PCI_ACS_UF)
/* SV: Source Validation — 소스 ID 검증 */
/* RR: P2P Request Redirect — P2P 요청을 상위로 리디렉트 */
/* CR: P2P Completion Redirect — P2P 완료를 상위로 리디렉트 */
/* UF: Upstream Forwarding — 상위로 포워딩 차단 */

IOMMU Group 확인 실전

# 시스템의 모든 IOMMU group과 디바이스 목록
for g in /sys/kernel/iommu_groups/*/devices/*; do
    echo "Group $(basename $(dirname $(dirname $g))): $(basename $g)"
done

# ACS 지원 확인
# lspci -vvv -s 01:00.0 | grep -A5 "Access Control Services"
Capabilities: [2d0] Access Control Services
    ACSCap: SrcValid+ TransBlk+ ReqRedir+ CmpltRedir+ UpstreamFwd+ EgressCtrl+ DirectTrans+
    ACSCtl: SrcValid+ TransBlk- ReqRedir+ CmpltRedir+ UpstreamFwd- EgressCtrl- DirectTrans-

# ACS override (보안 위험! 개발/테스트 전용)
# 커널 파라미터: pci=noacs
# 패치: acs_override quirk (비공식)
디바이스 유형 Group 형성 규칙 일반적 결과
Root Port 직접 연결 항상 독립 그룹 GPU, NVMe 등 개별 passthrough 가능
PCIe 스위치 뒤 (ACS 지원) 각각 독립 그룹 서버 보드에서 흔함
PCIe 스위치 뒤 (ACS 미지원) 스위치 뒤 전체 병합 소비자 보드에서 흔함 (VFIO 제약)
레거시 PCI (non-Express) 같은 버스(Bus) 전체 병합 PCI 브리지(Bridge) 뒤 모든 장치
멀티펑션 디바이스 함수 간 ACS에 따라 결정 대부분 같은 그룹
Non-PCI (platform) 디바이스별 독립 또는 DT/ACPI 정의 ARM SoC에서 흔함
ACS Override 보안 경고: pci=noacs나 ACS override 패치(Patch)는 IOMMU group을 인위적으로 분리합니다. 이는 실제 하드웨어 격리를 우회하므로, 같은 스위치 뒤의 악의적 디바이스(또는 VM)가 다른 디바이스의 DMA 트래픽을 가로채거나 조작할 수 있습니다. 운영 환경에서는 절대 사용하지 마세요. 개발/테스트에서만 임시로 사용하세요.
VFIO passthrough 시 Group 전략: GPU passthrough를 위해 독립 group이 필요하면, 서버용 마더보드(ACS 지원)를 사용하거나, GPU를 Root Port에 직접 연결된 슬롯에 장착하세요. find /sys/kernel/iommu_groups/*/devices/ -name "*.0" | wc -l로 독립 group 수를 파악할 수 있습니다.

Intel VT-d

VT-d 데이터 구조

Intel VT-d는 계층적 페이지 테이블 구조를 사용합니다.

Intel IOMMU 페이지 테이블 계층 Root Table (4KB) 256개 엔트리 (버스당 1개) Context Table (4KB per bus) 256개 Context Entry (디바이스/함수별) Context Entry (128-bit per device) • Domain ID • Translation Type (pass-through/translate) • Pointer to Page Table Page Table (4-level): IOVA → Physical Address PML4 → PDPT → PD → PT → 물리 주소 (4KB/2MB/1GB 페이지)

VT-d MMIO 레지스터(Register)

/* VT-d 레지스터 오프셋 */
#define DMAR_VER_REG        0x00   /* Version */
#define DMAR_CAP_REG        0x08   /* Capability */
#define DMAR_ECAP_REG       0x10   /* Extended Capability */
#define DMAR_GCMD_REG       0x18   /* Global Command */
#define DMAR_GSTS_REG       0x1c   /* Global Status */
#define DMAR_RTADDR_REG     0x20   /* Root Table Address */
#define DMAR_CCMD_REG       0x28   /* Context Command */
#define DMAR_IVA_REG        0x50   /* Invalidate Address */
#define DMAR_IOTLB_REG      0x58   /* IOTLB Invalidate */
#define DMAR_IRTA_REG       0xb8   /* Interrupt Remapping Table */

/* Capability 비트 */
#define DMA_CAP_SAGAW       ((1 << 8) - 1)  /* Supported AGW */
#define DMA_CAP_CM          (1 << 7)         /* Caching Mode */
#define DMA_CAP_PI          (1 << 59)        /* Posted Interrupts */

DMAR (DMA Remapping)

ACPI DMAR 테이블은 VT-d 하드웨어 유닛을 기술합니다.

/* ACPI DMAR 테이블 파싱 */
# dmesg | grep -i dmar
DMAR: Host address width 39
DMAR: DRHD base: 0x00000000fed90000 flags: 0x1
DMAR: RMRR base: 0x0000000079800000 end: 0x000000007affffff
DMAR: IOMMU enabled

/* 커널 파라미터 */
intel_iommu=on               # VT-d 활성화
intel_iommu=on,sm_on         # Scalable Mode 활성화
intel_iommu=off              # VT-d 비활성화
iommu=pt                     # Pass-through 모드 (성능 우선)

Intel Scalable Mode (확장 모드)

Intel VT-d의 Scalable Mode는 Legacy Mode를 대체하는 확장 아키텍처로, PASID 기반 주소 공간 분리와 First/Second Level 페이지 테이블을 지원합니다. SVA, Nested Translation, Posted Interrupt 등 현대적 기능은 Scalable Mode에서만 동작합니다.

Intel VT-d Scalable Mode 구조 Root Table 256 entries (bus 별) Scalable Mode Context Entry (128-bit) P (Present) PASID Table Pointer PDTS (PASID Dir Size) PRE, PASID Enable, Nesting PASID Table Entry (64 bytes) FLPTPTR (First Level PT) PGTT, SRE, EAFE SLPTPTR (Second Level PT) AW (Address Width) First Level Page Table = CPU x86 PTE 포맷 SVA: CR3 직접 공유 가능 Second Level Page Table = IOMMU 전용 포맷 DMA remapping 전용 PGTT=001: First Level Only PGTT=010: Second Level Only PGTT=011: Nested (First→Second 2단계 변환)

Legacy vs Scalable 모드 비교

항목 Legacy Mode Scalable Mode
Context Entry 64-bit 128-bit
PASID 지원 제한적 (Extended Context) 네이티브 (PASID Table)
First Level PT 미지원 지원 (CPU PTE 호환)
Nested Translation 미지원 지원 (PGTT=011)
SVA/SVM 미지원 지원 (First Level + PASID)
5-Level PT (57-bit) 미지원 지원
커널 파라미터 기본 (구형 HW) intel_iommu=sm_on

PASID Table Entry 구조 (64 bytes)

/* drivers/iommu/intel/pasid.c */
/* PASID Table Entry — 512-bit (64 bytes, 8 × u64) */
struct pasid_entry {
    u64 val[8];
};

/* 주요 필드 (val[0]) */
#define PASID_ENTRY_P       (1ULL << 0)   /* Present */
#define PASID_ENTRY_PGTT    (7ULL << 6)   /* Page Table Type [8:6] */
#define PASID_ENTRY_SRE     (1ULL << 1)   /* Supervisor Request Enable */
#define PASID_ENTRY_EAFE    (1ULL << 7)   /* Execute Access Fault Enable */

/* PGTT (Page Table Type) 값 */
#define PASID_PGTT_FL_ONLY  1   /* First Level Only (SVA) */
#define PASID_PGTT_SL_ONLY  2   /* Second Level Only (DMA) */
#define PASID_PGTT_NESTED   3   /* Nested (FL→SL, 가상화) */
#define PASID_PGTT_PT       4   /* Pass-through (변환 없음) */

/* First Level PT 포인터 (val[2]) */
/* FLPTPTR[63:12] = First Level PML5/PML4 주소 */
/* SVA: 프로세스의 CR3 값을 직접 기록 */

/* Second Level PT 포인터 (val[0]) */
/* SLPTPTR[63:12] = Second Level PML4 주소 */

Scalable Mode 설정 코드

/* drivers/iommu/intel/iommu.c */
static int intel_iommu_init(void)
{
    /* ECAP_SMTS 비트로 Scalable Mode 지원 확인 */
    if (ecap_smts(iommu->ecap)) {
        /* Root Table의 RTADDR_REG[11] = TTM (Translation Table Mode) */
        /* TTM=01: Scalable Mode */
        iommu->root_entry[bus].hi |= ROOT_ENTRY_TABLE_TYPE;
    }
}

/* Scalable Mode Context Entry 설정 */
static int domain_context_mapping_one(...)
{
    /* 128-bit Context Entry 기록 */
    context->lo = PASID_TABLE_ADDR | CONTEXT_PRESENT;
    context->hi = CONTEXT_TT_DEV_IOTLB |  /* ATS 지원 */
                  CONTEXT_PASIDE;            /* PASID 활성화 */
}
코드 설명

drivers/iommu/intel/iommu.c에 구현된 Intel VT-d Scalable Mode 초기화 흐름입니다. Scalable Mode는 PASID, SVA, Nested Translation 등 현대적 기능의 전제 조건입니다.

  • ecap_smts()ECAP(Extended Capability) 레지스터의 SMTS 비트를 확인하여 하드웨어가 Scalable Mode를 지원하는지 검사합니다. 미지원 시 Legacy Mode로 동작합니다.
  • ROOT_ENTRY_TABLE_TYPERTADDR_REG(Root Table Address Register)의 TTM(Translation Table Mode) 비트를 01로 설정하여 Scalable Mode를 활성화합니다. Legacy Mode는 TTM=00입니다.
  • domain_context_mapping_one()Scalable Mode에서는 Context Entry가 128비트로 확장됩니다. lo 필드에 PASID Table의 물리 주소와 Present 비트를, hi 필드에 ATS 지원(TT_DEV_IOTLB)과 PASID 활성화(PASIDE) 플래그를 기록합니다.
Scalable Mode 활성화: intel_iommu=sm_on 커널 파라미터로 활성화합니다. 최신 커널(6.0+)에서는 하드웨어가 지원하면 기본 활성화됩니다 (CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON=y). dmesg | grep "Scalable"으로 활성화 여부를 확인하세요.
PGTT=001 (First Level Only)과 SVA: SVA 바인딩 시 PASID Table Entry의 FLPTPTR에 프로세스(Process)의 CR3 값(pgd 물리 주소)을 직접 기록합니다. IOMMU가 CPU와 동일한 x86 PTE를 워크하므로, 디바이스는 유저 공간 가상 주소로 직접 DMA할 수 있습니다. 페이지 폴트(Page Fault) 시 PRI를 통해 커널이 페이지(Page)를 할당합니다.

IOMMU 페이지 테이블 워크

IOMMU의 페이지 테이블 워크는 IOVA를 물리 주소로 변환하는 핵심 과정입니다. Intel, AMD, ARM 세 아키텍처 모두 계층적 페이지 테이블을 사용하지만, 엔트리 포맷과 레벨 구조가 다릅니다.

IOMMU 페이지 테이블 워크 비교 Intel VT-d Root Table [Bus] Context Table [Dev:Fn] PML4 [47:39] PDPT [38:30] ← 1GB PD [29:21] ← 2MB PT [20:12] Physical Address AMD-Vi Device Table [BDF] DTE → PT root Level 4 [47:39] Level 3 [38:30] ← 1GB Level 2 [29:21] ← 2MB Level 1 [20:12] Physical Address ARM SMMUv3 Stream Table [SID] Context Descriptor [SSID] L0 (TTB) [47:39] L1 [38:30] ← 1GB L2 [29:21] ← 2MB L3 [20:12] Physical Address 48-bit IOVA 비트 분해 (4-level, 4KB 페이지) [47:39] L4/PML4 인덱스 | [38:30] L3/PDPT 인덱스 | [29:21] L2/PD 인덱스 | [20:12] L1/PT 인덱스 | [11:0] 페이지 오프셋

Intel 페이지 테이블 워크

/* drivers/iommu/intel/iommu.c */
/* IOVA → PTE 조회 (Second Level) */
static struct dma_pte *pfn_to_dma_pte(
    struct dmar_domain *domain,
    unsigned long pfn,
    int *target_level)
{
    struct dma_pte *parent, *pte;
    int level = agaw_to_level(domain->agaw);

    parent = domain->pgd;  /* PML4 (최상위) */

    while (level > 0) {
        int offset = pfn_level_offset(pfn, level);
        pte = &parent[offset];

        /* 대형 페이지 확인: SP 비트 설정이면 워크 중단 */
        if (dma_pte_superpage(pte) || level == 1) {
            *target_level = level;
            return pte;
        }

        /* 다음 레벨 테이블 주소 추출 */
        parent = phys_to_virt(dma_pte_addr(pte));
        level--;
    }
    return NULL;
}

ARM SMMUv3 Stream Table 구조

/* ARM SMMUv3: Linear vs 2-Level Stream Table */

/* Linear Stream Table: SID 직접 인덱싱 */
/* 크기 = num_sids × 64 bytes (STE 크기) */
/* 적은 디바이스 수에 적합 */

/* 2-Level Stream Table: SID를 2단계로 분해 */
/* L1: SID[N:8] → L1 Descriptor (L2 테이블 주소) */
/* L2: SID[7:0] → STE (Stream Table Entry) */
/* 많은 디바이스에서 메모리 절약 */

struct arm_smmu_ste {
    __le64 data[8];  /* 512-bit (64 bytes) */
};

/* STE → Context Descriptor → TTB (Translation Table Base) */
/* Stage 1: TTB0 → LPAE 워크 (VA→IPA) */
/* Stage 2: VMID + VTTBR → LPAE 워크 (IPA→PA) */

주소 폭(AGAW) 비교

아키텍처 주소 폭 레벨 수 디바이스 인덱싱
Intel VT-d 39/48/57-bit 3/4/5-level Root Table [Bus] → Context [Dev:Fn]
AMD-Vi 48/57-bit 4/5-level Device Table [BDF] 직접
ARM SMMUv3 32/36/40/42/44/48-bit 1~4-level Stream Table [SID] → CD [SSID]
대형 페이지 Intel VT-d AMD-Vi ARM SMMU
2MB Level 2 (SP=1) Level 2 (NL=0) Level 2 Block
1GB Level 3 (SP=1) Level 3 (NL=0) Level 1 Block
효과 워크 레벨 단축 + IOTLB 커버리지 증가 → 성능 향상
대형 페이지 최적화: 2MB/1GB 대형 페이지를 사용하면 페이지 테이블 워크 레벨이 줄어들고, 하나의 IOTLB 엔트리가 더 넓은 범위를 커버합니다. 대용량 DMA 버퍼(GPU 프레임버퍼, RDMA 영역)에서 특히 효과적입니다. iommu_map() 호출 시 pgsize_bitmap에 따라 자동으로 최적 페이지 크기를 선택합니다.
5-Level 페이지 테이블 (57-bit): Intel VT-d Scalable Mode와 AMD-Vi v3는 5-level 페이지 테이블을 지원하여 최대 128PB(57-bit) 주소 공간을 제공합니다. 이는 CXL(Compute Express Link) 메모리 확장과 대규모 메모리 풀링에 필요합니다. ARM SMMUv3는 48-bit가 최대이며, ARMv8.2-LVA 확장에서 52-bit를 지원합니다.

AMD-Vi (AMD I/O Virtualization)

AMD-Vi는 AMD 플랫폼의 IOMMU 구현으로, Intel VT-d와 유사한 기능을 제공하지만 다른 하드웨어 구조를 사용합니다.

AMD-Vi 데이터 구조

AMD-Vi는 Device Table을 사용하여 디바이스별 설정을 관리합니다. Intel의 Root/Context 2단계 구조와 달리, 단일 Device Table에서 BDF(Bus/Device/Function)를 직접 인덱싱합니다.

AMD-Vi 페이지 테이블 구조 Device Table (DTE) BDF 직접 인덱싱 (최대 65,536 엔트리) Device Table Entry (256-bit) V (Valid) TV (Translation Valid) Domain ID Page Table Root Pointer IntTabLen / IntCtl GCR3 Table Root (v2, PASID용) I/O Page Table 4-level (4KB/2MB/1GB) GCR3 Table (v2) PASID → Guest CR3
/* AMD IOMMU Device Table Entry (drivers/iommu/amd/amd_iommu_types.h) */
struct dev_table_entry {
    u64 data[4];   /* 256-bit (32 바이트) */
};

/* DTE 주요 비트 필드 */
#define DTE_FLAG_V       (1ULL << 0)   /* Valid */
#define DTE_FLAG_TV      (1ULL << 1)   /* Translation Valid */
#define DTE_FLAG_IR      (1ULL << 61)  /* Interrupt Remapping */
#define DTE_FLAG_IW      (1ULL << 62)  /* Interrupt Write */
#define DTE_FLAG_IOTLB   (1ULL << 9)   /* IOTLB 지원 (ATS) */
#define DTE_FLAG_GV      (1ULL << 2)   /* Guest Translation Valid (v2) */

AMD-Vi 커널 파라미터

# AMD IOMMU 활성화 (대부분 자동 감지)
amd_iommu=on                  # 명시적 활성화
amd_iommu=off                 # 비활성화
amd_iommu=force_isolation     # 모든 디바이스 격리 강제
iommu=pt                      # pass-through 모드 (Intel/AMD 공통)

# IVRS (I/O Virtualization Reporting Structure) 확인
# dmesg | grep -i amd
AMD-Vi: Found IOMMU at 0000:00:00.2 cap 0x40
AMD-Vi: Interrupt remapping enabled
AMD-Vi: Lazy IO/TLB flushing enabled
Intel VT-d vs AMD-Vi 핵심 차이: Intel은 ACPI DMAR 테이블로 IOMMU 유닛을 기술하고, AMD는 ACPI IVRS(I/O Virtualization Reporting Structure) 테이블을 사용합니다. 또한 Intel은 Root/Context 2단계 테이블, AMD는 단일 Device Table을 사용하여 디바이스를 식별합니다.

Interrupt Remapping

Interrupt remapping은 MSI/MSI-X 인터럽트를 IOMMU가 가로채서 재매핑합니다. 이는 interrupt injection 공격을 방지합니다.

Interrupt Remapping Table Entry (IRTE)

struct irte {
    u64 present       : 1;
    u64 fpd           : 1;   /* Fault Processing Disable */
    u64 dst_mode      : 1;   /* Destination Mode (physical/logical) */
    u64 redir_hint    : 1;   /* Redirection Hint */
    u64 trigger_mode  : 1;   /* Trigger Mode (edge/level) */
    u64 dlvry_mode    : 3;   /* Delivery Mode */
    u64 avail         : 4;
    u64 reserved      : 3;
    u64 irte_mode     : 1;   /* 0=Remapped, 1=Posted */
    u64 vector        : 8;   /* Interrupt Vector */
    u64 reserved2     : 8;
    u64 dest_id       : 32;  /* Destination APIC ID */
};

/* Interrupt remapping 활성화 */
# dmesg | grep -i "interrupt remapping"
DMAR-IR: Enabled IRQ remapping in x2apic mode

Posted Interrupts

Posted Interrupts는 가상화 환경에서 인터럽트를 VM에 직접 전달하여 VM-exit를 줄입니다.

struct pi_desc {
    u32 pir[8];          /* Posted Interrupt Requests (256-bit) */
    u32 control;          /* ON, SN, NV */
    u32 rsvd[7];
} __aligned(64);

/* VT-d Posted Interrupt Descriptor Address */
#define IRTE_PI_ADDR_MASK    0xfffffffffff00000

PASID (Process Address Space ID)

PASID는 단일 디바이스가 여러 주소 공간(프로세스)을 구분할 수 있게 합니다.

PASID Table

struct pasid_entry {
    u64 present       : 1;
    u64 pwt           : 1;   /* Page-level Write-Through */
    u64 pcd           : 1;   /* Page-level Cache Disable */
    u64 reserved      : 9;
    u64 slptptr       : 52;  /* Second Level Page Table Pointer */
    u64 pgtt          : 3;   /* Page Table Type */
    u64 reserved2     : 61;
};

/* PASID 할당 */
int pasid;
pasid = iommu_sva_alloc_pasid(dev, 1, (1 << 20) - 1);

SVA (Shared Virtual Addressing)

SVA는 CPU와 디바이스가 동일한 가상 주소 공간을 공유합니다. CPU의 페이지 테이블을 IOMMU가 직접 사용합니다.

#include <linux/iommu.h>

/* SVA 바인딩 */
struct iommu_sva *sva;
sva = iommu_sva_bind_device(dev, current->mm, NULL);
if (IS_ERR(sva))
    return PTR_ERR(sva);

/* PASID 획득 */
int pasid = iommu_sva_get_pasid(sva);

/* 디바이스는 이제 user space 가상 주소 직접 사용 가능 */
void *user_buf = mmap(...);
device_submit_command(user_buf, pasid);  /* CPU와 동일 VA */

/* SVA 언바인딩 */
iommu_sva_unbind_device(sva);

ATS / PRI (PCIe IOMMU 가속)

ATS(Address Translation Services)와 PRI(Page Request Interface)는 PCIe 디바이스가 IOMMU와 직접 상호작용하여 성능을 높이는 PCIe 확장 기능입니다.

ATS (Address Translation Services)

ATS를 지원하는 디바이스는 IOMMU에 주소 변환을 요청하여 결과를 디바이스 내부 ATC(Address Translation Cache)에 캐싱합니다. 이후 DMA 요청 시 IOMMU 페이지 테이블 워크를 건너뛸 수 있어 레이턴시가 줄어듭니다.

/* ATS 활성화 (drivers/pci/ats.c) */
int pci_enable_ats(struct pci_dev *dev, int ps)
{
    u16 ctrl;
    if (!dev->ats_cap)
        return -EINVAL;

    pci_read_config_word(dev, dev->ats_cap + PCI_ATS_CTRL, &ctrl);
    ctrl |= PCI_ATS_CTRL_ENABLE;
    ctrl |= PCI_ATS_CTRL_STU(ps);    /* Smallest Translation Unit */
    pci_write_config_word(dev, dev->ats_cap + PCI_ATS_CTRL, ctrl);

    dev->ats_enabled = 1;
    return 0;
}

/* ATS 무효화 (IOMMU가 매핑 변경 시) */
void pci_ats_invalidate(struct pci_dev *dev, u16 did,
                         unsigned long addr, unsigned int size);

PRI (Page Request Interface)

PRI는 SVA 환경에서 디바이스가 매핑되지 않은 페이지에 접근할 때 page fault를 발생시키고, OS가 페이지를 할당한 뒤 디바이스에 응답하는 메커니즘입니다. CPU의 demand paging과 유사합니다.

/* PRI 활성화 */
pci_enable_pri(pdev, 32);   /* 최대 32개 outstanding page request */

/* Page Request 처리 흐름 */
/*  1. 디바이스가 IOVA에 접근 → IOMMU 변환 실패 */
/*  2. IOMMU가 Page Request 큐에 이벤트 기록 */
/*  3. 커널이 페이지 할당/매핑 후 Page Response 전송 */
/*  4. 디바이스가 DMA 재시도 */

/* IOMMU 드라이버에서 PRI 이벤트 처리 */
static irqreturn_t priq_event_thread(int irq, void *data)
{
    struct arm_smmu_device *smmu = data;
    /* Page Request Queue에서 이벤트 읽기 */
    /* handle_mm_fault() 호출 → 페이지 할당 */
    /* iommu_page_response() 로 디바이스에 응답 */
    return IRQ_HANDLED;
}
주의: ATS는 디바이스 내부 ATC를 신뢰하므로, 악의적인 디바이스가 잘못된 변환 결과를 사용할 수 있습니다. 보안이 중요한 환경에서는 ATS를 비활성화하거나, VFIO passthrough 시 ATS 사용 여부를 신중히 판단해야 합니다. 커널 파라미터 pci=noats로 전역 비활성화할 수 있습니다.

IOPF (I/O Page Fault) 프레임워크

IOPF(I/O Page Fault)는 SVA/PRI 환경에서 디바이스가 매핑되지 않은 가상 주소에 접근할 때 발생하는 복구 가능한 fault를 처리하는 커널 프레임워크입니다. CPU의 demand paging과 유사하게, 디바이스 DMA도 lazy하게 페이지를 할당받을 수 있습니다.

IOPF (I/O Page Fault) 처리 흐름 디바이스 DMA (VA) IOMMU PT miss! PRI/PRIQ Page Request iopf workqueue iommu_report_device_fault() → iopf_handler() handle_mm_fault() 페이지 할당/매핑 → PT 업데이트 iommu_page_response() 성공/실패 응답 IOMMU 캐시 갱신 DMA 재시도 IOPF vs DMA Fault 구분 IOPF (복구 가능) SVA/PRI 활성 디바이스, handle_mm_fault()로 해결 DMA Fault (복구 불가) 매핑 오류, 권한 위반 → 커널 로그 + 디바이스 정지

IOPF 주요 구조체

/* drivers/iommu/io-pgfault.c */
struct iopf_fault {
    struct iommu_fault fault;    /* fault 정보 */
    struct list_head list;       /* 그룹 fault 연결 */
};

struct iopf_group {
    struct iopf_fault last_fault; /* 마지막 fault (응답 대상) */
    struct list_head faults;     /* 같은 Group ID의 fault 목록 */
    struct list_head pending;    /* 보류 큐 */
    struct device *dev;
    struct work_struct work;     /* workqueue에서 처리 */
};

/* Fault 유형 */
struct iommu_fault {
    u32 type;                    /* IOMMU_FAULT_DMA_UNRECOV 또는
                                  * IOMMU_FAULT_PAGE_REQ */
    struct iommu_fault_page_request prm; /* page request 정보 */
};

struct iommu_fault_page_request {
    u32 flags;                   /* IOMMU_FAULT_PAGE_REQUEST_PASID_VALID 등 */
    u32 pasid;                   /* 요청 프로세스 PASID */
    u32 grpid;                   /* Group ID (관련 fault 묶기) */
    u32 perm;                    /* 요청 권한 (R/W/X) */
    u64 addr;                    /* fault 주소 (가상 주소) */
};

IOPF 핸들러(Handler) 코드 경로

/* IOMMU 드라이버가 fault 보고 */
iommu_report_device_fault(dev, &fault_evt);
    /* → iopf_group에 fault 추가 */
    /* → last_fault(LPSG=1)이면 workqueue 스케줄 */

/* workqueue에서 실행 */
static void iopf_handler(struct work_struct *work)
{
    struct iopf_group *group = ...;

    /* SVA 핸들러 호출 */
    ret = iommu_sva_handle_iopf(group);
    /* 내부:
     * 1. PASID → mm_struct 조회
     * 2. mmap_read_lock(mm)
     * 3. handle_mm_fault(vma, addr, flags)
     *    → 일반 page fault 처리 (demand paging)
     * 4. mmap_read_unlock(mm)
     */

    /* 응답 전송 */
    struct iommu_page_response resp = {
        .code = ret ? IOMMU_PAGE_RESP_FAILURE
                    : IOMMU_PAGE_RESP_SUCCESS,
        .grpid = group->last_fault.fault.prm.grpid,
        .pasid = group->last_fault.fault.prm.pasid,
    };
    iommu_page_response(dev, &group->last_fault.fault, &resp);
}

IOPF 지원 매트릭스

구성 요소 Intel VT-d AMD-Vi v2 ARM SMMUv3
PRI 지원 Scalable Mode 필요 PPR (Peripheral Page Request) PRIQ (PRI Queue)
PASID 필수 예 (SSID)
fault 보고 경로 QI → prq_event_thread PPR Log → ppr_event_thread PRIQ → priq_event_thread
응답 경로 QI: Page Group Response Command Buffer: COMPLETE_PPR CMDQ: CMD_PRI_RESP
커널 설정 CONFIG_INTEL_IOMMU_SVM CONFIG_AMD_IOMMU_V2 CONFIG_ARM_SMMU_V3_SVA
Group Fault: 디바이스는 하나의 DMA 작업에서 여러 페이지에 동시에 접근할 수 있습니다. IOMMU는 이를 같은 Group ID로 묶어 보고하며, 마지막 fault에 LAST_PAGE(LPSG) 플래그를 설정합니다. 커널은 그룹의 모든 fault를 처리한 후 한 번의 page_response()로 응답합니다.
IOPF 성능 영향: IOPF는 디바이스 DMA를 중단시키고 커널의 page fault 처리를 기다리므로, 레이턴시가 수십~수백 µs 추가됩니다. 대역폭(Bandwidth) 민감한 워크로드(NVMe, GPU)에서는 사전에 mlock()이나 pin_user_pages()로 페이지를 고정하여 IOPF 발생을 최소화하세요.

VFIO (Virtual Function I/O)

VFIO는 IOMMU를 활용하여 userspace에서 안전하게 디바이스를 제어할 수 있게 합니다.

VFIO Container & Group

/* VFIO 사용 예시 (QEMU/KVM) */
# modprobe vfio-pci

# PCI 디바이스를 vfio-pci로 바인딩
# echo 0000:01:00.0 > /sys/bus/pci/devices/0000\:01\:00.0/driver/unbind
# echo 8086 10fb > /sys/bus/pci/drivers/vfio-pci/new_id

# IOMMU group 확인
# readlink /sys/bus/pci/devices/0000:01:00.0/iommu_group
../../../kernel/iommu_groups/1

# QEMU에서 디바이스 패스스루
qemu-system-x86_64 \
  -device vfio-pci,host=01:00.0 \
  ...

VFIO API

#include <linux/vfio.h>

/* VFIO container 열기 */
int container = open("/dev/vfio/vfio", O_RDWR);

/* IOMMU group 열기 */
int group = open("/dev/vfio/1", O_RDWR);

/* Group을 container에 추가 */
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);

/* IOMMU type 설정 (Type1 = DMA remapping) */
ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);

/* DMA 매핑 */
struct vfio_iommu_type1_dma_map dma_map = {
    .argsz = sizeof(dma_map),
    .flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
    .vaddr = (__u64)buffer,
    .iova = 0x100000,
    .size = 4096,
};
ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
코드 설명

VFIO 유저스페이스 API를 사용한 디바이스 패스스루(passthrough) DMA 매핑 시퀀스입니다. QEMU/KVM 가상화에서 물리 디바이스를 VM에 직접 할당할 때 이 패턴을 사용합니다.

  • /dev/vfio/vfioVFIO container를 엽니다. Container는 하나의 IOMMU domain을 나타내며, 여러 IOMMU group을 포함할 수 있습니다.
  • /dev/vfio/1IOMMU group 1을 엽니다. 그룹 번호는 /sys/kernel/iommu_groups/에서 확인할 수 있으며, 같은 그룹의 모든 디바이스가 함께 할당됩니다.
  • VFIO_GROUP_SET_CONTAINERgroup을 container에 추가합니다. 커널 내부에서 iommu_group_claim_dma_owner()가 호출되어 기존 DMA domain이 해제됩니다.
  • VFIO_SET_IOMMUVFIO_TYPE1_IOMMU를 설정하여 DMA remapping 모드를 활성화합니다. 커널이 IOMMU_DOMAIN_UNMANAGED domain을 할당하고 디바이스를 attach합니다.
  • VFIO_IOMMU_MAP_DMA유저 버퍼의 가상 주소(vaddr)를 IOVA 0x100000에 매핑합니다. 커널이 유저 페이지를 pin하고 iommu_map()으로 IOVA→물리 주소 매핑을 설정합니다. VM에서 디바이스가 이 IOVA로 DMA하면 호스트 물리 메모리에 접근합니다.
참고: VFIO와 mdev의 전체 API 흐름, mdev_parent_ops 구현, GPU passthrough 실전 설정, SR-IOV VF mdev 패턴은 VFIO & mdev (디바이스 패스스루) 페이지에서 자세히 다룹니다.

Domain Attach/Switch 경로

IOMMU domain의 attach/detach는 디바이스의 DMA 주소 공간을 전환하는 핵심 동작입니다. 부팅 시 DMA domain → VFIO 바인딩 시 UNMANAGED domain → VFIO 해제 시 DMA domain 복귀가 전형적인 라이프사이클입니다.

IOMMU Domain 라이프사이클 부팅/Probe iommu_probe_device() DMA Domain 기본 주소 변환 UNMANAGED VFIO passthrough IDENTITY 1:1 매핑 (iommu=pt) BLOCKED 모든 DMA 차단 attach VFIO claim VFIO release sysfs 전환 detach 전환 시 반드시 수행: ① 이전 domain detach → ② IOTLB 전체 flush → ③ 새 domain attach → ④ HW context 업데이트 커널 5.18+: iommu_group_claim_dma_owner()로 VFIO 전환 시 group 전체를 원자적으로 claim

iommu_attach_device() 내부

/* drivers/iommu/iommu.c */
int iommu_attach_device(struct iommu_domain *domain,
                         struct device *dev)
{
    struct iommu_group *group = dev->iommu_group;

    /* 1. 현재 domain detach */
    __iommu_detach_device(group->domain, dev);

    /* 2. 벤더별 attach 콜백 */
    ret = domain->ops->attach_dev(domain, dev);
    /* Intel: intel_iommu_attach_device()
     *   → context entry 기록 + root table 업데이트
     *   → qi_flush_context() + qi_flush_iotlb()
     * AMD: amd_iommu_attach_device()
     *   → DTE(Device Table Entry) 업데이트
     *   → command buffer로 DTE + IOTLB flush
     * ARM: arm_smmu_attach_dev()
     *   → STE(Stream Table Entry) 업데이트
     *   → CMDQ: CFGI_STE + TLBI_NSNH_ALL */

    /* 3. group->domain 갱신 */
    group->domain = domain;
    return ret;
}
코드 설명

drivers/iommu/iommu.c의 domain attach 핵심 함수입니다. 디바이스의 DMA 주소 공간을 전환하는 원자적(atomic) 동작입니다.

  • __iommu_detach_device()현재 domain에서 디바이스를 분리합니다. 벤더 드라이버의 detach_dev 콜백이 하드웨어 테이블에서 디바이스 엔트리를 제거하고 IOTLB를 flush합니다.
  • domain->ops->attach_dev()새 domain의 벤더별 attach 콜백을 호출합니다. Intel VT-d는 Context Entry를 기록하고 QI(Queued Invalidation)로 flush하며, AMD는 DTE(Device Table Entry)를 갱신하고 Command Buffer로 flush합니다. ARM SMMU는 STE(Stream Table Entry)를 갱신하고 CMDQ로 CFGI_STE + TLBI_NSNH_ALL 명령을 전송합니다.
  • group->domain = domain그룹의 현재 domain 포인터를 갱신합니다. 이후 같은 그룹의 다른 디바이스도 이 domain의 주소 공간을 공유합니다.

VFIO DMA Owner Claim (커널 5.18+)

/* VFIO가 디바이스 그룹을 점유할 때 */
int iommu_group_claim_dma_owner(struct iommu_group *group,
                                  void *owner)
{
    /* 1. 그룹 내 모든 디바이스가 바인딩된 드라이버 없는지 확인 */
    /*    (커널 드라이버 → vfio-pci로 전환 완료 후 호출) */
    /* 2. 기존 DMA domain detach */
    /* 3. BLOCKED domain 임시 attach (DMA 차단) */
    /* 4. owner 기록 → UNMANAGED domain attach 허용 */
}

/* VFIO 해제 시 */
void iommu_group_release_dma_owner(struct iommu_group *group)
{
    /* 1. UNMANAGED domain detach */
    /* 2. 기본 DMA domain 복원 attach */
    /* 3. owner 해제 */
}
코드 설명

drivers/iommu/iommu.c에 구현된 VFIO DMA 소유권 관리 함수입니다. 커널 5.18+에서 도입되어, VFIO가 디바이스 그룹을 점유/해제할 때 domain 전환을 안전하게 관리합니다.

  • iommu_group_claim_dma_owner()VFIO가 그룹을 점유할 때 호출됩니다. 먼저 그룹 내 모든 디바이스에 커널 드라이버가 바인딩되어 있지 않은지 확인합니다(vfio-pci 전환 완료 후). 기존 DMA domain을 detach하고 IOMMU_DOMAIN_BLOCKED domain을 임시 attach하여 DMA를 완전히 차단합니다. 이후 VFIO가 IOMMU_DOMAIN_UNMANAGED domain을 attach할 수 있게 됩니다.
  • iommu_group_release_dma_owner()VFIO 해제 시 호출됩니다. UNMANAGED domain을 detach하고, 부팅 시 할당된 기본 DMA domain을 복원합니다. 이후 커널 드라이버가 다시 바인딩되면 정상적인 DMA API를 사용할 수 있습니다.

sysfs Domain 타입 전환 (커널 6.2+)

# 현재 domain 타입 확인
# cat /sys/kernel/iommu_groups/3/type
DMA

# DMA → identity 전환 (디바이스에 바인딩된 드라이버 없을 때만)
# echo identity > /sys/kernel/iommu_groups/3/type

# 허용 타입 목록
# cat /sys/kernel/iommu_groups/3/available_types
DMA DMA-FQ identity

# Flush Queue 모드로 전환
# echo DMA-FQ > /sys/kernel/iommu_groups/3/type
핫플러그(Hotplug) 주의: PCIe 핫플러그로 디바이스가 추가되면 iommu_probe_device()가 호출되어 기본 domain에 자동 attach됩니다. 그러나 같은 IOMMU group 내 다른 디바이스가 이미 VFIO에 할당된 경우, 새 디바이스의 group 배치와 domain 전환에 충돌이 발생할 수 있습니다. 핫플러그 시나리오에서는 group 구성을 사전에 검증하세요.
Domain 전환 성능: Domain 전환은 IOTLB 전체 flush를 수반하므로 일시적 성능 저하가 발생합니다. VFIO passthrough 설정은 VM 시작 시 1회만 수행하므로 런타임 성능에는 영향이 없습니다. 빈번한 전환이 필요한 경우(예: SR-IOV VF 동적 할당), 커널 6.2+의 sysfs 인터페이스보다 iommufd 기반 API가 더 효율적입니다.

ARM SMMU

ARM System Memory Management Unit는 ARM 플랫폼의 IOMMU 구현입니다.

SMMU 아키텍처

ARM SMMU (System Memory Management Unit) ARM CPU Cluster CPU MMU (VA→PA) SMMU (arm-smmu-v3) Stream Table SID (Stream ID) → Context Descriptor 매핑 Context Descriptor (CD) • Translation Table Base (TTB0/TTB1) • PASID Support (SVA용) Command Queue Event Queue (오류 보고) ↓ DMA 변환 Device 0 SID = 0x0 Device 1 SID = 0x1 Device 2 SID = 0x2 각 디바이스의 DMA 주소는 SMMU를 통해 물리 주소로 변환/검증됨

SMMU 버전

버전 특징
SMMUv2 Stream ID (SID) 기반, Context Bank, Stage 1/2 translation
SMMUv3 향상된 확장성, Command Queue, Event Queue, PRI/ATS 지원
SMMUv3.1 Sub-streams, HTTU (Hardware Translation Table Update)

SMMU 드라이버

/* Device Tree에서 SMMU 정의 */
smmu: iommu@2b400000 {
    compatible = "arm,smmu-v3";
    reg = <0x0 0x2b400000 0x0 0x100000>;
    interrupts = <GIC_SPI 74 IRQ_TYPE_EDGE_RISING>,
                 <GIC_SPI 75 IRQ_TYPE_EDGE_RISING>,
                 <GIC_SPI 76 IRQ_TYPE_EDGE_RISING>;
    interrupt-names = "eventq", "priq", "gerror";
    #iommu-cells = <1>;
};

/* 디바이스가 SMMU 사용 */
pcie@40000000 {
    iommus = <&smmu 0>;  /* Stream ID = 0 */
};

SMMUv3 Command/Event Queue

SMMUv3는 소프트웨어와 하드웨어 간 통신을 위해 3개의 링 버퍼(Ring Buffer) 큐를 사용합니다. Command Queue(CMDQ)는 SW→HW 명령 전달, Event Queue(EVENTQ)는 HW→SW 오류 보고, PRI Queue(PRIQ)는 HW→SW 페이지 요청을 처리합니다.

SMMUv3 큐 아키텍처 Command Queue (CMDQ) SW → HW (명령 전달) PROD CONS TLBI_*, CFGI_STE, CFGI_CD, CMD_SYNC SW가 PROD 전진 → HW가 CONS까지 소비 Event Queue (EVENTQ) HW → SW (오류 보고) CONS PROD TRANSLATION_FAULT, PERMISSION, ADDR_SIZE HW가 PROD 전진 → SW가 CONS까지 처리 PRI Queue (PRIQ) HW → SW (페이지 요청) CONS PROD Page Request Group → iopf_handler() SVA/PRI 환경에서 demand paging SMMU MMIO 레지스터 CMDQ_BASE / EVTQ_BASE / PRIQ_BASE + PROD/CONS 레지스터 쌍 Wrap 비트 프로토콜 PROD/CONS 레지스터 최상위 비트 = wrap flag. 인덱스 동일 + wrap 다름 → 큐 가득 참 인덱스 동일 + wrap 같음 → 큐 비어 있음

arm_smmu_queue 구조체

/* drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h */
struct arm_smmu_queue {
    struct arm_smmu_ll_queue llq;   /* prod/cons + max_n_shift */
    dma_addr_t base_dma;             /* 큐 물리 주소 */
    void *base;                       /* 큐 가상 주소 */
    struct arm_smmu_cmdq_ent *entries; /* 엔트리 배열 */
};

struct arm_smmu_ll_queue {
    union {
        u64 val;
        struct {
            u32 prod;              /* Producer 인덱스 + wrap */
            u32 cons;              /* Consumer 인덱스 + wrap */
        };
    };
    u32 max_n_shift;                /* log2(큐 크기) */
};

/* 큐 사이즈: 2^max_n_shift 엔트리 */
/* CMDQ 엔트리: 16 바이트 (128-bit) */
/* EVENTQ/PRIQ 엔트리: 32 바이트 (256-bit) */

CMDQ 주요 Opcode

Opcode 설명 인자
CMDQ_OP_CFGI_STE 0x03 Stream Table Entry 캐시 무효화 SID
CMDQ_OP_CFGI_CD 0x05 Context Descriptor 캐시 무효화 SID, SSID
CMDQ_OP_CFGI_ALL 0x04 모든 설정 캐시 무효화 -
CMDQ_OP_TLBI_NH_ASID 0x11 ASID 기반 TLB 무효화 ASID
CMDQ_OP_TLBI_NH_VA 0x12 VA 기반 TLB 무효화 ASID, VA
CMDQ_OP_TLBI_S2_IPA 0x2a Stage 2 IPA TLB 무효화 VMID, IPA
CMDQ_OP_TLBI_NSNH_ALL 0x30 Non-secure Non-hypervisor 전체 무효화 -
CMDQ_OP_CMD_SYNC 0x46 이전 명령 완료 대기 (배리어) MSI addr (선택)

CMDQ 배치 제출

/* 배치 명령 제출 — 여러 명령을 모아서 한 번에 전송 */
struct arm_smmu_cmdq_batch cmds;
arm_smmu_cmdq_batch_init(smmu, &cmds, &cmd_ent);

/* TLB 무효화 명령 배치 추가 */
for (i = 0; i < nr_pages; i++) {
    cmd_ent.opcode = CMDQ_OP_TLBI_NH_VA;
    cmd_ent.tlbi.asid = asid;
    cmd_ent.tlbi.addr = iova + i * SZ_4K;
    arm_smmu_cmdq_batch_add(smmu, &cmds, &cmd_ent);
}

/* CMD_SYNC를 마지막에 추가 → HW 완료 보장 */
arm_smmu_cmdq_batch_submit(smmu, &cmds);

/* 내부적으로:
 * 1. CMDQ 공간 확보 (CONS 따라잡기 대기)
 * 2. 엔트리 기록 → PROD 전진 (writel)
 * 3. CMD_SYNC 대기 (polling 또는 MSI)
 */

EVENTQ Fault 처리

/* Event Queue 처리 스레드 */
static irqreturn_t arm_smmu_evtq_thread(int irq, void *dev)
{
    struct arm_smmu_device *smmu = dev;
    struct arm_smmu_queue *q = &smmu->evtq.q;
    u64 evt[EVTQ_ENT_DWORDS];   /* 256-bit 이벤트 */

    while (!queue_empty(q)) {
        queue_read(evt, q, q->llq.cons);
        arm_smmu_handle_evt(smmu, evt);
        queue_inc_cons(q);
    }
    return IRQ_HANDLED;
}

/* 이벤트 타입 파싱 */
/* evt[0] bits[7:0] = event ID */
#define EVT_ID_TRANSLATION_FAULT   0x10
#define EVT_ID_ADDR_SIZE_FAULT     0x11
#define EVT_ID_ACCESS_FAULT        0x12
#define EVT_ID_PERMISSION_FAULT    0x13
Event ID 이름 원인
0x10 Translation Fault 페이지 테이블 워크 실패 (매핑 없음)
0x11 Address Size Fault 입력 주소가 설정된 주소 폭 초과
0x12 Access Fault AF(Access Flag) 비트 미설정
0x13 Permission Fault 읽기/쓰기 권한 불일치
PRIQ와 IOPF 연결: PRIQ 이벤트는 arm_smmu_priq_thread()에서 읽어 iommu_report_device_fault()를 호출합니다. 이는 커널의 IOPF 프레임워크(io-pgfault.c)로 전달되어 handle_mm_fault()를 통해 페이지를 할당한 뒤, iommu_page_response()로 SMMU에 응답합니다. SVA/PRI가 활성화된 디바이스에서만 동작합니다.
CMDQ 고갈 주의: CMDQ가 가득 차면(PROD가 CONS를 wrap하여 따라잡음) SW는 HW가 명령을 소비할 때까지 spin 대기합니다. 대규모 TLB 무효화 시 CMDQ 크기가 작으면 성능 저하가 발생합니다. 기본 크기는 2^19(512K 엔트리)이며, DT/ACPI에서 큐 크기를 조정할 수 있습니다.

IOTLB 내부 구조와 무효화

IOTLB(I/O Translation Lookaside Buffer)는 IOMMU의 주소 변환 캐시입니다. CPU의 TLB와 동일한 원리로, 최근 사용된 IOVA→PA 변환 결과를 캐시하여 페이지 테이블 워크를 건너뜁니다. 매핑이 변경되면 반드시 무효화(invalidation)해야 하며, 이 비용이 IOMMU 성능의 핵심 요소입니다.

IOTLB 무효화 범위 Global Invalidation 모든 IOTLB 엔트리 무효화 가장 비용이 높음 IOMMU 초기화, 모드 변경 시 Domain Invalidation 특정 도메인의 모든 엔트리 중간 비용 Domain 해제, 디바이스 detach 시 Page Invalidation 특정 IOVA 범위만 무효화 가장 비용 낮음 (세밀) 개별 매핑 해제 시 Intel Queued Invalidation (QI) IOTLB inv Context inv IOTLB inv Wait Desc (비어 있음) (비어 있음) ↑ HEAD (HW) ↑ TAIL (SW) SW가 TAIL에 기술자(descriptor) 추가 → HW가 HEAD부터 순서대로 처리 Wait Descriptor: status_data 주소에 완료 마커 기록 → SW가 polling으로 완료 감지 Flush Queue (iova_fq) — Lazy Invalidation unmap 시 즉시 flush 대신 IOVA를 FQ에 추가 → 10ms 타이머 만료 시 배치 flush 트레이드오프: IOVA 재사용 지연 (use-after-free 위험 ↑) vs flush 오버헤드 감소 (성능 ↑)

iommu_iotlb_gather — 범위 누적 배치 flush

/* include/linux/iommu.h */
struct iommu_iotlb_gather {
    unsigned long start;      /* 무효화 시작 IOVA */
    unsigned long end;        /* 무효화 끝 IOVA */
    size_t pgsize;             /* 사용된 페이지 크기 */
    struct list_head freelist; /* 해제할 페이지 테이블 목록 */
    bool queued;               /* Flush Queue 사용 여부 */
};

/* unmap 시 gather에 범위 누적 */
iommu_iotlb_gather_add_range(&gather, iova, size);

/* 모든 unmap 후 한 번에 flush */
iommu_iotlb_sync(domain, &gather);
/* → domain->ops->iotlb_sync() 호출 */
/* → Intel: qi_flush_iotlb(), AMD: amd_iommu_flush_tlb_all() */

Intel Queued Invalidation (QI)

/* drivers/iommu/intel/qi.c */
struct qi_desc {
    u64 qw0;   /* Descriptor Type + 파라미터 */
    u64 qw1;   /* 추가 파라미터 */
    u64 qw2;   /* Wait: status_data */
    u64 qw3;   /* Wait: status_addr */
};

/* IOTLB 무효화 기술자 타입 */
#define QI_IOTLB_TYPE         0x2   /* IOTLB Invalidate */
#define QI_CC_TYPE            0x1   /* Context Cache Invalidate */
#define QI_IEC_TYPE           0x4   /* Interrupt Entry Cache */
#define QI_WAIT_TYPE          0x5   /* Wait Descriptor */
#define QI_PGRP_RESP_TYPE     0x7   /* Page Group Response */

/* 동기식 무효화 흐름 */
int qi_submit_sync(struct intel_iommu *iommu,
                    struct qi_desc *desc,
                    unsigned int count,
                    unsigned long options)
{
    /* 1. QI 큐 tail에 desc 기록 */
    /* 2. Wait Descriptor 추가 (status_data = QI_DONE) */
    /* 3. tail 레지스터 업데이트 → HW 시작 */
    /* 4. status_data가 QI_DONE이 될 때까지 spin/sleep */
}

AMD Command Buffer

/* AMD IOMMU Command Buffer — Intel QI와 유사한 링 버퍼 */
/* drivers/iommu/amd/iommu.c */

/* 주요 명령 타입 */
#define CMD_COMPL_WAIT              0x01  /* Completion Wait */
#define CMD_INV_DEV_ENTRY           0x02  /* Device Table Entry */
#define CMD_INV_IOMMU_PAGES         0x03  /* IOTLB Pages */
#define CMD_INV_IOTLB_PAGES         0x04  /* ATS Invalidation */
#define CMD_COMPLETE_PPR            0x07  /* PPR Response */
#define CMD_INV_IRT                 0x0a  /* Interrupt Remapping */

/* 페이지 무효화 명령 생성 */
static void build_inv_iommu_pages(struct iommu_cmd *cmd,
    u64 address, size_t size, u16 domid, bool pde)
{
    cmd->data[0] = CMD_INV_IOMMU_PAGES | (domid << 32);
    cmd->data[1] = address | (pde ? (1ULL << 1) : 0);
    /* pde=true: PDE 캐시도 함께 무효화 */
}

Flush Queue 동작

/* drivers/iommu/iova.c — Flush Queue (FQ) */
struct iova_fq {
    struct iova_fq_entry entries[IOVA_FQ_SIZE]; /* 256개 엔트리 */
    unsigned int head, tail;
    spinlock_t lock;
};

struct iova_fq_entry {
    unsigned long iova_pfn;
    unsigned long pages;
    u64 counter;              /* 무효화 시점 카운터 */
};

/* FQ 흐름: */
/* 1. unmap → IOVA를 per-CPU FQ에 추가 (flush 보류) */
/* 2. 10ms 타이머 만료 → fq_flush_timeout() */
/* 3. 보류된 범위 일괄 IOTLB flush */
/* 4. IOVA를 rcache에 반환 (재사용 가능) */
아키텍처 무효화 메커니즘 Page 무효화 레이턴시 동기 대기
Intel VT-d Queued Invalidation (QI) ~1-5 µs Wait Descriptor polling
AMD-Vi Command Buffer ~1-3 µs Completion Wait
ARM SMMUv3 Command Queue (CMDQ) ~2-10 µs CMD_SYNC + polling/MSI
Flush Queue vs Strict 모드: iommu.strict=1(기본값)에서는 매 unmap마다 즉시 IOTLB flush를 수행하여 보안성이 높지만, 고빈도 DMA 워크로드(10Gbps+ NIC, NVMe)에서 수십 % 성능 저하가 발생합니다. iommu.strict=0으로 Flush Queue를 활성화하면 배치 무효화로 오버헤드를 크게 줄일 수 있습니다. 단, unmap~flush 사이 최대 10ms 동안 stale 매핑이 남아 있어, 이론적으로 use-after-free DMA 공격 윈도우가 존재합니다.
ATS와 IOTLB 무효화: ATS가 활성화된 디바이스는 자체 ATC(Address Translation Cache)를 보유합니다. IOMMU의 IOTLB를 무효화할 때 ATC도 함께 무효화해야 합니다. Intel은 qi_flush_dev_iotlb(), ARM은 CMDQ_OP_ATC_INV 명령을 추가로 전송합니다.

크로스 아키텍처 PTE 포맷 비교

IOMMU의 페이지 테이블 엔트리(PTE)는 CPU의 PTE와 유사하지만, 벤더별로 비트 레이아웃이 상이합니다. 이 섹션에서는 Intel VT-d Second Level, AMD I/O PTE, ARM LPAE Stage 2의 비트 필드를 비교합니다.

IOMMU PTE 비트 레이아웃 비교 (64-bit) Intel VT-d Second Level PTE R [0] W [1] [2:6] SP [7] [8:10] SNP [11] Physical Address [12:51] 40-bit 물리 주소 (4KB 정렬) [52:61] 예약 TM [62] [63] AMD I/O Page Table Entry PR [0] NL [1:2] [3:4] FC [5] [6:11] Physical Address [12:51] 40-bit 물리 주소 (4KB 정렬) [52:58] 예약 IR [59] IW [60] [61:63] ARM LPAE Stage 2 PTE V [0] Ty [1] MemAttr [2:4] S2AP [6:7] SH [8:9] AF [10] Output Address [12:47] 36-bit 출력 주소 (4KB 정렬) Contig [52] XN [54] [55:63] 예약/SW 권한/유효 주소 크기/타입 캐시/속성 실행 금지 핵심 차이점 Intel: R/W가 비트 [0:1] (CPU PTE와 동일) → First Level에서 CPU PT 직접 공유 (SVA) AMD: 권한 비트가 상위 [59:60]에 위치 → CPU PTE와 포맷 불일치 ARM: S2AP[6:7]로 읽기/쓰기 인코딩, Contiguous 힌트로 TLB 효율 최적화

Intel VT-d PTE 상세

/* drivers/iommu/intel/iommu.h */
/* Second Level PTE — IOMMU 전용 주소 변환 */
#define DMA_PTE_READ         (1ULL << 0)   /* R: 읽기 허용 */
#define DMA_PTE_WRITE        (1ULL << 1)   /* W: 쓰기 허용 */
#define DMA_PTE_LARGE_PAGE   (1ULL << 7)   /* SP: Super Page (2MB/1GB) */
#define DMA_PTE_SNP          (1ULL << 11)  /* Snoop 비트 (캐시 일관성) */
#define DMA_PTE_TM           (1ULL << 62)  /* Transient Mapping */

/* First Level PTE — CPU x86 PTE와 동일 포맷 */
/* SVA에서 CPU 페이지 테이블을 IOMMU가 직접 워크 가능 */
#define FLPTE_PRESENT    (1ULL << 0)
#define FLPTE_WRITE      (1ULL << 1)
#define FLPTE_USER       (1ULL << 2)
#define FLPTE_ACCESSED   (1ULL << 5)
#define FLPTE_DIRTY      (1ULL << 6)
#define FLPTE_NX         (1ULL << 63)

AMD I/O PTE 상세

/* drivers/iommu/amd/amd_iommu_types.h */
#define IOMMU_PTE_PR    (1ULL << 0)   /* Present */
#define IOMMU_PTE_NL_0  (0ULL << 9)   /* Next Level = 0 → 물리 페이지 */
#define IOMMU_PTE_NL_1  (1ULL << 9)   /* Next Level = 1 → 다음 PT 레벨 */
#define IOMMU_PTE_FC    (1ULL << 1)   /* Force Coherency */
#define IOMMU_PTE_IR    (1ULL << 59)  /* I/O Read 허용 */
#define IOMMU_PTE_IW    (1ULL << 60)  /* I/O Write 허용 */

/* AMD 대형 페이지: NL(Next Level) 필드로 인코딩 */
/* NL=0 at Level 2 → 2MB 페이지 */
/* NL=0 at Level 3 → 1GB 페이지 */
/* Intel의 SP 비트와 달리 레벨별 NL=0으로 표현 */

ARM LPAE Stage 2 PTE 상세

/* arch/arm64/include/asm/pgtable-hwdef.h */
#define PTE_VALID        (1ULL << 0)
#define PTE_TYPE_PAGE    (3ULL << 0)   /* [1:0] = 11 → 4KB 페이지 */
#define PTE_TYPE_BLOCK   (1ULL << 0)   /* [1:0] = 01 → 블록 (2MB/1GB) */

/* Stage 2 Access Permissions (S2AP) */
#define PTE_S2AP_NONE    (0ULL << 6)   /* 접근 불가 */
#define PTE_S2AP_READ    (1ULL << 6)   /* 읽기만 */
#define PTE_S2AP_WRITE   (2ULL << 6)   /* 쓰기만 */
#define PTE_S2AP_RW      (3ULL << 6)   /* 읽기+쓰기 */

#define PTE_AF           (1ULL << 10)  /* Access Flag */
#define PTE_CONT         (1ULL << 52)  /* Contiguous (TLB 힌트) */
#define PTE_XN           (1ULL << 54)  /* Execute Never */

/* Contiguous 힌트: 연속 16개 PTE가 같은 속성이면
 * TLB가 하나의 엔트리로 캐시 가능 (16 × 4KB = 64KB)
 * 또는 16 × 2MB = 32MB 블록 */

권한 인코딩 비교

권한 Intel VT-d (2nd Level) AMD I/O PTE ARM LPAE Stage 2
읽기 R[0]=1 IR[59]=1 S2AP[6]=1
쓰기 W[1]=1 IW[60]=1 S2AP[7]=1
실행 금지 First Level만 (NX[63]) 해당 없음 XN[54]=1
대형 페이지 SP[7]=1 NL=0 (암시적) Type[1:0]=01 (Block)
Present R[0] | W[1] PR[0]=1 Valid[0]=1

페이지 크기 지원 비교

페이지 크기 Intel VT-d AMD-Vi ARM SMMU
4KB Level 1 PTE Level 1 PTE Level 3 PTE
2MB Level 2 SP Level 2 (NL=0) Level 2 Block
1GB Level 3 SP Level 3 (NL=0) Level 1 Block
64KB 미지원 미지원 Contiguous 16×4KB
주소 폭 (최대) 57-bit (5-level) 57-bit (v3) 48-bit (4-level)
SVA와 First Level PTE: Intel VT-d의 First Level PTE는 x86 CPU PTE와 비트 레이아웃이 동일합니다. 따라서 SVA(Shared Virtual Addressing) 환경에서 CPU의 CR3가 가리키는 페이지 테이블을 IOMMU가 직접 워크할 수 있습니다. AMD는 GCR3(Guest CR3) 테이블을 통해 유사 기능을 제공하지만, I/O PTE 포맷이 다르므로 별도의 변환 레이어가 필요합니다. ARM SMMUv3는 CPU LPAE 포맷과 Stage 1 PTE가 호환되어 SVA를 지원합니다.
SNP 비트 (Intel): Intel VT-d의 SNP[11](Snoop) 비트는 CPU 캐시 일관성(Cache Coherency)을 제어합니다. SNP=1이면 디바이스 DMA가 CPU 캐시를 자동으로 스누핑하여 일관성을 유지합니다. 대부분의 현대 시스템에서는 기본 활성화되지만, 성능에 민감한 RDMA 워크로드에서는 비활성화를 고려할 수 있습니다.

디버깅

sysfs 인터페이스

# IOMMU 정보 확인
# cat /sys/kernel/iommu_groups/0/type
intel-iommu

# ls /sys/kernel/iommu_groups/0/devices/
0000:00:00.0

# 디바이스별 IOMMU group
# find /sys/kernel/iommu_groups/ -type l | sort -V
/sys/kernel/iommu_groups/0/devices/0000:00:00.0
/sys/kernel/iommu_groups/1/devices/0000:00:02.0

커널 로그

# IOMMU 초기화 로그
# dmesg | grep -i iommu
DMAR: IOMMU enabled
DMAR-IR: Enabled IRQ remapping in x2apic mode
iommu: Default domain type: Translated

# IOMMU fault
DMAR: [DMA Read] Request device [01:00.0] fault addr 0xdeadbeef
DMAR: fault reason 0x06 (PTE Read access is not set)

Tracing

# IOMMU tracepoints
# cd /sys/kernel/debug/tracing
# echo 1 > events/iommu/enable

# 주요 tracepoint
iommu:map                # IOVA 매핑
iommu:unmap              # IOVA 언매핑
iommu:attach_device_to_domain
iommu:detach_device_from_domain

# cat trace
     swapper/0-1 [000] .... 1.234: iommu_map: IOMMU:0000:00:00.0 iova=0x100000 paddr=0x50000000 size=0x1000

성능 고려사항

IOTLB (I/O TLB) 최적화

/* IOTLB 무효화 지연 (Flush Queue) */
#define IOMMU_DOMAIN_DMA_FQ  4  /* Flush Queue 사용 */

/* Strict mode vs Flush Queue */
iommu.strict=0   # Flush Queue 사용 (성능 우선)
iommu.strict=1   # Strict mode (보안 우선, unmap 즉시 IOTLB flush)

Pass-through 모드

/* Pass-through: IOMMU 우회 (1:1 매핑) */
iommu=pt                 # 기본 디바이스는 pass-through

/* 성능 비교 */
/*   Pass-through: DMA latency 최소 */
/*   Translation: 보안 강화, VM passthrough 필요 */

IOMMU 모드 비교

모드 커널 파라미터 DMA 변환 보안 성능 용도
Off iommu=off 없음 (DMA = PA) 없음 최고 레거시, 신뢰 환경
Pass-through iommu=pt 1:1 매핑 낮음 높음 베어메탈 서버
Translated (strict) iommu.strict=1 IOVA 변환 최고 낮음 보안 중시, VM
Translated (lazy) iommu.strict=0 IOVA 변환 + FQ 높음 중간 일반 운영 (권장)
운영 권장: 대부분의 서버 환경에서는 iommu=pt를 기본으로 설정하여 IOMMU 오버헤드를 피하고, VFIO passthrough가 필요한 디바이스만 선택적으로 vfio-pci 드라이버에 바인딩하여 DMA remapping을 적용합니다. KVM 가상화를 사용할 때는 intel_iommu=on iommu=pt 조합이 일반적입니다.

SWIOTLB (소프트웨어 I/O TLB)

SWIOTLB(Software I/O Translation Lookaside Buffer)는 IOMMU가 없거나 디바이스의 DMA 주소 범위가 제한적일 때(예: 32-bit DMA만 지원하는 장치가 4GB 이상 메모리에 접근해야 할 때) 사용하는 바운스 버퍼 메커니즘입니다.

동작 원리

SWIOTLB 바운스 버퍼 동작 드라이버 버퍼 (PA > 4GB) memcpy Bounce Buffer (PA < 4GB, SWIOTLB 영역) DMA PCIe 디바이스 (32-bit DMA only) SWIOTLB는 DMA 요청마다 memcpy를 수행하므로 성능 저하가 발생합니다. IOMMU가 있으면 주소 변환으로 바운스 없이 직접 DMA 가능 → SWIOTLB 불필요
/* SWIOTLB 초기화 (부팅 시 저메모리 영역에 바운스 풀 할당) */
/* 기본 크기: 64MB */
/* 커널 파라미터로 조정: swiotlb=131072  (슬롯 수, 1 슬롯 = 2KB) */

/* DMA 매핑 시 SWIOTLB 사용 경로 */
dma_addr_t swiotlb_map(struct device *dev, phys_addr_t paddr,
                        size_t size, enum dma_data_direction dir)
{
    /* 1. bounce pool에서 슬롯 할당 */
    /* 2. DMA_TO_DEVICE이면 orig → bounce memcpy */
    /* 3. bounce 물리 주소 반환 (디바이스 DMA 범위 내) */
}

/* DMA 완료 후 sync */
void swiotlb_sync_single_for_cpu(struct device *dev,
    dma_addr_t addr, size_t size, enum dma_data_direction dir)
{
    /* DMA_FROM_DEVICE이면 bounce → orig memcpy */
}
주의: 가상 머신(KVM, Xen) 환경에서는 게스트 커널이 호스트의 실제 물리 메모리를 직접 알 수 없어 SWIOTLB가 자주 사용됩니다. 대용량 DMA 워크로드(NVMe, 고속 네트워크)에서 SWIOTLB 풀이 부족하면 swiotlb buffer is full 오류가 발생합니다. 커널 파라미터 swiotlb=262144로 풀 크기를 늘리거나, IOMMU를 활성화하여 바운스 버퍼를 우회하세요.

DMA Fault 처리

IOMMU가 디바이스의 DMA 요청을 변환하지 못하거나 권한 검증에 실패하면 DMA fault가 발생합니다. 이는 드라이버 버그, 잘못된 IOMMU 설정, 악의적 디바이스 접근 등에 의해 발생할 수 있습니다.

Fault 유형

Fault 유형 원인 대응
Translation Fault IOVA에 대한 매핑이 없음 DMA 매핑 누락 확인, iommu_map() 호출 검증
Permission Fault 읽기/쓰기 권한 불일치 DMA 방향(DMA_TO_DEVICE 등) 확인
External Abort 물리 주소가 유효하지 않음 해제된 메모리에 DMA 시도 여부 확인
ATS Fault 디바이스 ATC에 캐시된 변환이 무효화됨 ATS invalidation 타이밍 검증

Fault 핸들러

/* IOMMU fault 핸들러 등록 */
int iommu_set_fault_handler(struct iommu_domain *domain,
                             iommu_fault_handler_t handler,
                             void *token);

/* Fault 콜백 시그니처 */
typedef int (*iommu_fault_handler_t)(struct iommu_domain *domain,
    struct device *dev, unsigned long iova, int flags, void *token);

/* Intel VT-d fault 로그 예시 */
/* DMAR: [DMA Read no-pasid] Request device [01:00.0] */
/*   fault addr 0x3fff0000 [fault reason 0x06] */
/*   PTE Read access is not set */

/* 일반적인 디버깅 단계 */
/* 1. fault addr → 어떤 IOVA가 문제인지 확인 */
/* 2. Request device → 어떤 PCIe 디바이스가 요청했는지 */
/* 3. fault reason → 매핑 누락 / 권한 오류 / 페이지 크기 불일치 */
/* 4. 드라이버의 dma_map_single/dma_map_page 호출 검증 */
치명적 오류: IOMMU fault가 반복 발생하면 디바이스가 정지할 수 있습니다. 특히 NVMe SSD에서 DMA fault가 발생하면 I/O 타임아웃 → 파일시스템(Filesystem) 읽기 전용(Read-Only) 전환 → 데이터 유실로 이어질 수 있습니다. 운영 환경에서는 dmesg의 DMAR/AMD-Vi fault 로그를 모니터링하고, IOMMU 그룹 구성이 올바른지 정기적으로 검증하세요.

커널 설정

CONFIG_IOMMU_SUPPORT=y
CONFIG_IOMMU_API=y

# Intel VT-d
CONFIG_INTEL_IOMMU=y
CONFIG_INTEL_IOMMU_DEFAULT_ON=y
CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON=y
CONFIG_INTEL_IOMMU_SVM=y              # SVA 지원

# AMD-Vi
CONFIG_AMD_IOMMU=y
CONFIG_AMD_IOMMU_V2=y

# ARM SMMU
CONFIG_ARM_SMMU=y
CONFIG_ARM_SMMU_V3=y
CONFIG_ARM_SMMU_V3_SVA=y

# VFIO
CONFIG_VFIO=y
CONFIG_VFIO_PCI=y
CONFIG_VFIO_IOMMU_TYPE1=y

# 디버깅
CONFIG_IOMMU_DEBUG=y
CONFIG_IOMMU_DEBUGFS=y

참고자료

커널 공식 문서

커널 소스 코드

규격 문서

외부 자료

다음 학습: