IOMMU (Input-Output Memory Management Unit)

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

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

핵심 요약

  • IOMMU — 디바이스 DMA 주소를 물리 주소로 변환하고 접근 제어하는 하드웨어입니다.
  • 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) (디바이스 패스스루), 메모리 관리 (심화) (페이지 테이블)

개요

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 최적화 */

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

IOMMU Operations

iommu_ops 구조체

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);
};

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);

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 레지스터

/* 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 모드 (성능 우선)

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);

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와 mdev의 전체 API 흐름, mdev_parent_ops 구현, GPU passthrough 실전 설정, SR-IOV VF mdev 패턴은 VFIO & mdev (디바이스 패스스루) 페이지에서 자세히 다룹니다.

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 */
};

디버깅

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 필요 */

커널 설정

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

참고자료

다음 학습: