VFIO & mdev (디바이스 패스스루)

VFIO와 mdev를 고성능 장치 패스스루와 안전한 멀티테넌트 격리 관점에서 심층 분석합니다. IOMMU 기반 DMA 격리와 interrupt remapping, VFIO group/container/device 모델, userspace 드라이버와 QEMU 연동 ioctl 경로, SR-IOV와 full passthrough 비교, mdev를 통한 GPU/가속기 분할 제공 메커니즘, 페이지 핀 고정과 메모리 회수 트레이드오프, 리셋·오류 복구·라이브 마이그레이션 제약, 성능 계측과 보안 점검 절차까지 실제 가상화 플랫폼 운영에서 필요한 핵심을 다룹니다.

전제 조건: IOMMU, PCI/PCIe, 가상화 (KVM/QEMU) 문서를 먼저 읽으세요. VFIO는 IOMMU의 DMA 격리 기능을 활용하므로, IOMMU와 IOMMU 그룹 개념이 필수입니다.
일상 비유: VFIO는 자동차 공유 서비스와 비슷합니다. 물리 자동차(GPU)를 특정 사용자(VM)에게 완전히 빌려주는 것(VFIO passthrough)과, 기사가 여러 승객을 동시에 태우는 것(mdev — 하나의 GPU를 여러 VM에 분할)의 차이를 생각해보세요. IOMMU는 각 사용자가 다른 사람의 짐(메모리)에 손댈 수 없도록 막는 역할입니다.

핵심 요약

  • IOMMU 그룹 — 격리 가능한 최소 단위. 같은 그룹 내 모든 장치를 같은 VM에 할당해야 함
  • VFIO Container — VM이 사용할 IOMMU 도메인. DMA 매핑의 집합
  • vfio-pci 드라이버 — 패스스루 직전 물리 장치에 바인딩하는 VFIO 전용 드라이버
  • mdev (Mediated Device) — 1개의 물리 장치를 여러 가상 장치로 분할 (SR-IOV 없이도 가능)
  • GPU passthrough — VM이 GPU 네이티브 드라이버로 직접 제어 (CUDA/ROCm 가능)
  • DPDK / SPDK — 호스트에서 VFIO로 NIC/NVMe를 유저스페이스에서 직접 제어
  • ACS 요구사항 — GPU passthrough에 ACS(Access Control Services) 지원 필요
  • VFIO_NOIOMMU — IOMMU 없는 환경용 모드 (보안 없음, 개발/테스트용)

단계별 이해

  1. IOMMU 그룹 파악
    lspci로 패스스루 대상 장치의 IOMMU 그룹을 확인합니다.
  2. vfio-pci 드라이버 바인딩
    기존 드라이버를 언바인딩하고 vfio-pci를 바인딩하는 과정을 이해합니다.
  3. VFIO API 흐름 학습
    container → group → device 순서의 ioctl API를 파악합니다.
  4. QEMU GPU passthrough 설정
    -device vfio-pci 옵션으로 VM에 GPU를 할당하는 방법을 익힙니다.
  5. mdev 분할 이해
    물리 GPU를 4개의 가상 GPU로 나누는 mdev 드라이버 구현을 파악합니다.
  6. 보안 요구사항 확인
    ACS, IOMMU 그룹 격리, DMA 공격 방지 방법을 확인합니다.

VFIO 개요

VFIO는 커널 5.x 이전부터 존재했던 레거시 UIO(Userspace I/O)의 보안 취약점을 해결하기 위해 IOMMU와 통합하여 설계된 디바이스 패스스루 프레임워크입니다. VM의 버그나 악성 코드가 DMA를 통해 호스트 메모리에 접근하는 것을 IOMMU가 차단합니다.

방식격리성능보안사용 사례
에뮬레이션 (virtio)완전 격리낮음최고일반 가상화
SR-IOV VF하드웨어 격리높음높음네트워크/스토리지
VFIO passthroughIOMMU 격리거의 네이티브높음GPU, 전용 NVMe
mdev소프트웨어 격리중간중간GPU 분할, vGPU
VFIO NOIOMMU없음높음없음개발/테스트만

VFIO 아키텍처 계층 모델

VFIO 아키텍처 계층 모델 User Process (QEMU / DPDK / SPDK) open(/dev/vfio/vfio) → container_fd VFIO Container (IOMMU 도메인) SET_IOMMU → TYPE1_IOMMU · DMA 매핑 집합 관리 · IOVA 공간 정의 VFIO Group 14 open(/dev/vfio/14) → group_fd · SET_CONTAINER IOMMU Domain (Page Table) IOVA → HPA 변환 · IOTLB · DMA 격리 경계 iommu_domain_alloc() · iommu_attach_group() 01:00.0 GPU GET_DEVICE_FD → device_fd 01:00.1 Audio GET_DEVICE_FD → device_fd DEVICE_GET_INFO · DEVICE_GET_REGION_INFO DEVICE_SET_IRQS · DEVICE_RESET IOMMU_MAP_DMA · IOMMU_UNMAP_DMA IOMMU_DIRTY_PAGES (v2 전용) VFIO Group 22 open(/dev/vfio/22) → group_fd IOMMU Domain (Page Table) 독립 IOVA 공간 · Group 14와 완전 격리 별도 IOTLB 엔트리 · 독립 fault handler 05:00.0 NVMe GET_DEVICE_FD → device_fd 06:00.0 NIC DPDK 직접 접근 → device_fd DEVICE_GET_INFO · DEVICE_GET_REGION_INFO DEVICE_SET_IRQS · DEVICE_RESET IOMMU_MAP_DMA · IOMMU_UNMAP_DMA NOIOMMU 시 격리 없음 (개발용) Container 내 모든 Group이 동일 IOMMU 타입 · 동일 DMA 매핑 공유

UIO vs VFIO 비교: 레거시 UIO(drivers/uio/)는 장치를 유저스페이스에 노출하지만 DMA 격리 메커니즘이 없습니다. 악성 프로그램이 DMA를 통해 호스트 커널 메모리를 읽거나 쓸 수 있어 보안 위험이 큽니다. VFIO는 IOMMU를 필수로 요구하여 모든 DMA 접근을 IOVA 주소 공간 내로 제한합니다.

Container/Group/Device 포함 관계:

VFIO_NOIOMMU 모드: IOMMU 없는 환경에서 echo 1 > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode로 활성화합니다. 이 모드에서는 /dev/vfio/noiommu-<group_id> 장치가 생성되고, DMA 격리 없이 장치에 접근합니다. DPDK 개발/테스트 환경에서만 사용하며, 프로덕션에서는 절대 사용하지 않아야 합니다.

IOMMU 그룹과 격리 경계

IOMMU 그룹은 IOMMU가 격리할 수 있는 최소 단위입니다. 같은 PCIe 스위치 아래에 있는 장치들은 P2P DMA 경로를 공유하므로 같은 IOMMU 그룹에 묶입니다. 패스스루를 하려면 IOMMU 그룹 내 모든 장치를 같은 VM에 할당해야 합니다.

# IOMMU 그룹 확인 스크립트
for d in /sys/kernel/iommu_groups/*/devices/*; do
    n=${d#*/iommu_groups/}; n=${n%%/*}
    printf "IOMMU Group %s: " $n
    lspci -nns $(basename $d)
done

# 예시 출력:
# IOMMU Group 14: 01:00.0 VGA [0300]: NVIDIA RTX 4090 [10de:2684]
# IOMMU Group 14: 01:00.1 Audio [0403]: NVIDIA [10de:22ba]
# (GPU와 오디오가 같은 그룹 — 둘 다 패스스루해야 함)

# ACS 지원 확인 (PCIe 루트 포트 ACS 필요)
lspci -vvv | grep -i "Access Control"

# IOMMU 그룹 경계 시각화
find /sys/kernel/iommu_groups -type l | sort -V
IOMMU 그룹 분리 방법: GPU와 HDMI 오디오가 같은 그룹에 있으면 둘 다 패스스루해야 합니다. ACS(Access Control Services) 패치를 적용하면 강제로 그룹을 분리할 수 있지만, 보안 위험이 있습니다. 메인보드 설계상 루트 포트가 ACS를 지원하면 자연스럽게 장치당 별도 그룹이 형성됩니다.

ACS (Access Control Services) 메커니즘

IOMMU 그룹 토폴로지와 ACS 격리 경계 CPU / PCIe Root Complex IOMMU (VT-d / AMD-Vi) 내장 Root Port 1 (ACS 지원) SV + TB + RR + CR + UF + DT Root Port 2 (ACS 지원) SV + TB + RR + CR + UF + DT Root Port 3 (ACS 미지원) P2P DMA 가능 → 격리 불가 ACS 격리 경계 ACS 격리 경계 격리 경계 없음 IOMMU Group 14 01:00.0 GPU 01:00.1 Audio Multi-function → 같은 그룹 ACS 분리 → 단독 그룹 IOMMU Group 22 05:00.0 NVMe SSD 단일 Function 장치 ACS 분리 → 단독 그룹 IOMMU Group 30 (문제) 06:00.0 NIC 07:00.0 USB P2P DMA 가능 → 같은 그룹 NIC만 패스스루 불가! P2P DMA ACS (Access Control Services) 비트 플래그 SV (Source Validation) : 요청자 ID(BDF) 검증. 위조된 Bus:Device:Function 차단 TB (Translation Blocking) : ATS 변환된 요청 차단. 장치가 IOMMU를 우회하는 것 방지 RR (P2P Request Redirect) : P2P 요청을 Root Complex로 리다이렉트 → IOMMU 통과 강제 CR (P2P Completion Redirect) : P2P 완료 TLP를 Root Complex로 리다이렉트 UF (Upstream Forwarding) : 업스트림 포워딩 차단. 스위치 간 P2P 경로 제거 DT (Direct Translated P2P) : ATS 직접 변환 P2P 차단. 모든 변환이 IOMMU를 거침 모든 비트가 활성화되어야 완전한 ACS 격리 달성 → 각 장치가 독립 IOMMU 그룹

ACS는 PCIe 스위치나 루트 포트에서 P2P(Peer-to-Peer) DMA 경로를 차단하여 같은 스위치 아래에 있는 장치들이 서로의 메모리에 접근하지 못하게 합니다. ACS가 없으면 장치 A의 DMA 요청이 IOMMU를 거치지 않고 직접 장치 B의 메모리에 도달할 수 있으며, 이 경우 두 장치는 같은 IOMMU 그룹에 묶여 별도로 패스스루할 수 없습니다.

그룹 격리 실패 사례

실패 원인증상해결 방법
루트 포트 ACS 미지원여러 장치가 하나의 IOMMU 그룹에 묶임ACS override 패치 (보안 위험) 또는 메인보드 교체
PCIe 스위치 ACS 미지원스위치 하위 모든 장치가 같은 그룹스위치 펌웨어 업데이트 또는 물리 슬롯 변경
Multi-function 장치GPU + HDMI Audio가 같은 그룹둘 다 패스스루 (정상 동작)
PLX 스위치 사용PLX 칩셋 하위 장치 격리 불가ACS quirk 등록 또는 물리 분리
/* IOMMU 그룹 조회 및 장치 열거 — 커널 내부 */
#include <linux/iommu.h>

static int check_iommu_group(struct pci_dev *pdev)
{
    struct iommu_group *group;
    int group_id;

    /* 장치의 IOMMU 그룹 가져오기 */
    group = iommu_group_get(&pdev->dev);
    if (!group) {
        dev_err(&pdev->dev, "IOMMU 그룹 없음 — IOMMU 비활성?\n");
        return -ENODEV;
    }

    group_id = iommu_group_id(group);
    dev_info(&pdev->dev, "IOMMU Group %d\n", group_id);

    /* 그룹 내 모든 장치 열거 */
    iommu_group_for_each_dev(group, NULL, print_device_cb);

    /* ACS 상태 확인 */
    if (pci_acs_enabled(pdev, REQ_ACS_FLAGS))
        dev_info(&pdev->dev, "ACS 활성화 — 격리 보장\n");
    else
        dev_warn(&pdev->dev, "ACS 미지원 — P2P DMA 위험\n");

    iommu_group_put(group);
    return 0;
}

/* ACS 오버라이드 — 보안 위험, 테스트 전용 */
/* 커널 파라미터: pcie_acs_override=downstream,multifunction */
/* 이 옵션은 커뮤니티 패치로만 제공 (mainline 미포함) */

VFIO 그룹 API 흐름

아키텍처 계층 VM / QEMU / 유저스페이스 애플리케이션 Guest Physical Address (GPA) · VM 메모리 영역 VFIO Container (/dev/vfio/vfio) IOMMU 도메인 — DMA 매핑 집합 VFIO_TYPE1_IOMMU VFIO_TYPE1v2_IOMMU VFIO_ARM_SMMU_V3 VFIO_NOIOMMU (위험) VFIO Group 14 (/dev/vfio/14) 01:00.0 GPU NVIDIA RTX 4090 device_fd = ioctl(group_fd, GET_DEVICE_FD, "0000:01:00.0") 01:00.1 Audio NVIDIA HDMI Audio device_fd = ioctl(group_fd, GET_DEVICE_FD, "0000:01:00.1") VFIO Group 22 (/dev/vfio/22) 05:00.0 NVMe Samsung PM9A3 device_fd = ioctl(group_fd, GET_DEVICE_FD, "0000:05:00.0") IOMMU (Intel VT-d / AMD-Vi / ARM SMMU) IOVA → HPA 변환 · 장치별 DMA 격리 · IOTLB 캐시 관리 호스트 물리 메모리 (HPA) VFIO_IOMMU_MAP_DMA ioctl 시퀀스 ① open() /dev/vfio/vfio → container_fd ② open() /dev/vfio/14 → group_fd ③ SET_CONTAINER ioctl(group_fd, SET_CONTAINER) ④ SET_IOMMU ioctl(container_fd, SET_IOMMU, TYPE1) ⑤ GET_DEVICE_FD ioctl(group_fd, GET_DEVICE_FD) ⑥ MAP_DMA ioctl(container_fd, MAP_DMA, {vaddr,iova,size}) DMA 흐름: VM(GPA) → VFIO Container(IOVA 매핑) → IOMMU(HPA 변환) → 장치 DMA

VFIO ioctl 시퀀스

#include <linux/vfio.h>

int setup_vfio_device(const char *sysfs_path)
{
    int container_fd, group_fd, device_fd;

    /* 1. Container 열기 (IOMMU 도메인 생성) */
    container_fd = open("/dev/vfio/vfio", O_RDWR);

    /* 2. Group 열기 (IOMMU 그룹 번호는 sysfs에서 확인) */
    /*    /sys/bus/pci/devices/0000:01:00.0/iommu_group -> ../../kernel/iommu_groups/14 */
    group_fd = open("/dev/vfio/14", O_RDWR);

    /* 3. Group을 Container에 추가 */
    ioctl(group_fd, VFIO_GROUP_SET_CONTAINER, &container_fd);

    /* 4. IOMMU 타입 설정 (Type1 = Intel VT-d / AMD-Vi) */
    ioctl(container_fd, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);

    /* 5. 장치 FD 가져오기 */
    device_fd = ioctl(group_fd, VFIO_GROUP_GET_DEVICE_FD, "0000:01:00.0");

    /* 6. DMA 매핑 — 호스트 가상 메모리를 IOVA(장치 주소)에 매핑 */
    struct vfio_iommu_type1_dma_map dma_map = {
        .argsz = sizeof(dma_map),
        .flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE,
        .vaddr = (uint64_t)host_vaddr,   /* 호스트 가상 주소 */
        .iova  = 0x1000000,             /* 장치에서 보이는 주소 */
        .size  = dma_size,
    };
    ioctl(container_fd, VFIO_IOMMU_MAP_DMA, &dma_map);

    return device_fd;
}

보안 모델과 DMA 격리

VFIO의 핵심 보안 특성은 IOMMU를 통한 DMA 격리입니다. 디바이스가 DMA를 시도할 때 IOMMU가 IOVA를 물리 주소로 변환하는 과정에서 허가되지 않은 메모리 영역 접근을 차단합니다.

보안 요구사항설명확인 방법
IOMMU 활성화intel_iommu=on 또는 amd_iommu=ondmesg | grep -i iommu
ACS 지원PCIe 루트 포트 ACS로 그룹 분리lspci -vvv | grep ACS
그룹 완전 할당같은 그룹 내 모든 장치 패스스루iommu_group/devices 확인
Reset 지원VM 종료 후 장치 리셋 (FLR/Bus Reset)lspci -vvv | grep FLR
PASID 지원SR-IOV 없이 프로세스별 격리CONFIG_PCI_PASID

DMA 공격 방지

# IOMMU가 올바르게 활성화됐는지 확인
dmesg | grep -E "IOMMU|DMAR|IOMMU enabled"
# [ 0.082] DMAR: IOMMU enabled
# [ 0.156] DMAR: Intel(R) Virtualization Technology for Directed I/O

# IOMMU 그룹별 장치 격리 확인
for d in /sys/bus/pci/devices/*; do
    echo "$(basename $d): $(readlink $d/iommu_group | sed 's/.*iommu_groups\///')"
done

# Kernel lockdown 모드에서 VFIO_NOIOMMU 금지 확인
cat /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
# 0 (비활성) — 프로덕션 환경에서 반드시 0이어야 함

DMA Remapping 심화

DMA Remapping 흐름 (IOVA → IOTLB → Page Table → HPA) Device DMA IOVA: 0x1000000 BDF: 01:00.0 IOMMU (Intel VT-d / AMD-Vi) Context Table BDF → Domain ID 매핑 Root Entry → Context Entry IOTLB (캐시) IOVA → HPA 캐시 엔트리 Hit: 즉시 변환 / Miss: Page Walk IOTLB Hit! Miss ↓ Multi-Level Page Table Walk L4 (PML4) IOVA[47:39] L3 (PDPE) IOVA[38:30] L2 (PDE) IOVA[29:21] L1 (PTE) IOVA[20:12] 변환 결과 HPA: 0x7F200000 권한: Read + Write 확인 IOMMU Fault 권한 위반 또는 매핑 부재 DMA_FAULT → 장치 격리/리셋 물리 메모리 HPA: 0x7F200000 (VM에 할당된 영역) pinned pages HPA 접근 IOTLB 무효화 Page-selective: 개별 페이지 Domain-selective: 전체 도메인 Global: 모든 도메인 (느림)

IOVA(I/O Virtual Address) 할당: VFIO Type1 백엔드는 사용자가 VFIO_IOMMU_MAP_DMA로 지정한 IOVA 주소를 IOMMU 페이지 테이블에 등록합니다. QEMU는 VM의 게스트 물리 주소(GPA)를 IOVA로 직접 사용하여 1:1 매핑을 구성하며, 이로써 게스트 디바이스 드라이버가 사용하는 DMA 주소가 IOMMU를 통해 올바른 호스트 물리 메모리(HPA)에 도달합니다.

IOTLB 무효화 전략: DMA 매핑이 변경되면 IOMMU의 IOTLB(I/O Translation Lookaside Buffer)를 무효화해야 합니다. 무효화 범위가 클수록 오버헤드가 커집니다.

무효화 유형범위지연시간사용 시점
Page-selective개별 IOVA 페이지~1-5us소규모 매핑 변경
Domain-selective전체 IOMMU 도메인~10-50usVM 종료, 대규모 unmap
Global모든 도메인~100us+IOMMU 설정 변경 (드물게 사용)

Nested Translation (2단계 변환): Intel VT-d의 Scalable Mode와 AMD-Vi의 Guest Translation에서 지원하는 기능으로, 게스트 IOMMU와 호스트 IOMMU가 동시에 동작합니다. 1단계(Stage-1)는 게스트 OS가 관리하는 GVA→GPA 변환이고, 2단계(Stage-2)는 호스트가 관리하는 GPA→HPA 변환입니다. 이를 통해 게스트 내에서 VFIO 중첩 가상화(nested VFIO)를 구현할 수 있습니다.

PASID (Process Address Space ID): PCIe PASID 확장을 사용하면 단일 장치 내에서 여러 프로세스의 주소 공간을 독립적으로 매핑할 수 있습니다. SR-IOV 없이도 프로세스별 격리를 제공하며, SVA(Shared Virtual Addressing)와 결합하여 CPU와 장치가 동일한 가상 주소를 사용합니다.

기능Intel VT-dAMD-Vi (AMD IOMMU)
IOTLB 구조DMAR 유닛당 IOTLBIOMMU당 IOTLB + Device Table Entry 캐시
Nested TranslationScalable Mode (SM) 2-levelGuest CR3 + Nested Page Table
PASID 지원PASID Table (Scalable Mode)PASID + PPR(Page Request/Response)
Interrupt RemappingIR Table (IRT)GA Log (GALOG)
Page Walk 레벨4-level (48bit) / 5-level (57bit)4-level (48bit) / 5-level (57bit)
Fault ReportingDMAR Fault RecordEvent Log / PPR Log
DMA 최대 주소MGAW (최대 57bit)VA Size (최대 57bit)
스케일러빌리티Root/Context Table → PASID TableDevice Table → PASID 지원
/* IOTLB 무효화 코드 경로 — drivers/iommu/intel/iommu.c */

/* Page-selective IOTLB 무효화 */
static void qi_flush_iotlb(struct intel_iommu *iommu,
                            u16 did, u64 addr,
                            unsigned int size_order, u64 type)
{
    struct qi_desc desc;

    desc.qw0 = QI_IOTLB_DID(did) | QI_IOTLB_GRAN(type)
             | QI_IOTLB_TYPE;
    desc.qw1 = QI_IOTLB_ADDR(addr) | QI_IOTLB_IH(ih)
             | QI_IOTLB_AM(size_order);

    /* Queued Invalidation — 비동기 무효화 큐 제출 */
    qi_submit_sync(iommu, &desc, 1, 0);
}

/* VFIO unmap 시 호출되는 IOTLB 무효화 */
/* vfio_iommu_type1.c → vfio_unmap_unpin() → iommu_unmap() */
/*   → intel_iommu_iotlb_sync_map() → qi_flush_iotlb() */

/* IOVA 할당자 — kernel/dma/iova.c */
struct iova *alloc_iova(struct iova_domain *iovad,
                        unsigned long size,
                        unsigned long limit_pfn,
                        bool size_aligned)
{
    /* Red-black tree 기반 IOVA 범위 할당 */
    /* VFIO는 사용자가 IOVA를 직접 지정하므로 */
    /* 이 함수 대신 rb_tree에 직접 삽입 */
    ...
}

VFIO_TYPE1_IOMMU 구현

VFIO_TYPE1_IOMMU는 x86 Intel VT-d 및 AMD-Vi IOMMU를 지원하는 가장 일반적인 VFIO 백엔드입니다. VFIO_TYPE1v2_IOMMU는 여기에 IOTLB 무효화 최적화와 pinned pages 지원을 추가합니다.

/* drivers/vfio/vfio_iommu_type1.c 핵심 자료구조 */

struct vfio_iommu {
    struct list_head    domain_list;  /* IOMMU 도메인 목록 */
    struct list_head    iova_list;    /* 유효한 IOVA 범위 */
    struct rb_root      dma_list;     /* DMA 매핑 트리 */
    bool                v2;           /* TYPE1v2 여부 */
};

struct vfio_dma {
    struct rb_node  node;
    dma_addr_t      iova;       /* 장치 주소 */
    unsigned long   vaddr;      /* 호스트 가상 주소 */
    size_t          size;
    int             prot;       /* IOMMU_READ | IOMMU_WRITE */
    bool            iommu_mapped;
};

/* VFIO_IOMMU_MAP_DMA ioctl 처리 */
static int vfio_dma_do_map(struct vfio_iommu *iommu,
                           struct vfio_iommu_type1_dma_map *map)
{
    /* 호스트 페이지 핀 (물리 주소 고정) */
    vfio_pin_pages_remote(dma, vaddr, npage, &pfn_base);

    /* IOMMU 도메인에 IOVA → 물리 주소 매핑 */
    iommu_map(domain->domain, iova, pfn_to_phys(pfn), size, prot);
    return 0;
}

mdev (Mediated Device) 프레임워크

mdev는 하나의 물리 장치를 여러 가상 장치(mdev 인스턴스)로 분할하는 소프트웨어 프레임워크입니다. SR-IOV가 있는 하드웨어에서는 VF를 mdev에 노출하고, 없는 경우(구형 GPU)에는 소프트웨어로 에뮬레이션합니다.

mdev 드라이버 구현

#include <linux/mdev.h>

/* mdev 부모 장치 오퍼레이션 — 물리 GPU 드라이버가 구현 */
static const struct mdev_parent_ops my_gpu_mdev_ops = {
    .owner           = THIS_MODULE,
    .device_driver   = &my_gpu_mdev_driver,

    /* mdev 인스턴스 생성/삭제 */
    .create          = my_gpu_mdev_create,
    .remove          = my_gpu_mdev_remove,

    /* 지원하는 mdev 유형 목록 */
    .supported_type_groups = my_gpu_mdev_types,

    /* sysfs 인터페이스 */
    .mdev_attr_groups = my_gpu_mdev_dev_attrs,
};

/* mdev 유형 정의 (예: 1/4, 1/2 GPU) */
static struct attribute *my_gpu_1q_attrs[] = {
    &dev_attr_available_instances.attr,   /* 최대 4개 */
    &dev_attr_device_api.attr,            /* "vfio-pci" */
    &dev_attr_name.attr,                  /* "GPU-1/4" */
    NULL,
};

/* 인스턴스 생성 — VM당 1개의 mdev 인스턴스 */
static int my_gpu_mdev_create(struct mdev_device *mdev)
{
    struct my_vgpu *vgpu;

    vgpu = kzalloc(sizeof(*vgpu), GFP_KERNEL);
    my_gpu_partition_hw(vgpu);   /* GPU 자원(VRAM, SM) 분할 */
    mdev_set_drvdata(mdev, vgpu);
    return 0;
}

mdev sysfs 관리

# mdev 유형 확인 (GPU가 지원하는 분할 방식)
ls /sys/bus/pci/devices/0000:01:00.0/mdev_supported_types/
# nvidia-35 — Tesla M10-8Q (8GB per VM, max 2 instances)
# nvidia-36 — Tesla M10-4Q (4GB per VM, max 4 instances)

# mdev 인스턴스 생성
UUID=$(uuidgen)
echo $UUID > /sys/bus/pci/devices/0000:01:00.0/mdev_supported_types/nvidia-36/create

# 생성된 mdev 확인
ls /sys/bus/mdev/devices/  # $UUID 디렉토리
mdevctl list               # 활성 mdev 목록

# QEMU에 mdev 추가
## -device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$UUID

KVM 통합과 QEMU 설정

QEMU는 VFIO를 통해 물리 PCI 장치를 VM에 직접 노출합니다. KVM 하이퍼바이저와 결합하면 CPU 에뮬레이션 오버헤드 없이 하드웨어를 직접 제어할 수 있습니다.

# vfio-pci 드라이버 바인딩 (기존 드라이버 언바인딩)
# 방법 1: modprobe 시 바인딩
modprobe vfio-pci ids=10de:2684,10de:22ba

# 방법 2: 수동 바인딩 스크립트
DEVICE=0000:01:00.0
VENDOR=$(cat /sys/bus/pci/devices/$DEVICE/vendor)
DEVID=$(cat /sys/bus/pci/devices/$DEVICE/device)

# 기존 드라이버 언바인딩
echo $DEVICE > /sys/bus/pci/devices/$DEVICE/driver/unbind

# vfio-pci 드라이버 바인딩
echo $VENDOR $DEVID > /sys/bus/pci/drivers/vfio-pci/new_id

# QEMU VM 실행 — GPU passthrough
qemu-system-x86_64 \
  -enable-kvm \
  -cpu host \
  -m 32G \
  -smp 16 \
  -device vfio-pci,host=01:00.0,multifunction=on,x-vga=on \
  -device vfio-pci,host=01:00.1 \
  -drive file=windows.qcow2,if=virtio \
  -vga none \
  -nographic

KVM 메모리 매핑

/* KVM은 VM의 게스트 물리 주소(GPA)를 VFIO Container에 등록 */
/* QEMU의 memory_listener가 GPA → HPA 매핑을 VFIO DMA 맵으로 변환 */

/* QEMU vfio_listener_region_add() 내부 흐름: */
/* 1. KVM_SET_USER_MEMORY_REGION으로 KVM EPT/NPT 설정 */
/* 2. VFIO_IOMMU_MAP_DMA로 IOMMU 매핑 추가 */
/* → GPU DMA가 게스트 물리 주소를 사용해 VM 메모리 직접 접근 가능 */

VFIO 라이브 마이그레이션

VFIO Migration 상태 머신 (v2 Protocol) RUNNING 장치 정상 동작 DMA 활성 · IRQ 활성 dirty page tracking 가능 RUNNING + PRE_COPY 장치 계속 동작하면서 상태 데이터 점진적 전송 PRE_COPY 시작 STOP_COPY 장치 중지 · 상태 추출 DMA 중단 · 최종 상태 전송 migration_fd에서 read() STOP 전이 직접 전이 (PRE_COPY 생략) STOP 장치 완전 정지 전송 완료 RESUMING 대상 호스트에서 복원 상태 데이터 주입 migration_fd에 write() 대상 호스트 RUNNING (대상 호스트에서 재개) 복원 완료 ERROR 마이그레이션 실패 → 폴백 마이그레이션 프로토콜 1. VFIO_DEVICE_FEATURE (MIG_SET_STATE) 2. migration_fd = read/write 스트림 3. dirty pages: VFIO_IOMMU_DIRTY_PAGES 4. vfio-user: UNIX socket 기반 원격

VFIO v2 마이그레이션 프로토콜은 커널 6.2에서 도입되었으며, 장치 상태를 file descriptor 기반의 스트리밍 인터페이스로 전송합니다. 이전 v1 프로토콜은 구조체 기반이었으나 유연성 부족으로 폐기되었습니다.

Pre-copy / Stop-copy 프로토콜:

/* VFIO v2 마이그레이션 상태 전이 — VFIO_DEVICE_FEATURE ioctl */
#include <linux/vfio.h>

int vfio_migrate_start_precopy(int device_fd)
{
    struct vfio_device_feature *feature;
    struct vfio_device_feature_mig_state *mig;
    size_t alloc_size;
    int ret;

    alloc_size = sizeof(*feature) + sizeof(*mig);
    feature = calloc(1, alloc_size);
    feature->argsz = alloc_size;
    feature->flags = VFIO_DEVICE_FEATURE_SET
                   | VFIO_DEVICE_FEATURE_MIG_DEVICE_STATE;

    mig = (struct vfio_device_feature_mig_state *)feature->data;

    /* 1단계: PRE_COPY 상태로 전이 (장치 계속 동작) */
    mig->device_state = VFIO_DEVICE_STATE_PRE_COPY;
    ret = ioctl(device_fd, VFIO_DEVICE_FEATURE, feature);
    if (ret < 0) return ret;

    /* migration_fd를 통해 상태 스트리밍 읽기 */
    int migration_fd = mig->data_fd;
    /* read(migration_fd, buf, size) 반복 → 대상 호스트 전송 */

    /* 2단계: STOP_COPY 상태로 전이 (장치 정지) */
    mig->device_state = VFIO_DEVICE_STATE_STOP_COPY;
    ret = ioctl(device_fd, VFIO_DEVICE_FEATURE, feature);
    /* 마지막 변경분 read(migration_fd, ...) */

    close(migration_fd);
    free(feature);
    return 0;
}
/* 대상 호스트에서 장치 상태 복원 */
int vfio_migrate_resume(int device_fd, const void *state_data,
                        size_t state_size)
{
    struct vfio_device_feature *feature;
    struct vfio_device_feature_mig_state *mig;

    /* ... feature 할당 (위와 동일) ... */

    /* 1단계: RESUMING 상태로 전이 */
    mig->device_state = VFIO_DEVICE_STATE_RESUMING;
    ioctl(device_fd, VFIO_DEVICE_FEATURE, feature);

    int migration_fd = mig->data_fd;

    /* 상태 데이터를 migration_fd에 기록 */
    write(migration_fd, state_data, state_size);
    close(migration_fd);

    /* 2단계: RUNNING 상태로 전이 (장치 재개) */
    mig->device_state = VFIO_DEVICE_STATE_RUNNING;
    ioctl(device_fd, VFIO_DEVICE_FEATURE, feature);

    free(feature);
    return 0;
}

/* Dirty page tracking */
struct vfio_iommu_type1_dirty_bitmap dirty = {
    .argsz = sizeof(dirty),
    .flags = VFIO_IOMMU_DIRTY_PAGES_FLAG_GET_BITMAP,
};
struct vfio_iommu_type1_dirty_bitmap_get range = {
    .iova   = 0x1000000,
    .size   = dma_size,
    .bitmap = {
        .pgsize = 4096,
        .size   = bitmap_size,
        .data   = bitmap_ptr,
    },
};
/* ioctl(container_fd, VFIO_IOMMU_DIRTY_PAGES, &dirty) */
/* bitmap_ptr에 dirty 페이지 비트맵 반환 */

vfio-user 프로토콜: vfio-user는 VFIO 장치를 UNIX 도메인 소켓을 통해 원격 프로세스에 노출하는 프로토콜입니다. QEMU와 장치 에뮬레이터가 별도 프로세스에서 동작할 수 있어 보안 격리와 장애 격리를 강화합니다. SPDK의 NVMe 에뮬레이터가 이 방식을 사용합니다.

마이그레이션 제약: GPU 패스스루 마이그레이션은 벤더 드라이버 지원이 필수입니다. NVIDIA vGPU는 자체 마이그레이션 프로토콜을 사용하며, 오픈소스 드라이버(nouveau/amdgpu)는 아직 VFIO 마이그레이션을 완전히 지원하지 않습니다. mlx5 (Mellanox ConnectX) NIC가 VFIO v2 마이그레이션의 첫 번째 완전 구현체입니다.

GPU Passthrough 실용 설정

시스템 요구사항

# CPU IOMMU 지원 확인
grep -E "vmx|svm" /proc/cpuinfo | head -1
# vmx — Intel VT-x (VT-d도 함께 필요)
# svm — AMD-V (AMD-Vi도 함께 필요)

# 커널 부트 파라미터 설정 (/etc/default/grub)
# Intel: GRUB_CMDLINE_LINUX="intel_iommu=on iommu=pt"
# AMD:   GRUB_CMDLINE_LINUX="amd_iommu=on iommu=pt"
# iommu=pt: Passthrough 모드 — 비-패스스루 장치 성능 보호

update-grub && reboot

# 재부팅 후 IOMMU 활성화 확인
dmesg | grep -E "IOMMU|DMAR" | head -5

AMD GPU passthrough (ROCm)

# AMD GPU passthrough — reset 버그 없음, 오픈소스 드라이버
# 1. vfio-pci 바인딩
echo "0000:03:00.0" > /sys/bus/pci/devices/0000:03:00.0/driver/unbind
echo "1002 744c" > /sys/bus/pci/drivers/vfio-pci/new_id  # RX 7900 XTX

# 2. QEMU AMD GPU passthrough (ROCm 가능)
qemu-system-x86_64 \
  -enable-kvm \
  -cpu host \
  -device vfio-pci,host=03:00.0,multifunction=on \
  -device vfio-pci,host=03:00.1 \
  ...

# 3. VM 내 ROCm 설치 후 GPU 확인
rocm-smi  # 패스스루된 GPU 직접 접근
rocminfo  # 컴퓨팅 큐 확인

NVIDIA GPU passthrough 고급 설정

NVIDIA GPU는 패스스루 시 몇 가지 추가 작업이 필요합니다. 특히 vBIOS 추출/패칭, Code 43 에러 회피, ACS override가 빈번하게 요구됩니다.

# NVIDIA vBIOS 추출 (GPU ROM 덤프)
# 일부 GPU는 UEFI vBIOS를 QEMU에 전달해야 정상 부팅
echo 1 > /sys/bus/pci/devices/0000:01:00.0/rom
cat /sys/bus/pci/devices/0000:01:00.0/rom > gpu_vbios.rom
echo 0 > /sys/bus/pci/devices/0000:01:00.0/rom

# QEMU에 vBIOS 전달
# -device vfio-pci,host=01:00.0,romfile=gpu_vbios.rom

# NVIDIA Code 43 우회 (Windows VM에서 드라이버 거부 방지)
# QEMU에서 하이퍼바이저 정보 숨기기
# -cpu host,kvm=off,hv_vendor_id=randomid
# 또는 libvirt XML에서:
# <hyperv><vendor_id state='on' value='randomid'/></hyperv>
# <kvm><hidden state='on'/></kvm>

# ACS override 패치 적용 확인 (보안 위험)
# 커널 파라미터에 pcie_acs_override=downstream,multifunction 추가
dmesg | grep -i "acs override"
# 패치가 적용되면 각 장치가 독립 IOMMU 그룹으로 분리됨

# GPU FLR(Function Level Reset) 지원 확인
lspci -vvv -s 01:00.0 | grep -E "FLR|Reset"
# Capabilities: [xxx] ... FLReset+
# AMD GPU는 FLR 지원이 우수 (리셋 버그 거의 없음)
# NVIDIA GPU는 일부 모델에서 FLR 후 행(hang) 발생 가능
AMD vs NVIDIA FLR 특성:
  • AMD GPU: FLR이 안정적으로 동작합니다. VM을 종료하고 재시작할 때 장치 리셋이 깔끔하게 처리됩니다. 오픈소스 amdgpu 드라이버와 ROCm 스택을 VM 안에서 직접 사용할 수 있습니다.
  • NVIDIA GPU: 일부 소비자용 GPU(GeForce)에서 FLR 후 복구 실패가 발생할 수 있습니다. 이 경우 Secondary Bus Reset(SBR)을 사용하거나, 호스트 재부팅이 필요할 수 있습니다. 데이터센터 GPU(A100, H100)는 FLR이 안정적입니다.
# GPU 패스스루 종합 문제해결 스크립트
#!/bin/bash

echo "=== IOMMU 상태 ==="
dmesg | grep -E "DMAR|IOMMU" | head -5

echo "=== VFIO 모듈 ==="
lsmod | grep vfio

echo "=== GPU IOMMU 그룹 ==="
GPU_BDF="0000:01:00.0"
IOMMU_GRP=$(readlink /sys/bus/pci/devices/$GPU_BDF/iommu_group | sed 's/.*iommu_groups\///')
echo "GPU $GPU_BDF → IOMMU Group $IOMMU_GRP"
ls /sys/kernel/iommu_groups/$IOMMU_GRP/devices/

echo "=== 드라이버 바인딩 상태 ==="
for dev in /sys/kernel/iommu_groups/$IOMMU_GRP/devices/*; do
    BDF=$(basename $dev)
    DRV=$(readlink /sys/bus/pci/devices/$BDF/driver 2>/dev/null | xargs basename 2>/dev/null)
    echo "  $BDF: driver=${DRV:-none}"
done

echo "=== FLR / Reset 지원 ==="
lspci -vvv -s ${GPU_BDF} | grep -E "FLR|Reset"

echo "=== VFIO dmesg 로그 ==="
dmesg | grep -i vfio | tail -10

VFIO-PCI 드라이버 내부 구조

VFIO-PCI 드라이버 내부 흐름 open() vfio_pci_core_open() refcount++, power on ioctl() 디스패처 vfio_pci_core_ioctl() cmd 분기 → 핸들러 호출 DEVICE_GET_INFO num_regions, num_irqs flags: RESET, MIGRATION DEVICE_GET_REGION_INFO BAR0~5, ROM, Config, VGA size, offset, flags(R/W/MMAP) DEVICE_SET_IRQS INTX, MSI, MSI-X, ERR, REQ eventfd 기반 인터럽트 전달 DEVICE_RESET FLR → Bus Reset → PM Reset 우선순위 순 시도 BAR 매핑 (mmap) MMIO BAR io_remap_pfn_range() I/O Port BAR read/write (mmap 불가) Expansion ROM vBIOS (Read-only) MMIO BAR → 유저스페이스에서 직접 GPU 레지스터 접근 VGA region → VFIO_PCI_VGA_REGION (프레임버퍼) vm_pgoff으로 region/offset 인코딩 PCI Config Space 가상화 직접 전달 Vendor/Device ID 가상화/필터 Command, BAR 주소 차단/에뮬레이션 PM, AER Capability vfio_pci_config_rw() → 레지스터별 권한 테이블 virt_cfg: 가상화된 값 저장 (쓰기 가로챔) Capability 구조 보존하되 위험한 쓰기 차단 close() vfio_pci_core_close() IRQ 해제 → BAR unmap → FLR → power down 흐름: open() → ioctl(GET_INFO, GET_REGION, SET_IRQS) → mmap(BAR) → 장치 사용 → ioctl(RESET) → close()

vfio-pci 모듈 구조: drivers/vfio/pci/ 디렉토리에 위치하며, vfio_pci_core가 공통 로직을, vfio_pci가 실제 PCI 드라이버 등록을 담당합니다. 벤더별 VFIO 변형(mlx5_vfio_pci, hisi_acc_vfio_pci 등)은 vfio_pci_core를 기반으로 마이그레이션이나 특수 기능을 추가합니다.

/* drivers/vfio/pci/vfio_pci_core.c — 핵심 구조체 */

struct vfio_pci_core_device {
    struct vfio_device    vdev;          /* VFIO 장치 기본 */
    struct pci_dev       *pdev;          /* PCI 장치 */
    struct mutex         igate;          /* 인터럽트 게이트 */

    /* BAR 영역 정보 */
    struct vfio_pci_region *region;       /* BAR0-5, ROM, Config */
    u32                  num_regions;

    /* 인터럽트 상태 */
    struct vfio_pci_irq_ctx *ctx;         /* eventfd 컨텍스트 */
    int                  num_ctx;
    int                  irq_type;       /* INTX/MSI/MSI-X */

    /* Config space 가상화 */
    u8                   *vconfig;       /* 가상화된 config 값 */
    u8                   *pci_config_map; /* 레지스터별 권한 */

    /* 리셋 관련 */
    bool                 has_flr;
    bool                 has_pm_reset;
    bool                 needs_reset;
};

/* BAR mmap 구현 */
static int vfio_pci_mmap(struct vfio_device *core_vdev,
                         struct vm_area_struct *vma)
{
    /* vm_pgoff에서 region index와 offset 디코딩 */
    unsigned int index = vma->vm_pgoff >> (VFIO_PCI_OFFSET_SHIFT - PAGE_SHIFT);

    /* MMIO BAR만 mmap 허용 (I/O port BAR 불가) */
    if (!(vdev->bar_mmap_supported & (1 << index)))
        return -EINVAL;

    /* 물리 BAR 주소를 유저스페이스에 매핑 */
    return io_remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT,
                              req_len, vma->vm_page_prot);
}

FLR / Bus Reset / PM Reset 우선순위: VFIO_DEVICE_RESET ioctl은 다음 순서로 리셋을 시도합니다.

  1. FLR (Function Level Reset): PCIe Capability의 FLR 비트를 사용. 가장 정밀한 리셋으로 해당 Function만 리셋합니다.
  2. PM Reset (D3hot → D0): PCI Power Management을 이용한 리셋. 일부 장치에서 불완전할 수 있습니다.
  3. Bus Reset (Secondary Bus Reset): 해당 버스 아래 모든 장치를 리셋합니다. 같은 IOMMU 그룹 내 다른 장치에 영향을 줄 수 있어 마지막 수단입니다.
/* vfio_pci_core_ioctl() — VFIO_DEVICE_RESET 처리 */
static int vfio_pci_core_ioctl_reset(struct vfio_pci_core_device *vdev)
{
    int ret;

    /* 1. FLR 시도 */
    if (vdev->has_flr) {
        ret = pci_reset_function(vdev->pdev);
        if (!ret) goto post_reset;
    }

    /* 2. PM Reset 시도 */
    if (vdev->has_pm_reset) {
        ret = pci_pm_reset(vdev->pdev, PCI_RESET_DO_RESET);
        if (!ret) goto post_reset;
    }

    /* 3. Bus Reset (같은 그룹 모든 장치 영향) */
    ret = pci_reset_bus(vdev->pdev);

post_reset:
    /* Config space 재초기화 */
    vfio_pci_set_power_state(vdev, PCI_D0);
    vfio_pci_init_config(vdev);
    return ret;
}

Interrupt Remapping (인터럽트 리매핑)

MSI/MSI-X Interrupt Remapping 흐름 PCI Device MSI/MSI-X Write addr: 0xFEExxxxx data: vector + delivery handle: IR Table Index MSI Write IOMMU Interrupt Remapping Engine Interrupt Remapping Table (IRT) Index → { dest_apic_id, vector, trigger_mode, delivery_mode } Source ID(BDF) 검증 → 위조 인터럽트 차단 Present Dest APIC Vector SID/SVT Source ID Verification: 요청 BDF ↔ IRT Entry SID 비교 Match → 인터럽트 전달 Mismatch → 인터럽트 차단 Physical LAPIC APIC ID → Physical CPU Core Posted Interrupt 사용 시 LAPIC 바이패스 가능 KVM vLAPIC → vCPU virtual APIC page → guest IDT handler 실행 Posted Interrupt: PIR → 직접 vCPU notification (VM-Exit 없음) Posted Interrupt (PI) PI Descriptor (PID): - Posted Interrupt Request (PIR) bitmap - Notification Vector (NV) - Notification Destination (NDST) 장점: VM-Exit 없이 인터럽트 전달 요구: Intel VT-d + APICv AMD: AVIC + GALOG 사용 vCPU가 sleep 상태면 wakeup IPI 발생 PI 경로

MSI/MSI-X 가상화: VFIO는 패스스루된 장치의 MSI/MSI-X 인터럽트를 KVM eventfd를 통해 VM에 전달합니다. 장치가 MSI 주소(0xFEExxxxx)에 쓰기를 하면, IOMMU의 Interrupt Remapping 테이블이 이를 적절한 CPU/벡터로 리매핑합니다.

기능Intel VT-d IRAMD-Vi (GALOG)
IR 테이블Interrupt Remapping Table (IRT)Interrupt Remapping Table + GA Log
Posted InterruptPI Descriptor (PID) + APICvGA Log + AVIC
Source ID 검증SID/SQ/SVT 필드DeviceID 기반 검증
VM-Exit 회피APICv + PI → VM-Exit 없음AVIC + GALOG → VM-Exit 없음
vCPU 대기 시Notification Vector → wakeupGA Log Entry → wakeup
/* VFIO MSI-X 인터럽트 설정 — eventfd 기반 */
#include <linux/vfio.h>
#include <sys/eventfd.h>

int setup_msix_interrupt(int device_fd, int vector_count)
{
    struct vfio_irq_set *irq_set;
    int32_t *fds;
    size_t alloc_size;

    alloc_size = sizeof(*irq_set) + vector_count * sizeof(int32_t);
    irq_set = calloc(1, alloc_size);

    irq_set->argsz = alloc_size;
    irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD
                   | VFIO_IRQ_SET_ACTION_TRIGGER;
    irq_set->index = VFIO_PCI_MSIX_IRQ_INDEX;
    irq_set->start = 0;
    irq_set->count = vector_count;

    fds = (int32_t *)irq_set->data;
    for (int i = 0; i < vector_count; i++)
        fds[i] = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);

    /* MSI-X 인터럽트 활성화 */
    ioctl(device_fd, VFIO_DEVICE_SET_IRQS, irq_set);

    /* 각 eventfd를 epoll/poll로 감시 */
    /* → KVM irqfd로 연결하면 VM-Exit 없이 인터럽트 전달 */
    free(irq_set);
    return 0;
}
/* KVM irqfd — eventfd → 게스트 인터럽트 직접 주입 */
struct kvm_irqfd irqfd = {
    .fd    = msix_eventfd,     /* VFIO MSI-X eventfd */
    .gsi   = guest_gsi,        /* 게스트 GSI 번호 */
    .flags = 0,
};
ioctl(kvm_vm_fd, KVM_IRQFD, &irqfd);

/* 결과: 장치 인터럽트 → eventfd → KVM → vLAPIC → vCPU */
/* Posted Interrupt 활성 시: 장치 → IOMMU → PID → vCPU (VM-Exit 없음) */

/* Posted Interrupt 활성화 조건: */
/* 1. CPU: Intel VT-x + APICv 또는 AMD-V + AVIC */
/* 2. IOMMU: VT-d IR + PI 또는 AMD-Vi GALOG */
/* 3. KVM: kvm_intel.enable_apicv=1 (기본값) */
/* 4. 장치: MSI/MSI-X 인터럽트 사용 */
Posted Interrupt 성능 효과: 일반적인 인터럽트 경로는 장치 → IOMMU → host IRQ → KVM → VM-Entry → guest ISR로 VM-Exit/VM-Entry 왕복이 필요합니다(~수 us). Posted Interrupt는 IOMMU가 직접 vCPU의 PIR(Posted Interrupt Request) 레지스터에 벡터를 기록하고, vCPU가 다음 VM-Entry 시 자동으로 인터럽트를 인식합니다. 네트워크 패킷 처리나 NVMe I/O 완료 같은 고빈도 인터럽트 시나리오에서 10-30%의 지연시간 개선이 가능합니다.

진단 및 디버깅

# VFIO 로드 상태 확인
lsmod | grep vfio
# vfio_pci, vfio_pci_core, vfio_iommu_type1, vfio

# 장치가 vfio-pci에 바인딩됐는지 확인
lspci -nnk -d 10de:2684
# Kernel driver in use: vfio-pci  ← 성공
# Kernel driver in use: nvidia    ← 아직 바인딩 안 됨

# IOMMU 그룹 상태
ls /sys/kernel/iommu_groups/14/devices/
cat /sys/kernel/iommu_groups/14/type  # DMAv42 또는 identity

# VFIO 이벤트 dmesg 확인
dmesg | grep -i vfio | tail -20

# mdevctl로 mdev 상태 확인
mdevctl list --defined   # 정의된 인스턴스
mdevctl list             # 활성 인스턴스

mdev 드라이버 스택

mdev 드라이버 스택 — VM에서 물리 디바이스까지의 전체 흐름 Guest Driver (VM 내부) nvidia.ko / amdvgpu.ko / i915 GPU Driver VM 1 (Guest) vfio-pci / mdev MMIO (GPA) Guest OS Kernel VM 2 (Guest) vfio-pci / mdev MMIO (GPA) Guest OS Kernel VM 3 (Guest) vfio-pci / mdev MMIO (GPA) Guest OS Kernel MMIO MMIO MMIO QEMU / KVM 하이퍼바이저 GPA → HPA 매핑 · VFIO ioctl · Memory Listener KVM_SET_USER_MEMORY_REGION VFIO_DEVICE_GET_INFO VFIO_IOMMU_MAP_DMA mdev_create() mdev_create() mdev_create() mdev instance 0 UUID: aa-aa-aa-aa (VM1) VRAM: 2GB · 1 vGPU ENC: 1 · DEC: 1 mdev instance 1 UUID: bb-bb-bb-bb (VM2) VRAM: 4GB · 2 vGPU ENC: 2 · DEC: 2 mdev instance 2 UUID: cc-cc-cc-cc (VM3) VRAM: 2GB · 1 vGPU ENC: 1 · DEC: 1 vGPU Manager (Vendor Driver) nvidia.ko (vGPU) / amdvgpu.ko (MxGPU) / i915 (GVT-g) GPU partitioning · Scheduling · Memory mediation mdev_parent_ops vfio_mdev / mdev 코어 /drivers/vfio/mdev/ — IOMMU 격리 · DMA 리맵 create/remove callbacks open/release callbacks supported_type_groups VFIO Container /dev/vfio/vfio VFIO Group /dev/vfio/14 SET_CONTAINER SET_IOMMU VFIO_IOMMU_MAP_DMA DMA Mapping IOMMU ops IOMMU (Intel VT-d / AMD-Vi / ARM SMMU) IOVA → HPA 변환 · DMA 격리 · Interrupt Remapping IRQ (MSI/MSI-X) 물리 GPU Hardware NVIDIA A100 (4 instances) / AMD Instinct MI250 / Intel Data Center GPU Host GPU Driver (nvidia.ko / amdgpu.ko / i915) GPU Firmware Load · Hardware Control · Performance Monitoring Guest Physical Address (GPA) Host Physical Address (HPA) 데이터 흐름 유형 Control Path (ioctl) DMA Mapping MMIO Access Interrupt (IRQ) Vendor Driver VFIO/mdev Core VFIO Interface 자원 할당 요약 Total VRAM: 8GB vGPU Count: 4 instances Encoder: 4 · Decoder: 4 IOMMU Group: 14 * 예: NVIDIA A100 40GB 기준 DMA: GPU → IOMMU(IOVA→HPA) → Host Memory MMIO: Guest → QEMU → mdev → vGPU Manager → GPU Register

커널 소스 가이드

파일 / 디렉토리설명
drivers/vfio/VFIO 코어 — container, group, device 관리
drivers/vfio/pci/vfio-pci 드라이버 — PCI 장치 패스스루
drivers/vfio/vfio_iommu_type1.cx86 IOMMU 백엔드 — DMA 매핑/해제
drivers/vfio/mdev/mdev 코어 — 가상 장치 생성/삭제
include/linux/vfio.hVFIO 공개 API 헤더
include/linux/mdev.hmdev API — mdev_parent_ops 정의
include/uapi/linux/vfio.h사용자 공간 VFIO ioctl 상수
Documentation/driver-api/vfio.rstVFIO 공식 커널 문서
Documentation/driver-api/vfio-mediated-device.rstmdev 드라이버 작성 가이드

커널 설정

# VFIO 핵심
CONFIG_VFIO=y                       # VFIO 코어
CONFIG_VFIO_PCI=y                   # vfio-pci 드라이버
CONFIG_VFIO_PCI_VGA=y               # VGA 장치 패스스루 (GPU)
CONFIG_VFIO_IOMMU_TYPE1=y           # x86 IOMMU 백엔드
CONFIG_VFIO_MDEV=y                  # mdev 코어
CONFIG_VFIO_MDEV_DEVICE=y           # mdev VFIO 장치

# IOMMU (선택)
CONFIG_INTEL_IOMMU=y                # Intel VT-d
CONFIG_AMD_IOMMU=y                  # AMD-Vi
CONFIG_IOMMU_SUPPORT=y
CONFIG_PCI_ACS=y                    # PCIe ACS 지원 (그룹 분리)
CONFIG_PCI_PASID=y                  # Process Address Space ID

# KVM 통합
CONFIG_KVM=y
CONFIG_KVM_VFIO=y                   # KVM ↔ VFIO 통합
다음 학습 경로: